Merge branch 'attachRework' into 'master'

Attach rework

Closes #1447

See merge request core-developers/forge!5059
This commit is contained in:
Michael Kamensky
2021-09-26 03:37:05 +00:00
49 changed files with 579 additions and 438 deletions

View File

@@ -860,7 +860,7 @@ public class AiController {
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
}
if (sa.usesTargeting()) {
if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa)) {
return AiPlayDecision.TargetingFailed;
}
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {

View File

@@ -1252,7 +1252,7 @@ public abstract class GameState {
p.getGame().getAction().moveTo(ZoneType.Battlefield, c, null);
} else {
p.getZone(ZoneType.Hand).add(c);
p.getGame().getAction().moveToPlay(c, null);
p.getGame().getAction().moveToPlay(c, null, null);
}
c.setTapped(tapped);

View File

@@ -175,7 +175,7 @@ public abstract class SpellAbilityAi {
// a mandatory SpellAbility with targeting but without candidates,
// does not need to go any deeper
if (sa.usesTargeting() && mandatory && !sa.isTargetNumberValid()
&& !sa.getTargetRestrictions().hasCandidates(sa, true)) {
&& !sa.getTargetRestrictions().hasCandidates(sa)) {
return false;
}
@@ -233,7 +233,7 @@ public abstract class SpellAbilityAi {
// sub-SpellAbility might use targets too
if (sa.usesTargeting()) {
// no Candidates, no adding to Stack
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
if (!sa.getTargetRestrictions().hasCandidates(sa)) {
return false;
}
// but if it does, it should override this function

View File

@@ -1,10 +1,6 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.*;
import com.google.common.base.Predicate;
@@ -12,6 +8,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.ai.AiAttackController;
import forge.ai.AiCardMemory;
import forge.ai.AiController;
import forge.ai.AiProps;
@@ -52,6 +49,7 @@ import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
public class AttachAi extends SpellAbilityAi {
@@ -1330,6 +1328,29 @@ public class AttachAi extends SpellAbilityAi {
return null;
}
// Is a SA that moves target attachment
if ("MoveTgtAura".equals(sa.getParam("AILogic"))) {
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);
list = CardLists.filter(list, Predicates.not(CardPredicates.isProtectedFrom(attachSource)));
list = CardLists.filter(list, Predicates.or(CardPredicates.isControlledByAnyOf(aiPlayer.getOpponents()), new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
return ComputerUtilCard.isUselessCreature(aiPlayer, card.getAttachedTo());
}
}));
return !list.isEmpty() ? ComputerUtilCard.getBestAI(list) : null;
} else if ("Unenchanted".equals(sa.getParam("AILogic"))) {
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);
CardCollection preferred = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
return card.getAttachedCards().isEmpty();
}
});
return preferred.isEmpty() ? Aggregates.random(list) : Aggregates.random(preferred);
}
// is no attachment so no using attach
if (!attachSource.isAttachment()) {
return null;
@@ -1442,10 +1463,11 @@ public class AttachAi extends SpellAbilityAi {
* the logic
* @return the card
*/
private static Card attachGeneralAI(final Player ai, final SpellAbility sa, final List<Card> list, final boolean mandatory,
public static Card attachGeneralAI(final Player ai, final SpellAbility sa, final List<Card> list, final boolean mandatory,
final Card attachSource, final String logic) {
Player prefPlayer = ai.getWeakestOpponent();
if ("Pump".equals(logic) || "Animate".equals(logic) || "Curiosity".equals(logic)) {
Player prefPlayer = AiAttackController.choosePreferredDefenderPlayer(ai);
if ("Pump".equals(logic) || "Animate".equals(logic) || "Curiosity".equals(logic) || "MoveTgtAura".equals(logic)
|| "MoveAllAuras".equals(logic)) {
prefPlayer = ai;
}
// Some ChangeType cards are beneficial, and PrefPlayer should be
@@ -1470,14 +1492,13 @@ public class AttachAi extends SpellAbilityAi {
return chooseUnpreferred(mandatory, list);
}
// Preferred list has at least one card in it to make to the actual
// Logic
// Preferred list has at least one card in it to make to the actual Logic
Card c = null;
if ("GainControl".equals(logic)) {
c = attachAIControlPreference(sa, prefList, mandatory, attachSource);
} else if ("Curse".equals(logic)) {
c = attachAICursePreference(sa, prefList, mandatory, attachSource, ai);
} else if ("Pump".equals(logic)) {
} else if ("Pump".equals(logic) || (logic != null && logic.startsWith("Move"))) {
c = attachAIPumpPreference(ai, sa, prefList, mandatory, attachSource);
} else if ("Curiosity".equals(logic)) {
c = attachAICuriosityPreference(sa, prefList, mandatory, attachSource);
@@ -1725,7 +1746,7 @@ public class AttachAi extends SpellAbilityAi {
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
return attachToCardAIPreferences(ai, sa, true);
return attachGeneralAI(ai, sa, (List<Card>)options, !isOptional, sa.getHostCard(), sa.getParam("AILogic"));
}
@Override

View File

@@ -1,11 +1,6 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
@@ -59,6 +54,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.MyRandom;
public class ChangeZoneAi extends SpellAbilityAi {
@@ -909,6 +905,19 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
});
}
if (sa.hasParam("AttachAfter")) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (card.isValid(sa.getParam("AttachAfter"), ai, c, sa)) {
return true;
}
}
return false;
}
});
}
if (list.size() < sa.getMinTargets()) {
return false;
@@ -1702,7 +1711,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
// Called when looking for creature to attach aura or equipment
return ComputerUtilCard.getBestAI(options);
return AttachAi.attachGeneralAI(ai, sa, (List<Card>)options, !isOptional, sa.getHostCard(), sa.getParam("AILogic"));
}
/* (non-Javadoc)
@@ -1710,9 +1719,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
// Currently only used by Curse of Misfortunes, so this branch should never get hit
// But just in case it does, just select the first option
return Iterables.getFirst(options, null);
// Called when attaching Aura to player
return Aggregates.random(options);
}
private boolean doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) {

View File

@@ -203,15 +203,23 @@ public class Game {
}
}
public CardCollectionView copyLastStateBattlefield() {
public CardCollectionView copyLastState(ZoneType type) {
CardCollection result = new CardCollection();
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (final Player p : getPlayers()) {
result.addAll(p.getZone(ZoneType.Battlefield).getLKICopy(cachedMap));
result.addAll(p.getZone(type).getLKICopy(cachedMap));
}
return result;
}
public CardCollectionView copyLastStateBattlefield() {
return copyLastState(ZoneType.Battlefield);
}
public CardCollectionView copyLastStateGraveyard() {
return copyLastState(ZoneType.Graveyard);
}
public void updateLastStateForCard(Card c) {
if (c == null || c.getZone() == null) {
return;

View File

@@ -43,7 +43,6 @@ import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.AttachEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
@@ -68,11 +67,13 @@ import forge.game.mulligan.MulliganService;
import forge.game.player.GameLossReason;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerPredicates;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.staticability.StaticAbilityLayer;
@@ -83,7 +84,9 @@ import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.CardTranslation;
import forge.util.Expressions;
import forge.util.Localizer;
import forge.util.MyRandom;
import forge.util.ThreadUtil;
import forge.util.Visitor;
@@ -158,6 +161,28 @@ public class GameAction {
return c;
}
// Aura entering indirectly
// need to check before it enters
if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) {
boolean found = false;
if (Iterables.any(game.getPlayers(),PlayerPredicates.canBeAttached(c))) {
found = true;
}
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c))) {
found = true;
}
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c))) {
found = true;
}
if (!found) {
c.clearControllers();
if (c.removeChangedState()) {
c.updateStateForView();
}
return c;
}
}
// LKI is only needed when something is moved from the battlefield.
// also it does messup with Blink Effects like Eldrazi Displacer
if (fromBattlefield && zoneTo != null && !zoneTo.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Flashback)) {
@@ -363,6 +388,27 @@ public class GameAction {
copied.getOwner().removeInboundToken(copied);
// Aura entering as Copy from stack
// without targets it is sent to graveyard
if (copied.isAura() && !copied.isAttachedToEntity() && toBattlefield) {
if (zoneFrom != null && zoneFrom.is(ZoneType.Stack) && game.getStack().isResolving(c)) {
boolean found = false;
if (Iterables.any(game.getPlayers(),PlayerPredicates.canBeAttached(copied))) {
found = true;
}
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(copied))) {
found = true;
}
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(copied))) {
found = true;
}
if (!found) {
return moveToGraveyard(copied, cause, params);
}
}
attachAuraOnIndirectEnterBattlefield(copied, params);
}
// Handle merged permanent here so all replacement effects are already applied.
CardCollection mergedCards = null;
if (fromBattlefield && !toBattlefield && c.hasMergedCard()) {
@@ -736,13 +782,6 @@ public class GameAction {
c.setCastSA(null);
}
if (c.isAura() && zoneTo.is(ZoneType.Battlefield) && ((zoneFrom == null) || !zoneFrom.is(ZoneType.Stack))
&& !c.isEnchanting()) {
// TODO Need a way to override this for Abilities that put Auras
// into play attached to things
AttachEffect.attachAuraOnIndirectEnterBattlefield(c);
}
if (c.isRealCommander()) {
c.setMoveToCommandZone(true);
}
@@ -842,13 +881,8 @@ public class GameAction {
return moveTo(hand, c, cause, params);
}
public final Card moveToPlay(final Card c, SpellAbility cause) {
final PlayerZone play = c.getController().getZone(ZoneType.Battlefield);
return moveTo(play, c, cause, null);
}
public final Card moveToPlay(final Card c, final Player p, SpellAbility cause) {
return moveToPlay(c, p, cause, null);
public final Card moveToPlay(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
return moveToPlay(c, c.getController(), cause, params);
}
public final Card moveToPlay(final Card c, final Player p, SpellAbility cause, Map<AbilityKey, Object> params) {
@@ -1225,6 +1259,7 @@ public class GameAction {
}
CardCollection noRegCreats = null;
CardCollection desCreats = null;
CardCollection unAttachList = new CardCollection();
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
if (c.isCreature()) {
// Rule 704.5f - Put into grave (no regeneration) for toughness <= 0
@@ -1259,12 +1294,7 @@ public class GameAction {
}
checkAgain |= stateBasedAction_Saga(c, table);
checkAgain |= stateBasedAction704_attach(c, table); // Attachment
if (c.isCreature() && c.isAttachedToEntity()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached
c.unattachFromEntity(c.getEntityAttachedTo());
checkAgain = true;
}
checkAgain |= stateBasedAction704_attach(c, unAttachList); // Attachment
checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones
@@ -1291,10 +1321,30 @@ public class GameAction {
}
}
// cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(c);
checkAgain = true;
}
if (checkAgain) {
cardsToUpdateLKI.add(c);
}
}
for (Card u : unAttachList) {
u.unattachFromEntity(u.getEntityAttachedTo());
// cleanup aura
if (u.isAura() && u.isInPlay() && !u.isEnchanting()) {
if (noRegCreats == null) {
noRegCreats = new CardCollection();
}
noRegCreats.add(u);
checkAgain = true;
}
}
// only check static abilities once after destroying all the creatures
// (e.g. helpful for Erebos's Titan and another creature dealing lethal damage to each other simultaneously)
@@ -1423,13 +1473,13 @@ public class GameAction {
}
}
private boolean stateBasedAction704_attach(Card c, CardZoneTable table) {
private boolean stateBasedAction704_attach(Card c, CardCollection unAttachList) {
boolean checkAgain = false;
if (c.isAttachedToEntity()) {
final GameEntity ge = c.getEntityAttachedTo();
if (!ge.canBeAttached(c, true)) {
c.unattachFromEntity(ge);
unAttachList.add(c);
checkAgain = true;
}
}
@@ -1437,16 +1487,13 @@ public class GameAction {
if (c.hasCardAttachments()) {
for (final Card attach : Lists.newArrayList(c.getAttachedCards())) {
if (!attach.isInPlay()) {
attach.unattachFromEntity(c);
unAttachList.add(attach);
checkAgain = true;
}
}
}
// cleanup aura
if (c.isAura() && c.isInPlay() && !c.isEnchanting()) {
c.updateWasDestroyed(true);
sacrificeDestroy(c, null, table, null);
if (c.isCreature() && c.isAttachedToEntity()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached
unAttachList.add(c);
checkAgain = true;
}
return checkAgain;
@@ -2260,4 +2307,70 @@ public class GameAction {
runParams.put(AbilityKey.Player, player);
game.getTriggerHandler().runTrigger(TriggerType.DungeonCompleted, runParams, false);
}
/**
* Attach aura on indirect enter battlefield.
*
* @param source
* the source
* @return true, if successful
*/
public static boolean attachAuraOnIndirectEnterBattlefield(final Card source, Map<AbilityKey, Object> params) {
// When an Aura ETB without being cast you can choose a valid card to
// attach it to
final SpellAbility aura = source.getFirstAttachSpell();
if (aura == null) {
return false;
}
aura.setActivatingPlayer(source.getController());
final Game game = source.getGame();
final TargetRestrictions tgt = aura.getTargetRestrictions();
Player p = source.getController();
if (tgt.canTgtPlayer()) {
final FCollection<Player> players = new FCollection<>();
for (Player player : game.getPlayers()) {
if (player.isValid(tgt.getValidTgts(), aura.getActivatingPlayer(), source, aura)) {
players.add(player);
}
}
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (pa != null) {
source.attachToEntity(pa);
return true;
}
}
else {
List<ZoneType> zones = Lists.newArrayList(tgt.getZone());
CardCollection list = new CardCollection();
if (params != null) {
if (zones.contains(ZoneType.Battlefield)) {
list.addAll((CardCollectionView) params.get(AbilityKey.LastStateBattlefield));
zones.remove(ZoneType.Battlefield);
}
if (zones.contains(ZoneType.Graveyard)) {
list.addAll((CardCollectionView) params.get(AbilityKey.LastStateGraveyard));
zones.remove(ZoneType.Graveyard);
}
}
list.addAll(game.getCardsIn(zones));
list = CardLists.getValidCards(list, tgt.getValidTgts(), aura.getActivatingPlayer(), source, aura);
if (list.isEmpty()) {
return false;
}
final Card o = p.getController().chooseSingleEntityForEffect(list, aura,
Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (o != null) {
source.attachToEntity(game.getCardState(o), true);
return true;
}
}
return false;
}
}

View File

@@ -77,6 +77,7 @@ public enum AbilityKey {
IndividualCostPaymentInstance("IndividualCostPaymentInstance"),
IsMadness("IsMadness"),
LastStateBattlefield("LastStateBattlefield"),
LastStateGraveyard("LastStateGraveyard"),
LifeAmount("LifeAmount"), //TODO confirm that this and LifeGained can be merged
LifeGained("LifeGained"),
Mana("Mana"),

View File

@@ -405,8 +405,12 @@ public class AbilityUtils {
}
if (incR.length > 1 && !cards.isEmpty()) {
final String excR = "Card." + incR[1];
cards = CardLists.getValidCards(cards, excR.split(","), hostCard.getController(), hostCard, sa);
String[] valids = incR[1].split(",");
// need to add valids onto all of them
for (int i = 0; i < valids.length; i++) {
valids[i] = "Card." + valids[i];
}
cards = CardLists.getValidCards(cards, valids, hostCard.getController(), hostCard, sa);
}
return cards;

View File

@@ -1,73 +1,49 @@
package forge.game.ability.effects;
import java.util.List;
import forge.GameCommand;
import java.util.List;
import java.util.Map;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.collect.FCollection;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
public class AttachEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
if (host.isAura() && sa.isSpell()) {
CardZoneTable table = new CardZoneTable();
host.setController(sa.getActivatingPlayer(), 0);
ZoneType previousZone = host.getZone().getZoneType();
// The Spell_Permanent (Auras) version of this AF needs to
// move the card into play before Attaching
final Card c = game.getAction().moveToPlay(host, sa);
sa.setHostCard(c);
ZoneType newZone = c.getZone().getZoneType();
if (newZone != previousZone) {
table.put(previousZone, newZone, c);
}
table.triggerChangesZoneAll(game, sa);
}
final Card source = sa.getHostCard();
final Game game = source.getGame();
CardCollection attachments;
final List<GameObject> targets = getDefinedOrTargeted(sa, "Defined");
GameObject attachTo;
if (targets.isEmpty()) {
return;
}
attachTo = targets.get(0);
String attachToName = null;
if (attachTo instanceof Card) {
attachToName = CardTranslation.getTranslatedName(((Card)attachTo).getName());
}
else {
attachToName = attachTo.toString();
}
final Player p = sa.getActivatingPlayer();
if (sa.hasParam("Choices")) {
Player chooser = p;
if (sa.hasParam("Chooser")) {
chooser = Iterables.getFirst(AbilityUtils.getDefinedPlayers(source, sa.getParam("Chooser"), sa), null);
};
if (sa.hasParam("Object")) {
attachments = AbilityUtils.getDefinedCards(source, sa.getParam("Object"), sa);
} else if (sa.hasParam("Choices")) {
ZoneType choiceZone = ZoneType.Battlefield;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
@@ -76,23 +52,100 @@ public class AttachEffect extends SpellAbilityEffect {
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"), p, source, sa);
Card c = p.getController().chooseSingleEntityForEffect(choices, sa, title, null);
Map<String, Object> params = Maps.newHashMap();
params.put("Target", Iterables.getFirst(getDefinedEntitiesOrTargeted(sa, "Defined"), null));
Card c = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, params);
if (c == null) {
return;
}
attachments = new CardCollection(c);
} else if (sa.hasParam("Object")) {
attachments = AbilityUtils.getDefinedCards(source, sa.getParam("Object"), sa);
} else {
attachments = new CardCollection(source);
}
if (attachments.isEmpty()) {
return;
}
GameEntity attachTo;
if (sa.hasParam("Object") && sa.hasParam("Choices")) {
ZoneType choiceZone = ZoneType.Battlefield;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChoose") + " ";
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"), p, source, sa);
// Object + Choices means Attach Aura/Equipment onto new another card it can attach
// if multiple attachments, all of them need to be able to attach to new card
for (final Card attachment : attachments) {
if (sa.hasParam("Move")) {
Card e = attachment.getAttachedTo();
if (e != null)
choices.remove(e);
}
choices = CardLists.filter(choices, CardPredicates.canBeAttached(attachment));
}
Map<String, Object> params = Maps.newHashMap();
params.put("Attachments", attachments);
attachTo = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, params);
} else {
FCollection<GameEntity> targets = new FCollection<>(getDefinedEntitiesOrTargeted(sa, "Defined"));
if (targets.isEmpty()) {
return;
} else {
String title = Localizer.getInstance().getMessage("lblChoose");
Map<String, Object> params = Maps.newHashMap();
params.put("Attachments", attachments);
attachTo = chooser.getController().chooseSingleEntityForEffect(targets, sa, title, params);
}
}
String attachToName = null;
if (attachTo == null) {
return;
} else if (attachTo instanceof Card) {
attachToName = CardTranslation.getTranslatedName(((Card)attachTo).getName());
} else {
attachToName = attachTo.toString();
}
// If Cast Targets will be checked on the Stack
for (final Card attachment : attachments) {
String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", CardTranslation.getTranslatedName(attachment.getName()), attachToName);
if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, null, message))
// TODO add params for message
continue;
handleAttachment(attachment, attachTo, sa);
attachment.attachToEntity(attachTo);
}
if (source.isAura() && sa.isSpell()) {
CardZoneTable table = new CardZoneTable();
source.setController(sa.getActivatingPlayer(), 0);
ZoneType previousZone = source.getZone().getZoneType();
//CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
//CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
//moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
//moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
// The Spell_Permanent (Auras) version of this AF needs to
// move the card into play before Attaching
final Card c = game.getAction().moveToPlay(source, source.getController(), sa, moveParams);
ZoneType newZone = c.getZone().getZoneType();
if (newZone != previousZone) {
table.put(previousZone, newZone, c);
}
table.triggerChangesZoneAll(game, sa);
}
}
@@ -108,121 +161,4 @@ public class AttachEffect extends SpellAbilityEffect {
sb.append(Lang.joinHomogenous(targets));
return sb.toString();
}
/**
* Handle attachment.
*
* @param card
* the card
* @param o
* the o
*/
public static void handleAttachment(final Card card, final Object o, final SpellAbility sa) {
if (card == null) { return; }
if (o instanceof Card) {
final Card c = (Card) o;
if (card.isAura()) {
// Most Auras can enchant permanents, a few can Enchant cards in
// graveyards
// Spellweaver Volute, Dance of the Dead, Animate Dead
// Although honestly, I'm not sure if the three of those could
// handle being scripted
// 303.4h: If the card can't be enchanted, the aura doesn't move
if (c.canBeAttached(card)) {
handleAura(card, c);
}
} else {
card.attachToEntity(c);
}
} else if (o instanceof Player) {
// Currently, a few cards can enchant players
// Psychic Possession, Paradox Haze, Wheel of Sun and Moon, New
// Curse cards
final Player p = (Player) o;
if (card.isAura()) {
handleAura(card, p);
}
}
}
/**
* Handle aura.
*
* @param card
* the card
* @param tgt
* the tgt
*/
public static void handleAura(final Card card, final GameEntity tgt) {
final GameCommand onLeavesPlay = new GameCommand() {
private static final long serialVersionUID = -639204333673364477L;
@Override
public void run() {
final GameEntity entity = card.getEntityAttachedTo();
if (entity == null) {
return;
}
card.unattachFromEntity(entity);
}
}; // Command
card.addLeavesPlayCommand(onLeavesPlay);
card.attachToEntity(tgt);
}
/**
* Attach aura on indirect enter battlefield.
*
* @param source
* the source
* @return true, if successful
*/
public static boolean attachAuraOnIndirectEnterBattlefield(final Card source) {
// When an Aura ETB without being cast you can choose a valid card to
// attach it to
final SpellAbility aura = source.getFirstAttachSpell();
if (aura == null) {
return false;
}
aura.setActivatingPlayer(source.getController());
final Game game = source.getGame();
final TargetRestrictions tgt = aura.getTargetRestrictions();
Player p = source.getController();
if (tgt.canTgtPlayer()) {
final FCollection<Player> players = new FCollection<>();
for (Player player : game.getPlayers()) {
if (player.isValid(tgt.getValidTgts(), aura.getActivatingPlayer(), source, aura)) {
players.add(player);
}
}
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (pa != null) {
handleAura(source, pa);
return true;
}
}
else {
CardCollectionView list = game.getCardsIn(tgt.getZone());
list = CardLists.getValidCards(list, tgt.getValidTgts(), aura.getActivatingPlayer(), source, aura);
if (list.isEmpty()) {
return false;
}
final Card o = p.getController().chooseSingleEntityForEffect(list, aura,
Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (o != null) {
handleAura(source, o);
//source.enchantEntity((Card) o);
return true;
}
}
return false;
}
}

View File

@@ -13,6 +13,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
@@ -55,6 +56,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
CardCollection cards;
List<Player> tgtPlayers = getTargetPlayers(sa);
final Game game = sa.getActivatingPlayer().getGame();
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
if ((!sa.usesTargeting() && !sa.hasParam("Defined")) || sa.hasParam("UseAllOriginZones")) {
cards = new CardCollection(game.getCardsIn(origin));
@@ -164,6 +167,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
}
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
if (destination == ZoneType.Battlefield) {
if (sa.hasAdditionalAbility("AnimateSubAbility")) {
@@ -174,14 +179,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
source.removeRemembered(c);
}
// Auras without Candidates stay in their current location
if (c.isAura()) {
final SpellAbility saAura = c.getFirstAttachSpell();
if (saAura != null && !saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
continue;
}
}
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
@@ -205,6 +202,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
}
}
if (!movedCard.getZone().equals(originZone)) {
if (remember != null) {
final Card newSource = game.getCardState(source);
newSource.addRemembered(movedCard);
@@ -247,7 +245,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
movedCard.setTimestamp(ts);
}
if (!movedCard.getZone().equals(originZone)) {
triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
if (c.getMeldedWith() != null) {

View File

@@ -496,6 +496,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
chooser = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Chooser"), sa).get(0);
}
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
for (final Card tgtC : tgtCards) {
final Card gameCard = game.getCardState(tgtC, null);
// gameCard is LKI in that case, the card is not in game anymore
@@ -582,13 +585,22 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("AttachedTo")) {
CardCollection list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("AttachedTo"), sa);
if (list.isEmpty()) {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachedTo"), gameCard.getController(), gameCard, sa);
list = CardLists.getValidCards(lastStateBattlefield, sa.getParam("AttachedTo"), hostCard.getController(), hostCard, sa);
}
// only valid choices are when they could be attached
// TODO for multiple Auras entering attached this way, need to use LKI info
if (!list.isEmpty()) {
list = CardLists.filter(list, CardPredicates.canBeAttached(gameCard));
}
if (!list.isEmpty()) {
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", gameCard);
Card attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", gameCard.toString()), params);
gameCard.attachToEntity(attachedTo);
// TODO can't attach later or moveToPlay would attach indirectly
// bypass canBeAttached to skip Protection checks when trying to attach multiple auras that would grant protection
gameCard.attachToEntity(game.getCardState(attachedTo), true);
} else { // When it should enter the battlefield attached to an illegal permanent it fails
continue;
}
@@ -608,6 +620,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
if (sa.isReplacementAbility()) {
ReplacementEffect re = sa.getReplacementEffect();
moveParams.put(AbilityKey.ReplacementEffect, re);
@@ -627,20 +641,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
hostCard.removeRemembered(gameCard);
}
// Auras without Candidates stay in their current location
if (gameCard.isAura()) {
final SpellAbility saAura = gameCard.getFirstAttachSpell();
if (saAura != null) {
saAura.setActivatingPlayer(sa.getActivatingPlayer());
if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
if (sa.hasAdditionalAbility("AnimateSubAbility")) {
gameCard.removeChangedState();
}
continue;
}
}
}
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown")) {
gameCard.turnFaceDown(true);
@@ -648,7 +648,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
movedCard = game.getAction().moveTo(gameCard.getController().getZone(destination), gameCard, sa, moveParams);
if (sa.hasParam("Unearth")) {
// below stuff only if it changed zones
if (!movedCard.getZone().equals(originZone)) {
continue;
}
if (sa.hasParam("Unearth") && movedCard.isInPlay()) {
movedCard.setUnearthed(true);
movedCard.addChangedCardKeywords(Lists.newArrayList("Haste"), null, false,
game.getNextTimestamp(), 0, true);
@@ -670,6 +674,19 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
combatChanged = true;
}
movedCard.setTimestamp(ts);
if (sa.hasParam("AttachAfter") && movedCard.isAttachment()) {
CardCollection list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("AttachAfter"), sa);
if (list.isEmpty()) {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), hostCard.getController(), hostCard, sa);
}
if (!list.isEmpty()) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(gameCard.getName()));
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", gameCard);
Card attachedTo = chooser.getController().chooseSingleEntityForEffect(list, sa, title, params);
movedCard.attachToEntity(attachedTo);
}
}
} else {
// might set before card is moved only for nontoken
Card host = null;
@@ -1166,6 +1183,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
boolean combatChanged = false;
final CardZoneTable triggerList = new CardZoneTable();
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
for (Player player : HiddenOriginChoicesMap.keySet()) {
boolean searchedLibrary = HiddenOriginChoicesMap.get(player).searchedLibrary;
boolean shuffleMandatory = HiddenOriginChoicesMap.get(player).shuffleMandatory;
@@ -1182,6 +1202,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final Zone originZone = game.getZoneOf(c);
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
if (destination.equals(ZoneType.Library)) {
movedCard = game.getAction().moveToLibrary(c, libraryPos, sa, moveParams);
}
@@ -1225,26 +1247,25 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
if (sa.hasParam("AttachedTo")) {
if (sa.hasParam("AttachedTo") && c.isAttachment()) {
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachedTo"), sa);
if (list.isEmpty()) {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachedTo"), c.getController(), c, sa);
list = CardLists.getValidCards(lastStateBattlefield, sa.getParam("AttachedTo"), source.getController(), source, sa);
}
// only valid choices are when they could be attached
// TODO for multiple Auras entering attached this way, need to use LKI info
if (!list.isEmpty()) {
list = CardLists.filter(list, CardPredicates.canBeAttached(c));
}
if (!list.isEmpty()) {
Card attachedTo = null;
if (list.size() > 1) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", c);
attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
}
else {
attachedTo = list.get(0);
}
Card attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
if (c.isAttachment()) {
c.attachToEntity(attachedTo);
}
// TODO can't attach later or moveToPlay would attach indirectly
// bypass canBeAttached to skip Protection checks when trying to attach multiple auras that would grant protection
c.attachToEntity(game.getCardState(attachedTo), true);
}
else { // When it should enter the battlefield attached to an illegal permanent it fails
continue;
@@ -1277,6 +1298,20 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard = game.getAction().moveToPlay(c, c.getController(), sa, moveParams);
movedCard.setTimestamp(ts);
if (sa.hasParam("AttachAfter") && movedCard.isAttachment()) {
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachAfter"), sa);
if (list.isEmpty()) {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), c.getController(), c, sa);
}
if (!list.isEmpty()) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", movedCard);
Card attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
movedCard.attachToEntity(attachedTo);
}
}
}
else if (destination.equals(ZoneType.Exile)) {
movedCard = game.getAction().exile(c, sa, moveParams);

View File

@@ -148,11 +148,6 @@ public class CloneEffect extends SpellAbilityEffect {
tgtCard.clearRemembered();
}
// check if clone is now an Aura that needs to be attached
if (tgtCard.isAura() && !tgtCard.isInZone(ZoneType.Battlefield)) {
AttachEffect.attachAuraOnIndirectEnterBattlefield(tgtCard);
}
if (sa.hasParam("Duration")) {
final Card cloneCard = tgtCard;
// if clone is temporary, target needs old values back after (keep Death-Mask Duplicant working)

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import java.util.Map;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -14,12 +15,18 @@ import forge.game.spellability.SpellAbility;
public class ETBReplacementEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Game game = sa.getActivatingPlayer().getGame();
final Card card = (Card) sa.getReplacingObject(AbilityKey.Card);
Map<AbilityKey, Object> params = AbilityKey.newMap();
params.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI));
params.put(AbilityKey.ReplacementEffect, sa.getReplacementEffect());
params.put(AbilityKey.LastStateBattlefield, sa.getReplacingObject(AbilityKey.LastStateBattlefield));
params.put(AbilityKey.LastStateGraveyard, sa.getReplacingObject(AbilityKey.LastStateGraveyard));
final SpellAbility root = sa.getRootAbility();
SpellAbility cause = (SpellAbility) root.getReplacingObject(AbilityKey.Cause);
sa.getActivatingPlayer().getGame().getAction().moveToPlay(card, card.getController(), cause, params);
game.getAction().moveToPlay(card, card.getController(), cause, params);
}
}

View File

@@ -1,10 +1,16 @@
package forge.game.ability.effects;
import java.util.Map;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -23,6 +29,13 @@ public class ManifestEffect extends SpellAbilityEffect {
// Most commonly "defined" is Top of Library
final String defined = sa.hasParam("Defined") ? sa.getParam("Defined") : "TopOfLibrary";
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
for (final Player p : getTargetPlayers(sa, "DefinedPlayer")) {
if (sa.usesTargeting() || p.canBeTargetedBy(sa)) {
CardCollection tgtCards;
@@ -52,8 +65,8 @@ public class ManifestEffect extends SpellAbilityEffect {
CardLists.shuffle(tgtCards);
}
for (Card c : tgtCards) {
Card rem = c.manifest(p, sa);
for(Card c : tgtCards) {
Card rem = c.manifest(p, sa, moveParams);
if (sa.hasParam("RememberManifested") && rem != null && rem.isManifested()) {
source.addRemembered(rem);
}

View File

@@ -1,10 +1,15 @@
package forge.game.ability.effects;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -27,7 +32,14 @@ public class PermanentEffect extends SpellAbilityEffect {
host.setController(sa.getActivatingPlayer(), 0);
final Card c = game.getAction().moveToPlay(host, sa);
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
final Card c = game.getAction().moveToPlay(host, host.getController(), sa, moveParams);
sa.setHostCard(c);
// some extra for Dashing

View File

@@ -9,6 +9,7 @@ import org.apache.commons.lang3.mutable.MutableBoolean;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
@@ -21,6 +22,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactory;
import forge.game.card.CardUtil;
import forge.game.card.CardZoneTable;
@@ -104,6 +106,14 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & ")));
}
List<Card> allTokens = Lists.newArrayList();
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
for (final Table.Cell<Player, Card, Integer> c : tokenTable.cellSet()) {
Card prototype = c.getColumnKey();
Player creator = c.getRowKey();
@@ -153,7 +163,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
}
// Should this be catching the Card that's returned?
Card moved = game.getAction().moveToPlay(tok, sa);
Card moved = game.getAction().moveToPlay(tok, sa, moveParams);
if (moved == null || moved.getZone() == null) {
// in case token can't enter the battlefield, it isn't created
triggerList.put(ZoneType.None, ZoneType.None, moved);

View File

@@ -652,7 +652,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false;
}
public Card manifest(Player p, SpellAbility sa) {
public Card manifest(Player p, SpellAbility sa, Map<AbilityKey, Object> params) {
// Turn Face Down (even if it's DFC).
// Sometimes cards are manifested while already being face down
if (!turnFaceDown(true) && !isFaceDown()) {
@@ -667,7 +667,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// Mark this card as "manifested"
setManifested(true);
Card c = game.getAction().moveToPlay(this, p, sa);
Card c = game.getAction().moveToPlay(this, p, sa, params);
if (c.isInPlay()) {
c.setManifested(true);
c.turnFaceDown(true);
@@ -3454,7 +3454,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
public final void attachToEntity(final GameEntity entity) {
if (!entity.canBeAttached(this)) {
attachToEntity(entity, false);
}
public final void attachToEntity(final GameEntity entity, boolean overwrite) {
if (!overwrite && !entity.canBeAttached(this)) {
return;
}

View File

@@ -47,6 +47,7 @@ import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.cost.Cost;
@@ -110,7 +111,16 @@ public class CardFactoryUtil {
if (!hostCard.isFaceDown()) {
hostCard.setOriginalStateAsFaceDown();
}
hostCard.getGame().getAction().moveToPlay(hostCard, this);
final Game game = hostCard.getGame();
CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield();
CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard();
Map<AbilityKey, Object> moveParams = Maps.newEnumMap(AbilityKey.class);
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
hostCard.getGame().getAction().moveToPlay(hostCard, this, moveParams);
}
@Override

View File

@@ -913,9 +913,9 @@ public class CardProperty {
}
} else {
final String restriction = property.split("sharesControllerWith ")[1];
if (restriction.startsWith("Remembered") || restriction.startsWith("Imprinted")) {
CardCollection list = AbilityUtils.getDefinedCards(source, restriction, spellAbility);
return !CardLists.filter(list, CardPredicates.sharesControllerWith(card)).isEmpty();
if (!Iterables.any(list, CardPredicates.sharesControllerWith(card))) {
return false;
}
}
} else if (property.startsWith("sharesOwnerWith")) {

View File

@@ -121,6 +121,15 @@ public final class PlayerPredicates {
};
}
public static final Predicate<Player> canBeAttached(final Card aura) {
return new Predicate<Player>() {
@Override
public boolean apply(final Player p) {
return p.canBeAttached(aura);
}
};
}
public static final Comparator<Player> compareByZoneSize(final ZoneType zone) {
return new Comparator<Player>() {
@Override

View File

@@ -96,8 +96,7 @@ public class ReplaceMoved extends ReplacementEffect {
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Card, runParams.get(AbilityKey.Affected));
sa.setReplacingObject(AbilityKey.CardLKI, runParams.get(AbilityKey.CardLKI));
sa.setReplacingObject(AbilityKey.Cause, runParams.get(AbilityKey.Cause));
sa.setReplacingObjectsFrom(runParams, AbilityKey.CardLKI, AbilityKey.Cause, AbilityKey.LastStateBattlefield, AbilityKey.LastStateGraveyard);
}
}

View File

@@ -754,6 +754,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setReplacingObject(final AbilityKey type, final Object o) {
replacingObjects.put(type, o);
}
public void setReplacingObjectsFrom(final Map<AbilityKey, Object> repParams, final AbilityKey... types) {
int typesLength = types.length;
for (int i = 0; i < typesLength; i += 1) {
AbilityKey type = types[i];
if (repParams.containsKey(type)) {
setReplacingObject(type, repParams.get(type));
}
}
}
public void resetOnceResolved() {
//resetPaidHash(); // FIXME: if uncommented, breaks Dragon Presence, e.g. Orator of Ojutai + revealing a Dragon from hand.

View File

@@ -472,21 +472,27 @@ public class TargetRestrictions {
*
* @param sa
* the sa
* @param isTargeted
* Check Valid Candidates and Targeting
* @return a boolean.
*/
public final boolean hasCandidates(final SpellAbility sa, final boolean isTargeted) {
final Game game = sa.getHostCard().getGame();
for (Player player : game.getPlayers()) {
if (sa.canTarget(player)) {
return true;
}
}
public final boolean hasCandidates(final SpellAbility sa) {
final Card srcCard = sa.getHostCard(); // should there be OrginalHost at any moment?
final Game game = srcCard.getGame();
this.applyTargetTextChanges(sa);
final Card srcCard = sa.getHostCard(); // should there be OrginalHost at any moment?
for (Player player : game.getPlayers()) {
if (!player.isValid(this.validTgts, sa.getActivatingPlayer(), srcCard, sa)) {
continue;
}
if (!sa.canTarget(player)) {
continue;
}
if (sa.getTargets().contains(player)) {
continue;
}
return true;
}
if (this.tgtZone.contains(ZoneType.Stack)) {
// Stack Zone targets are considered later
return true;
@@ -495,7 +501,7 @@ public class TargetRestrictions {
if (!c.isValid(this.validTgts, sa.getActivatingPlayer(), srcCard, sa)) {
continue;
}
if (isTargeted && !sa.canTarget(c)) {
if (!sa.canTarget(c)) {
continue;
}
if (sa.getTargets().contains(c)) {

View File

@@ -1,9 +1,7 @@
Name:Aura Graft
ManaCost:1 U
Types:Instant
A:SP$ GainControl | Cost$ 1 U | ValidTgts$ Aura.AttachedTo Permanent | TgtPrompt$ Select target Aura attached to a permanent | SubAbility$ ChooseNewHost | SpellDescription$ Gain control of target Aura that's attached to a permanent. Attach it to another permanent it can enchant.
SVar:ChooseNewHost:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Permanent.CanBeEnchantedByTargeted+NotEnchantedByTargeted | ChoiceZone$ Battlefield | SubAbility$ ReEnchant | RememberChosen$ True | AILogic$ AtLeast1
SVar:ReEnchant:DB$ Attach | Object$ ParentTarget | Defined$ Remembered
A:SP$ GainControl | Cost$ 1 U | ValidTgts$ Aura.AttachedTo Permanent | TgtPrompt$ Select target Aura attached to a permanent | SubAbility$ ReEnchant | StackDescription$ SpellDescription | SpellDescription$ Gain control of target Aura that's attached to a permanent. Attach it to another permanent it can enchant.
SVar:ReEnchant:DB$ Attach | Object$ ParentTarget | Move$ True | Choices$ Permanent | StackDescription$ None
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/aura_graft.jpg
Oracle:Gain control of target Aura that's attached to a permanent. Attach it to another permanent it can enchant.

View File

@@ -1,10 +1,6 @@
Name:Crown of the Ages
ManaCost:2
Types:Artifact
A:AB$ Pump | Cost$ 4 T | Amount$ 1 | ValidTgts$ Aura.AttachedTo Creature | TgtPrompt$ Select target Aura attached to a creature| SubAbility$ ChooseNewHost | StackDescription$ None | SpellDescription$ Attach target Aura attached to a creature to another creature.
SVar:ChooseNewHost:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Creature.NotEnchantedByTargeted | ChoiceZone$ Battlefield | SubAbility$ CrownAttach | RememberChosen$ True | AILogic$ AtLeast1
SVar:CrownAttach:DB$ Attach | Object$ ParentTarget | Defined$ Remembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ Attach | Cost$ 4 T | ValidTgts$ Aura.AttachedTo Creature | TgtPrompt$ Select target Aura attached to a creature | Object$ Targeted | Choices$ Creature | Move$ True | StackDescription$ SpellDescription | SpellDescription$ Attach target Aura attached to a creature to another creature.
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/crown_of_the_ages.jpg
Oracle:{4}, {T}: Attach target Aura attached to a creature to another creature.

View File

@@ -1,10 +1,5 @@
Name:Enchantment Alteration
ManaCost:U
Types:Instant
A:SP$ Pump | Cost$ U | ValidTgts$ Aura.AttachedTo Creature,Aura.AttachedTo Land | TgtPrompt$ Select target Aura attached to a creature or land | SubAbility$ DBRem | StackDescription$ None | SpellDescription$ Attach target Aura attached to a creature or land to another permanent of that type.
SVar:DBRem:DB$ PumpAll | ValidCards$ Land.EnchantedBy Targeted,Creature.EnchantedBy Targeted | RememberAllPumped$ True | SubAbility$ ChooseNewHost
SVar:ChooseNewHost:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Permanent.CanBeEnchantedByTargeted+NotEnchantedByTargeted+sharesCardTypeWith Remembered | ChoiceZone$ Battlefield | SubAbility$ AlterationAttach | AILogic$ AtLeast1
SVar:AlterationAttach:DB$ Attach | Object$ ParentTarget | Defined$ ChosenCard | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/enchantment_alteration.jpg
A:SP$ Attach | Cost$ U | ValidTgts$ Aura.AttachedTo Creature,Aura.AttachedTo Land | TgtPrompt$ Select target Aura attached to a creature or land | Object$ Targeted | Choices$ Permanent.sharesCardTypeWith AttachedBy Targeted | Move$ True | AILogic$ MoveTgtAura | StackDescription$ SpellDescription | SpellDescription$ Attach target Aura attached to a creature or land to another permanent of that type.
Oracle:Attach target Aura attached to a creature or land to another permanent of that type.

View File

@@ -1,13 +1,7 @@
Name:Fumble
ManaCost:1 U
Types:Instant
A:SP$ Pump | Cost$ 1 U | IsCurse$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBRem | StackDescription$ SpellDescription | SpellDescription$ Return target creature to its owner's hand. Gain control of all Auras and Equipment that were attached to it, then attach them to another creature.
SVar:DBRem:DB$ PumpAll | ValidCards$ Aura.AttachedTo Targeted,Equipment.AttachedTo Targeted | RememberAllPumped$ True | SubAbility$ DBBounce
SVar:DBBounce:DB$ ChangeZone | Defined$ Targeted | Origin$ Battlefield | Destination$ Hand | SubAbility$ ChooseNewHost
SVar:ChooseNewHost:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Creature.CanBeEnchantedByAllRemembered | ChoiceZone$ Battlefield | SubAbility$ GainControl
SVar:GainControl:DB$ GainControl | AllValid$ Equipment.IsRemembered,Enchantment.IsRemembered | NewController$ You | ConditionDefined$ ChosenCard | ConditionPresent$ Card | ConditionCompare$ EQ1 | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Object$ Remembered | Defined$ ChosenCard | SubAbility$ CleanUpEnchantments
SVar:CleanUpEnchantments:DB$ ChangeZone | Origin$ All | Destination$ Graveyard | ChangeType$ Enchantment.IsRemembered | ConditionDefined$ ChosenCard | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ ControlEquipment
SVar:ControlEquipment:DB$ GainControl | AllValid$ Equipment.IsRemembered | NewController$ You | ConditionDefined$ ChosenCard | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanUp
SVar:DBCleanUp:DB$ Cleanup | ClearRemembered$ True
A:SP$ ChangeZone | Cost$ 1 U | IsCurse$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Hand | SubAbility$ GainControl | StackDescription$ SpellDescription | SpellDescription$ Return target creature to its owner's hand. Gain control of all Auras and Equipment that were attached to it, then attach them to another creature.
SVar:GainControl:DB$ GainControl | Defined$ AttachedTo Targeted.Aura,Enchantment | NewController$ You | SubAbility$ DBAttach | StackDescription$ None
SVar:DBAttach:DB$ Attach | Object$ AttachedTo Targeted.Aura,Enchantment | Choices$ Creature | StackDescription$ None
Oracle:Return target creature to its owner's hand. Gain control of all Auras and Equipment that were attached to it, then attach them to another creature.

View File

@@ -5,9 +5,7 @@ K:Enchant creature
A:SP$ Attach | Cost$ 4 B | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Deathtouch & Indestructible | Description$ Enchanted creature has deathtouch and indestructible.
K:Morph:Sac<1/Creature.Other/another creature>
R:Event$ TurnFaceUp | ValidCard$ Card.Self | ReplaceWith$ DBChoose | ActiveZones$ Battlefield | Description$ As CARDNAME is turned face up, you may attach it to a creature.
SVar:DBChoose:DB$ ChooseCard | Choices$ Creature | ChoiceTitle$ Choose a creature | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ ChosenCard | Object$ Self | Optional$ True | AILogic$ Pump | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
R:Event$ TurnFaceUp | ValidCard$ Card.Self | ReplaceWith$ DBAttach | ActiveZones$ Battlefield | Description$ As CARDNAME is turned face up, you may attach it to a creature.
SVar:DBAttach:DB$ Attach | Choices$ Creature | ChoiceTitle$ Choose a creature | Object$ Self | Optional$ True | AILogic$ Pump
AI:RemoveDeck:All
Oracle:Enchant creature\nEnchanted creature has deathtouch and indestructible.\nMorph—Sacrifice another creature. (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)\nAs Gift of Doom is turned face up, you may attach it to a creature.

View File

@@ -4,12 +4,6 @@ Types:Creature Faerie Wizard
PT:2/4
K:Flash
K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRem | TriggerDescription$ When CARDNAME enters the battlefield, attach all Auras enchanting target permanent to another permanent with the same controller.
SVar:TrigRem:DB$ Pump | ValidTgts$ Permanent | TgtPrompt$ Select target permanent to remove auras | ImprintCards$ Targeted | RememberObjects$ Valid Aura.AttachedTo Targeted | SubAbility$ DBNewHost
SVar:DBNewHost:DB$ ChooseCard | Choices$ Permanent.IsNotImprinted+sharesControllerWith Imprinted+CanBeEnchantedByAllRemembered | SubAbility$ ClearImprint
SVar:ClearImprint:DB$ Cleanup | ClearImprinted$ True | SubAbility$ DBMove
SVar:DBMove:DB$ RepeatEach | RepeatCards$ Aura.IsRemembered | RepeatSubAbility$ DBAuraAttach | UseImprinted$ True | SubAbility$ DBCleanup
SVar:DBAuraAttach:DB$ Attach | Object$ Imprinted | Defined$ ChosenCard
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/glamer_spinners.jpg
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAuraAttach | TriggerDescription$ When CARDNAME enters the battlefield, attach all Auras enchanting target permanent to another permanent with the same controller.
SVar:TrigAuraAttach:DB$ Attach | ValidTgts$ Permanent | TgtPrompt$ Select target permanent to remove auras | Object$ Valid Aura.AttachedTo Targeted | Choices$ Permanent.sharesControllerWith Targeted | Move$ True | AILogic$ Unenchanted
Oracle:Flash\nFlying\nWhen Glamer Spinners enters the battlefield, attach all Auras enchanting target permanent to another permanent with the same controller.

View File

@@ -3,9 +3,7 @@ ManaCost:3
Types:Artifact Equipment
K:Equip:1
K:Flash
K:ETBReplacement:Other:ChooseC
SVar:ChooseC:DB$ ChooseCard | Defined$ You | Choices$ Creature.YouCtrl+CanBeAttachedBy | Amount$ 1 | Mandatory$ True | SubAbility$ DBAttach | SpellDescription$ As CARDNAME enters the battlefield, choose a creature you control it could be attached to. If you do, it enters the battlefield attached to that creature.
SVar:DBAttach:DB$ Attach | Object$ Self | Defined$ ChosenCard
K:ETBReplacement:Other:DBAttach
SVar:DBAttach:DB$ Attach | Object$ Self | Choices$ Creature.YouCtrl | SpellDescription$ As CARDNAME enters the battlefield, choose a creature you control it could be attached to. If you do, it enters the battlefield attached to that creature.
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1.
SVar:Picture:http://www.wizards.com/global/images/magic/general/grifters_blade.jpg
Oracle:Flash\nAs Grifter's Blade enters the battlefield, choose a creature you control it could be attached to. If you do, it enters the battlefield attached to that creature.\nEquipped creature gets +1/+1.\nEquip {1}

View File

@@ -7,7 +7,6 @@ SVar:TrigFlip:DB$ SetState | Defined$ Self | Mode$ Flip
AI:RemoveDeck:Random
DeckNeeds:Type$Aura
SVar:EnchantMe:Multiple
SVar:Picture:http://www.wizards.com/global/images/magic/general/kitsune_mystic.jpg
AlternateMode:Flip
Oracle:At the beginning of the end step, if Kitsune Mystic is enchanted by two or more Auras, flip it.
@@ -17,10 +16,6 @@ Name:Autumn-Tail, Kitsune Sage
ManaCost:3 W
Types:Legendary Creature Fox Wizard
PT:4/5
A:AB$ Pump | Cost$ 1 | Amount$ 1 | ValidTgts$ Aura.AttachedTo Creature | TgtPrompt$ Select target Aura attached to a creature | SubAbility$ ChooseNewHost | StackDescription$ None | SpellDescription$ Attach target Aura attached to a creature to another creature.
SVar:ChooseNewHost:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Creature.NotEnchantedByTargeted | ChoiceZone$ Battlefield | SubAbility$ KitsuneAttach | RememberChosen$ True | AILogic$ AtLeast1
SVar:KitsuneAttach:DB$ Attach | Object$ ParentTarget | Defined$ Remembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ Attach | Cost$ 1 | ValidTgts$ Aura.AttachedTo Creature | TgtPrompt$ Select target Aura attached to a creature | Object$ Targeted | Choices$ Creature | Move$ True | StackDescription$ SpellDescription | SpellDescription$ Attach target Aura attached to a creature to another creature.
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/autumn_tail_kitsune_sage.jpg
Oracle:{1}: Attach target Aura attached to a creature to another creature.

View File

@@ -4,9 +4,7 @@ Types:Enchantment Aura
K:Enchant land
A:SP$ Attach | Cost$ 1 G G | ValidTgts$ Land | AILogic$ Curse
T:Mode$ Taps | ValidCard$ Card.AttachedBy | Execute$ TrigDestroy | TriggerDescription$ When enchanted land becomes tapped, destroy it. That land's controller may attach CARDNAME to a land of their choice.
SVar:TrigDestroy:DB$ Destroy | Defined$ TriggeredCardLKICopy | SubAbility$ DBChoose
SVar:DBChoose:DB$ ChooseCard | Defined$ TriggeredCardController | Choices$ Land | AILogic$ OppPreferred | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ ChosenCard
SVar:TrigDestroy:DB$ Destroy | Defined$ TriggeredCardLKICopy | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Object$ Self | Chooser$ TriggeredCardController | Choices$ Land | AILogic$ Curse
SVar:NonStackingAttachEffect:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/kudzu.jpg
Oracle:Enchant land\nWhen enchanted land becomes tapped, destroy it. That land's controller may attach Kudzu to a land of their choice.

View File

@@ -3,10 +3,9 @@ ManaCost:3 W W
Types:Legendary Planeswalker Nahiri
Loyalty:3
Text:CARDNAME can be your commander.
A:AB$ Token | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ w_1_1_kor_soldier | TokenOwner$ You | LegacyImage$ w 1 1 kor soldier c14 | RememberTokens$ True | SubAbility$ DBChooseToken | SpellDescription$ Create a 1/1 white Kor Soldier creature token. You may attach an Equipment you control to it.
SVar:DBChooseToken:DB$ ChooseCard | DefinedCards$ Remembered | Mandatory$ True | ChoiceTitle$ Choose a token | SubAbility$ DBAttach | StackDescription$ None
SVar:DBAttach:DB$ Attach | Optional$ True | Choices$ Equipment.YouCtrl | ChoiceTitle$ Choose an Equipment you control | Defined$ ChosenCard | SubAbility$ DBCleanup | StackDescription$ None
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True
A:AB$ Token | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ w_1_1_kor_soldier | TokenOwner$ You | LegacyImage$ w 1 1 kor soldier c14 | RememberTokens$ True | SubAbility$ DBAttach | SpellDescription$ Create a 1/1 white Kor Soldier creature token. You may attach an Equipment you control to it.
SVar:DBAttach:DB$ Attach | Optional$ True | Choices$ Equipment.YouCtrl | ChoiceTitle$ Choose an Equipment you control | Defined$ Remembered | SubAbility$ DBCleanup | StackDescription$ None
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ ChangeZone | Cost$ SubCounter<2/LOYALTY> | Origin$ Hand,Graveyard | Destination$ Battlefield | Hidden$ True | Planeswalker$ True | ChangeType$ Equipment.YouCtrl | Optional$ True | SpellDescription$ You may put an Equipment card from your hand or graveyard onto the battlefield.
A:AB$ Token | Cost$ SubCounter<10/LOYALTY> | Planeswalker$ True | Ultimate$ True | TokenAmount$ 1 | TokenScript$ stoneforged_blade | LegacyImage$ stoneforged blade c14 | TokenOwner$ You | SpellDescription$ Create a colorless Equipment artifact token named Stoneforged Blade. It has indestructible, "Equipped creature gets +5/+5 and has double strike," and equip {0}.
DeckHas:Ability$Token

View File

@@ -4,10 +4,8 @@ Types:Enchantment Aura
K:Enchant creature or land
A:SP$ Attach | Cost$ 4 B B | ValidTgts$ Creature,Land | AILogic$ Curse
S:Mode$ Continuous | Affected$ Card.AttachedBy | AddTrigger$ NettlevineTrig | Description$ Enchanted permanent has "At the beginning of your end step, sacrifice this permanent and attach CARDNAME to a creature or land you control."
SVar:NettlevineTrig:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ NettlevineSac | TriggerDescription$ At the beginning of your end step, sacrifice CARDNAME and attach ORIGINALHOST to a creature or land you control.
SVar:NettlevineSac:DB$ Sacrifice | Defined$ Self | SubAbility$ NettlevineChoose
SVar:NettlevineChoose:DB$ ChooseCard | Defined$ You | Choices$ Creature.YouCtrl,Land.YouCtrl | AILogic$ WorstCard | Mandatory$ True | SubAbility$ NettlevineAttach
SVar:NettlevineAttach:DB$ Attach | Defined$ ChosenCard | Object$ OriginalHost | SubAbility$ NettlevineCleanup
SVar:NettlevineCleanup:DB$ Pump | ClearChosenCard$ True
SVar:NettlevineTrig:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ NettlevineSac | TriggerDescription$ At the beginning of your end step, sacrifice CARDNAME and ORIGINALHOST to a creature or land you control.
SVar:NettlevineSac:DB$ Sacrifice | Defined$ Self | SubAbility$ NettlevineAttach
SVar:NettlevineAttach:DB$ Attach | Object$ OriginalHost | Choices$ Creature.YouCtrl,Land.YouCtrl
SVar:NonStackingAttachEffect:True
Oracle:Enchant creature or land\nEnchanted permanent has "At the beginning of your end step, sacrifice this permanent and attach Nettlevine Blight to a creature or land you control."

View File

@@ -2,6 +2,5 @@ Name:Nomad Mythmaker
ManaCost:2 W
Types:Creature Human Nomad Cleric
PT:2/2
A:AB$ ChangeZone | Cost$ W T | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Aura | GainControl$ True | AttachedTo$ Creature.CanBeEnchantedBySource+YouCtrl | SpellDescription$ Put target Aura card from a graveyard onto the battlefield under your control attached to a creature you control.
SVar:Picture:http://www.wizards.com/global/images/magic/general/nomad_mythmaker.jpg
A:AB$ ChangeZone | Cost$ W T | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Aura | GainControl$ True | AttachedTo$ Creature.YouCtrl | AILogic$ Pump | SpellDescription$ Put target Aura card from a graveyard onto the battlefield under your control attached to a creature you control.
Oracle:{W}, {T}: Put target Aura card from a graveyard onto the battlefield under your control attached to a creature you control.

View File

@@ -2,11 +2,10 @@ Name:Quest for the Holy Relic
ManaCost:W
Types:Enchantment
T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a creature spell, you may put a quest counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ QUEST | CounterNum$ 1 | SpellDescription$ Whenever you cast a creature spell, you may put a quest counter on CARDNAME.
A:AB$ ChangeZone | Cost$ SubCounter<5/QUEST> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Card.Equipment+YouOwn | ChangeNum$ 1 | AttachedTo$ Creature.YouCtrl | SpellDescription$ Search your library for an Equipment card, put it onto the battlefield, attach it to a creature you control, then shuffle.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ QUEST | CounterNum$ 1
A:AB$ ChangeZone | Cost$ SubCounter<5/QUEST> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Card.Equipment+YouOwn | ChangeNum$ 1 | AttachAfter$ Creature.YouCtrl | SpellDescription$ Search your library for an Equipment card, put it onto the battlefield, and attach it to a creature you control. Then shuffle your library.
AI:RemoveDeck:Random
DeckNeeds:Type$Equipment
DeckHas:Ability$Counters
SVar:MaxQuestEffect:5
SVar:Picture:http://www.wizards.com/global/images/magic/general/quest_for_the_holy_relic.jpg
Oracle:Whenever you cast a creature spell, you may put a quest counter on Quest for the Holy Relic.\nRemove five quest counters from Quest for the Holy Relic and sacrifice it: Search your library for an Equipment card, put it onto the battlefield, attach it to a creature you control, then shuffle.

View File

@@ -4,10 +4,9 @@ Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ 3 G | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Enchanted creature gets +2/+2.
T:Mode$ ChangesZone | ValidCard$ Creature.EnchantedBy | Origin$ Battlefield | Destination$ Graveyard | OptionalDecider$ You | Execute$ DBReturnChoose | TriggerDescription$ When enchanted creature dies, you may return CARDNAME from your graveyard to the battlefield attached to a creature that shares a creature type with that creature.
SVar:DBReturnChoose:DB$ ChooseCard | Choices$ Creature.CanBeEnchantedBy+sharesCreatureTypeWith TriggeredCardLKICopy | ChoiceTitle$ Choose a creature shares a creature type with the former enchanted creature | SubAbility$ DBReturn
SVar:DBReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | AttachedTo$ ChosenCard | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
T:Mode$ ChangesZone | ValidCard$ Creature.EnchantedBy | Origin$ Battlefield | Destination$ Graveyard | OptionalDecider$ You | Execute$ DBReturn | TriggerDescription$ When enchanted creature dies, you may return CARDNAME from your graveyard to the battlefield attached to a creature that shares a creature type with that creature.
#TODO CorrectedSelf might not be 100% correct, but there seems no other way
SVar:DBReturn:DB$ ChangeZone | Defined$ CorrectedSelf | Origin$ Graveyard | Destination$ Battlefield | AttachedTo$ Creature.sharesCreatureTypeWith TriggeredCardLKICopy | AILogic$ Pump
AI:RemoveDeck:All
AI:RemoveDeck:Random
Oracle:Enchant creature\nEnchanted creature gets +2/+2.\nWhen enchanted creature dies, you may return Reins of the Vinesteed from your graveyard to the battlefield attached to a creature that shares a creature type with that creature.

View File

@@ -1,9 +1,8 @@
Name:Retether
ManaCost:3 W
Types:Sorcery
A:SP$ RepeatEach | Cost$ 3 W | RepeatCards$ Aura.YouOwn | Zone$ Graveyard | RepeatSubAbility$ DBAttach | SpellDescription$ Return each Aura card from your graveyard to the battlefield. Only creatures can be enchanted this way. (Aura cards that can't enchant a creature on the battlefield remain in your graveyard.)
SVar:DBAttach:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Remembered | AttachedTo$ Creature.CanBeEnchantedByTargeted
A:SP$ ChangeZone | Cost$ 3 W | Origin$ Graveyard | Destination$ Battlefield | Defined$ ValidGraveyard Aura.YouOwn | AttachedTo$ Creature | AILogic$ Pump | SpellDescription$ Return each Aura card from your graveyard to the battlefield. Only creatures can be enchanted this way. (Aura cards that can't enchant a creature on the battlefield remain in your graveyard.)
AI:RemoveDeck:Random
DeckNeeds:Type$Aura
SVar:Picture:http://www.wizards.com/global/images/magic/general/retether.jpg
SVar:NeedsToPlay:Creature.YouCtrl
Oracle:Return each Aura card from your graveyard to the battlefield. Only creatures can be enchanted this way. (Aura cards that can't enchant a creature on the battlefield remain in your graveyard.)

View File

@@ -3,10 +3,6 @@ ManaCost:GU GU
Types:Creature Elf Wizard
PT:2/2
A:AB$ MoveCounter | Cost$ 1 G | ValidTgts$ Creature | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Select target creatures to move +1/+1 counters | TargetsWithSameController$ True | CounterType$ P1P1 | CounterNum$ 1 | StackDescription$ SpellDescription | SpellDescription$ Move a +1/+1 counter from target creature onto another target creature with the same controller.
A:AB$ Pump | Cost$ 1 U | ValidTgts$ Aura.AttachedTo Permanent | TgtPrompt$ Select target aura to move | RememberObjects$ Valid Permanent.EnchantedBy Targeted | SubAbility$ ChooseNewHost | StackDescription$ None | SpellDescription$ Attach target Aura attached to a permanent to another permanent with the same controller.
SVar:ChooseNewHost:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Permanent.NotEnchantedByTargeted+sharesControllerWith Remembered+CanBeEnchantedByTargeted | ChoiceZone$ Battlefield | SubAbility$ DBAttach | AILogic$ AtLeast1
SVar:DBAttach:DB$ Attach | Object$ ParentTarget | Defined$ ChosenCard | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ Attach | Cost$ 1 U | ValidTgts$ Aura.AttachedTo Permanent | TgtPrompt$ Select target aura to move | Object$ Targeted | Choices$ Permanent.sharesControllerWith AttachedBy Targeted | Move$ True | StackDescription$ SpellDescription | SpellDescription$ Attach target Aura attached to a permanent to another permanent with the same controller.
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/simic_guildmage.jpg
Oracle:({G/U} can be paid with either {G} or {U}.)\n{1}{G}: Move a +1/+1 counter from target creature onto another target creature with the same controller.\n{1}{U}: Attach target Aura attached to a permanent to another permanent with the same controller.

View File

@@ -5,12 +5,10 @@ K:Enchant instant card in a graveyard
A:SP$ Attach | Cost$ 3 U U | ValidTgts$ Instant | TgtZone$ Graveyard | TgtPrompt$ Select target instant card in a graveyard | AILogic$ Pump
T:Mode$ SpellCast | ValidCard$ Sorcery | ValidActivatingPlayer$ You | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a sorcery spell, copy the enchanted instant card. You may cast the copy without paying its mana cost. If you do, exile the enchanted card and attach CARDNAME to another instant card in a graveyard.
SVar:TrigCopy:DB$ Play | Defined$ Enchanted | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | CopyCard$ True | RememberPlayed$ True | SubAbility$ DBExile
SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ Enchanted | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SubAbility$ DBChooseCard
SVar:DBChooseCard:DB$ ChooseCard | Choices$ Instant | ChoiceZone$ Graveyard | Amount$ 1 | Mandatory$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ ChosenCard | Object$ Self | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ Enchanted | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Choices$ Instant | ChoiceZone$ Graveyard | Object$ Self | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$Amount
AI:RemoveDeck:All
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/spellweaver_volute.jpg
Oracle:Enchant instant card in a graveyard\nWhenever you cast a sorcery spell, copy the enchanted instant card. You may cast the copy without paying its mana cost. If you do, exile the enchanted card and attach Spellweaver Volute to another instant card in a graveyard.

View File

@@ -5,9 +5,7 @@ K:Enchant land
A:SP$ Attach | Cost$ 1 R R | ValidTgts$ Land | AILogic$ Curse
T:Mode$ Taps | ValidCard$ Card.AttachedBy | Execute$ TrigDestroy | TriggerDescription$ When enchanted land becomes tapped, destroy it and CARDNAME deals 1 damage to that land's controller. That player attaches CARDNAME to a land of their choice.
SVar:TrigDestroy:DB$ Destroy | Defined$ TriggeredCardLKICopy | SubAbility$ DBDmg
SVar:DBDmg:DB$ DealDamage | Defined$ TriggeredCardController | NumDmg$ 1 | SubAbility$ DBChoose
SVar:DBChoose:DB$ ChooseCard | Defined$ TriggeredCardController | Choices$ Land | AILogic$ OppPreferred | Mandatory$ True | Amount$ 1 | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Object$ Self | Defined$ ChosenCard
SVar:DBDmg:DB$ DealDamage | Defined$ TriggeredCardController | NumDmg$ 1 | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Object$ Self | Chooser$ TriggeredCardController | Choices$ Land | AILogic$ Curse
SVar:NonStackingAttachEffect:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/steam_vines.jpg
Oracle:Enchant land\nWhen enchanted land becomes tapped, destroy it and Steam Vines deals 1 damage to that land's controller. That player attaches Steam Vines to a land of their choice.

View File

@@ -3,10 +3,6 @@ ManaCost:3 W W
Types:Creature Giant Warrior
PT:4/4
K:Vigilance
A:AB$ ChangeZone | Cost$ 1 W T | Origin$ Library | Destination$ Battlefield | ChangeType$ Equipment | ChangeNum$ 1 | Imprint$ True | SubAbility$ DBChoose | SpellDescription$ Search your library for an Equipment card, put it onto the battlefield, attach it to a creature you control, then shuffle.
SVar:DBChoose:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Creature.YouCtrl | ChoiceTitle$ Choose a creature | SubAbility$ DBAttach | RememberChosen$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1
SVar:DBAttach:DB$ Attach | Object$ Imprinted | Defined$ Remembered | SubAbility$ DBCleanup | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
SVar:X:Imprinted$Amount
SVar:Picture:http://www.wizards.com/global/images/magic/general/stonehewer_giant.jpg
A:AB$ ChangeZone | Cost$ 1 W T | Origin$ Library | Destination$ Battlefield | ChangeType$ Card.Equipment+YouOwn | ChangeNum$ 1 | AttachAfter$ Creature.YouCtrl | SpellDescription$ Search your library for an Equipment card, put it onto the battlefield, attach it to a creature you control, then shuffle.
DeckNeeds:Type$Equipment
Oracle:Vigilance\n{1}{W}, {T}: Search your library for an Equipment card, put it onto the battlefield, attach it to a creature you control, then shuffle.

View File

@@ -4,7 +4,7 @@ Types:Creature Human Shaman
PT:3/2
K:Haste
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ GraveAuras | TriggerDescription$ When CARDNAME enters the battlefield, return any number of Aura cards from your graveyard to the battlefield attached to creatures you control. Exile those Auras at the beginning of your next end step. If those Auras would leave the battlefield, exile them instead of putting them anywhere else.
SVar:GraveAuras:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Aura.YouOwn | RememberChanged$ True | AttachedTo$ Creature.YouCtrl | ChangeNum$ GraveX | Optional$ True | Hidden$ True | SubAbility$ DBUnearthed
SVar:GraveAuras:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Aura.YouOwn | RememberChanged$ True | AttachedTo$ Creature.YouCtrl | ChangeNum$ GraveX | Optional$ True | Hidden$ True | AILogic$ Pump | SubAbility$ DBUnearthed
SVar:DBUnearthed:DB$ Animate | Defined$ Remembered | LeaveBattlefield$ Exile | Duration$ Permanent | SubAbility$ DelayedExile | StackDescription$ If those Auras would leave the battlefield, exile them instead of putting them anywhere else.
SVar:DelayedExile:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigReturn | RememberObjects$ RememberedLKI | TriggerDescription$ Exile those Auras at the beginning of your next end step. | SubAbility$ DBCleanup
SVar:TrigReturn:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ DelayTriggerRememberedLKI

View File

@@ -4,7 +4,5 @@ Types:Creature Human Warrior
PT:2/2
K:Haste
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAttachAll | TriggerDescription$ When CARDNAME enters the battlefield, attach all Equipment on the battlefield to it. (Control of the Equipment doesn't change.)
SVar:TrigAttachAll:DB$ RepeatEach | RepeatSubAbility$ DBAttach | RepeatCards$ Equipment | SpellDescription$ attach all Equipment on the battlefield to CARDNAME.
SVar:DBAttach:DB$ Attach | Object$ Remembered | Defined$ TriggeredCard
SVar:Picture:http://www.wizards.com/global/images/magic/general/vulshok_battlemaster.jpg
SVar:TrigAttachAll:DB$ Attach | Object$ Valid Equipment | Defined$ TriggeredCard | SpellDescription$ attach all Equipment on the battlefield to CARDNAME.
Oracle:Haste\nWhen Vulshok Battlemaster enters the battlefield, attach all Equipment on the battlefield to it. (Control of the Equipment doesn't change.)

View File

@@ -44,7 +44,7 @@ public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSele
getController().getGui().setSelectables(vCards);
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
for (final GameEntity c : validChoices) {
final Zone cz = (c instanceof Card) ? ((Card) c).getZone() : null;
final Zone cz = (c instanceof Card) ? ((Card) c).getLastKnownZone() : null;
if (cz != null) {
zonesToUpdate.add(new PlayerZoneUpdate(cz.getPlayer().getView(), cz.getZoneType()));
}

View File

@@ -133,14 +133,14 @@ public final class InputSelectTargets extends InputSyncronizedBase {
getController().getGui().updateButtons(getOwner(), true, true, false);
} else if (!sa.isMinTargetChosen() || (divisionValues != null && !divisionValues.isEmpty())){
// If reached Minimum targets, enable OK button
if (mandatory && tgt.hasCandidates(sa, true)) {
if (mandatory && tgt.hasCandidates(sa)) {
// Player has to click on a target
getController().getGui().updateButtons(getOwner(), false, false, false);
} else {
getController().getGui().updateButtons(getOwner(), false, true, false);
}
} else {
if (mandatory && tgt.hasCandidates(sa, true)) {
if (mandatory && tgt.hasCandidates(sa)) {
// Player has to click on a target or ok
getController().getGui().updateButtons(getOwner(), true, false, true);
} else {

View File

@@ -2766,7 +2766,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (finalC.getRules().getType().isLand()) {
// this is needed to ensure land abilities fire
getGame().getAction().moveToHand(forgeCard, null);
getGame().getAction().moveToPlay(forgeCard, null);
getGame().getAction().moveToPlay(forgeCard, null, null);
// ensure triggered abilities fire
getGame().getTriggerHandler().runWaitingTriggers();
} else {

View File

@@ -102,7 +102,7 @@ public class TargetSelection {
return true;
}
final boolean hasCandidates = tgt.hasCandidates(this.ability, true);
final boolean hasCandidates = tgt.hasCandidates(this.ability);
if (!hasCandidates && !hasEnoughTargets) {
// Cancel ability if there aren't any valid Candidates
return false;