diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 5685f38fecb..1108357393f 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -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)) { diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 87e219d2e11..d6316bbf8fd 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -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); @@ -1272,7 +1272,7 @@ public abstract class GameState { *

* processCardsForZone. *

- * + * * @param data * an array of {@link java.lang.String} objects. * @param player @@ -1291,7 +1291,7 @@ public abstract class GameState { break; } } - + Card c; boolean hasSetCurSet = false; if (cardinfo[0].startsWith("t:")) { @@ -1338,7 +1338,7 @@ public abstract class GameState { c.setState(CardStateName.Meld, true); } else if (info.startsWith("Modal")) { c.setState(CardStateName.Modal, true); - } + } else if (info.startsWith("OnAdventure")) { String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; SpellAbility saAdventure = AbilityFactory.getAbility(abAdventure, c); diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index 07ff3f7179b..27138e21823 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index 6248b29c1e4..282e5ede05f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -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() { + @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() { + @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 list, final boolean mandatory, + public static Card attachGeneralAI(final Player ai, final SpellAbility sa, final List 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 options, boolean isOptional, Player targetedPlayer, Map params) { - return attachToCardAIPreferences(ai, sa, true); + return attachGeneralAI(ai, sa, (List)options, !isOptional, sa.getHostCard(), sa.getParam("AILogic")); } @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 5229a00e729..151495511a2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -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() { + @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 options, boolean isOptional, Player targetedPlayer, Map params) { // Called when looking for creature to attach aura or equipment - return ComputerUtilCard.getBestAI(options); + return AttachAi.attachGeneralAI(ai, sa, (List)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 options, Map 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) { diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 64947309c73..35efd836357 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -203,15 +203,23 @@ public class Game { } } - public CardCollectionView copyLastStateBattlefield() { + public CardCollectionView copyLastState(ZoneType type) { CardCollection result = new CardCollection(); Map 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; diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index be76c27fb34..45c334f82a6 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -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 params) { + return moveToPlay(c, c.getController(), cause, params); } public final Card moveToPlay(final Card c, final Player p, SpellAbility cause, Map 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 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 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 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; + } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index ca37ff15a97..e3e5dd7db83 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -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"), diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index f9e07e85add..dfed723c49d 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -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; diff --git a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java index c5f78d3e8d9..044d91035d2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java @@ -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 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 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 params = Maps.newHashMap(); + params.put("Attachments", attachments); + + attachTo = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, params); + } else { + FCollection targets = new FCollection<>(getDefinedEntitiesOrTargeted(sa, "Defined")); + if (targets.isEmpty()) { + return; + } else { + String title = Localizer.getInstance().getMessage("lblChoose"); + Map 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 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 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; - } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 396a33268ee..911144f4efb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -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 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 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,49 +202,49 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { } } - if (remember != null) { - final Card newSource = game.getCardState(source); - newSource.addRemembered(movedCard); - if (!source.isRemembered(movedCard)) { - source.addRemembered(movedCard); - } - if (c.getMeldedWith() != null) { - Card meld = game.getCardState(c.getMeldedWith(), null); - if (meld != null) { - newSource.addRemembered(meld); - if (!source.isRemembered(meld)) { - source.addRemembered(meld); - } - } - } - if (c.hasMergedCard()) { - for (final Card card : c.getMergedCards()) { - if (card == c) continue; - newSource.addRemembered(card); - if (!source.isRemembered(card)) { - source.addRemembered(card); - } - } - } - } - if (remLKI && movedCard != null) { - final Card lki = CardUtil.getLKICopy(c); - game.getCardState(source).addRemembered(lki); - if (!source.isRemembered(lki)) { - source.addRemembered(lki); - } - } - if (forget != null) { - game.getCardState(source).removeRemembered(c); - } - if (imprint != null) { - game.getCardState(source).addImprintedCard(movedCard); - } - if (destination == ZoneType.Battlefield) { - movedCard.setTimestamp(ts); - } - if (!movedCard.getZone().equals(originZone)) { + if (remember != null) { + final Card newSource = game.getCardState(source); + newSource.addRemembered(movedCard); + if (!source.isRemembered(movedCard)) { + source.addRemembered(movedCard); + } + if (c.getMeldedWith() != null) { + Card meld = game.getCardState(c.getMeldedWith(), null); + if (meld != null) { + newSource.addRemembered(meld); + if (!source.isRemembered(meld)) { + source.addRemembered(meld); + } + } + } + if (c.hasMergedCard()) { + for (final Card card : c.getMergedCards()) { + if (card == c) continue; + newSource.addRemembered(card); + if (!source.isRemembered(card)) { + source.addRemembered(card); + } + } + } + } + if (remLKI && movedCard != null) { + final Card lki = CardUtil.getLKICopy(c); + game.getCardState(source).addRemembered(lki); + if (!source.isRemembered(lki)) { + source.addRemembered(lki); + } + } + if (forget != null) { + game.getCardState(source).removeRemembered(c); + } + if (imprint != null) { + game.getCardState(source).addImprintedCard(movedCard); + } + if (destination == ZoneType.Battlefield) { + movedCard.setTimestamp(ts); + } + triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard); if (c.getMeldedWith() != null) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index a05c7614207..eba2a99f9e4 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -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 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 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 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 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 params = Maps.newHashMap(); - params.put("Attach", c); - attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params); - } - else { - attachedTo = list.get(0); - } + String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName())); + Map params = Maps.newHashMap(); + params.put("Attach", c); + 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 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); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java index 69c3b31dc81..d49750ce0c3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java @@ -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) diff --git a/forge-game/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java index 15978d35795..77241fecdac 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ETBReplacementEffect.java @@ -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 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); } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java index 3c2829fc602..951ed4a9fb8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java @@ -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 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); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java index d9a55b45d06..85276e6a297 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java @@ -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 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 diff --git a/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java index ce34f023036..36d09b67f5f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java +++ b/forge-game/src/main/java/forge/game/ability/effects/TokenEffectBase.java @@ -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 allTokens = Lists.newArrayList(); + + CardCollectionView lastStateBattlefield = game.copyLastStateBattlefield(); + CardCollectionView lastStateGraveyard = game.copyLastStateGraveyard(); + + Map moveParams = Maps.newEnumMap(AbilityKey.class); + moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); + moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); + for (final Table.Cell 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); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 5462e03083c..00b37e7cf31 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -652,7 +652,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return false; } - public Card manifest(Player p, SpellAbility sa) { + public Card manifest(Player p, SpellAbility sa, Map 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, 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, 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; } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 5bf2e08e9ae..7ad6251209f 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -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 moveParams = Maps.newEnumMap(AbilityKey.class); + moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); + moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); + + hostCard.getGame().getAction().moveToPlay(hostCard, this, moveParams); } @Override diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index aca8ad4c00a..5d80adb41c7 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -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(); + CardCollection list = AbilityUtils.getDefinedCards(source, restriction, spellAbility); + if (!Iterables.any(list, CardPredicates.sharesControllerWith(card))) { + return false; } } } else if (property.startsWith("sharesOwnerWith")) { diff --git a/forge-game/src/main/java/forge/game/player/PlayerPredicates.java b/forge-game/src/main/java/forge/game/player/PlayerPredicates.java index 8a4b37254ba..a4087c20bf2 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerPredicates.java +++ b/forge-game/src/main/java/forge/game/player/PlayerPredicates.java @@ -121,6 +121,15 @@ public final class PlayerPredicates { }; } + public static final Predicate canBeAttached(final Card aura) { + return new Predicate() { + @Override + public boolean apply(final Player p) { + return p.canBeAttached(aura); + } + }; + } + public static final Comparator compareByZoneSize(final ZoneType zone) { return new Comparator() { @Override diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java b/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java index 4d9b3bbb264..ce068800cca 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java @@ -96,8 +96,7 @@ public class ReplaceMoved extends ReplacementEffect { @Override public void setReplacingObjects(Map 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); } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 65f4463b57b..64252661ba5 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -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 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. diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index 610558ba84f..f64ccb5fc03 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -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)) { diff --git a/forge-gui/res/cardsfolder/a/aura_graft.txt b/forge-gui/res/cardsfolder/a/aura_graft.txt index 9f38dac4570..0653bae4ba7 100644 --- a/forge-gui/res/cardsfolder/a/aura_graft.txt +++ b/forge-gui/res/cardsfolder/a/aura_graft.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/c/crown_of_the_ages.txt b/forge-gui/res/cardsfolder/c/crown_of_the_ages.txt index 9bf39383b2e..01956118e89 100644 --- a/forge-gui/res/cardsfolder/c/crown_of_the_ages.txt +++ b/forge-gui/res/cardsfolder/c/crown_of_the_ages.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/e/enchantment_alteration.txt b/forge-gui/res/cardsfolder/e/enchantment_alteration.txt index a20cff26791..0b6b6d9ecbd 100644 --- a/forge-gui/res/cardsfolder/e/enchantment_alteration.txt +++ b/forge-gui/res/cardsfolder/e/enchantment_alteration.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/f/fumble.txt b/forge-gui/res/cardsfolder/f/fumble.txt index 57baed00e31..5a89a9b7703 100644 --- a/forge-gui/res/cardsfolder/f/fumble.txt +++ b/forge-gui/res/cardsfolder/f/fumble.txt @@ -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 -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. \ No newline at end of file +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. diff --git a/forge-gui/res/cardsfolder/g/gift_of_doom.txt b/forge-gui/res/cardsfolder/g/gift_of_doom.txt index d2d3db1463f..271d16a4fd8 100644 --- a/forge-gui/res/cardsfolder/g/gift_of_doom.txt +++ b/forge-gui/res/cardsfolder/g/gift_of_doom.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/g/glamer_spinners.txt b/forge-gui/res/cardsfolder/g/glamer_spinners.txt index 0593d3ff2e3..2b114f5abb1 100644 --- a/forge-gui/res/cardsfolder/g/glamer_spinners.txt +++ b/forge-gui/res/cardsfolder/g/glamer_spinners.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/g/grifters_blade.txt b/forge-gui/res/cardsfolder/g/grifters_blade.txt index a371a547003..89dd191c3f5 100644 --- a/forge-gui/res/cardsfolder/g/grifters_blade.txt +++ b/forge-gui/res/cardsfolder/g/grifters_blade.txt @@ -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} diff --git a/forge-gui/res/cardsfolder/k/kitsune_mystic_autumn_tail_kitsune_sage.txt b/forge-gui/res/cardsfolder/k/kitsune_mystic_autumn_tail_kitsune_sage.txt index 8227bb3a474..90a292a2ef8 100644 --- a/forge-gui/res/cardsfolder/k/kitsune_mystic_autumn_tail_kitsune_sage.txt +++ b/forge-gui/res/cardsfolder/k/kitsune_mystic_autumn_tail_kitsune_sage.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/k/kudzu.txt b/forge-gui/res/cardsfolder/k/kudzu.txt index f5c5f0095cc..2679f2f7d1b 100644 --- a/forge-gui/res/cardsfolder/k/kudzu.txt +++ b/forge-gui/res/cardsfolder/k/kudzu.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/n/nahiri_the_lithomancer.txt b/forge-gui/res/cardsfolder/n/nahiri_the_lithomancer.txt index 8a7c46e01f1..13bd2ba6e34 100644 --- a/forge-gui/res/cardsfolder/n/nahiri_the_lithomancer.txt +++ b/forge-gui/res/cardsfolder/n/nahiri_the_lithomancer.txt @@ -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 diff --git a/forge-gui/res/cardsfolder/n/nettlevine_blight.txt b/forge-gui/res/cardsfolder/n/nettlevine_blight.txt index 30c96911cd9..3b18fd3fd4f 100644 --- a/forge-gui/res/cardsfolder/n/nettlevine_blight.txt +++ b/forge-gui/res/cardsfolder/n/nettlevine_blight.txt @@ -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." diff --git a/forge-gui/res/cardsfolder/n/nomad_mythmaker.txt b/forge-gui/res/cardsfolder/n/nomad_mythmaker.txt index 20e8d141d24..2ab043bf3cf 100644 --- a/forge-gui/res/cardsfolder/n/nomad_mythmaker.txt +++ b/forge-gui/res/cardsfolder/n/nomad_mythmaker.txt @@ -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 -Oracle:{W}, {T}: Put target Aura card from a graveyard onto the battlefield under your control attached to a creature you control. +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. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/q/quest_for_the_holy_relic.txt b/forge-gui/res/cardsfolder/q/quest_for_the_holy_relic.txt index 353ff7bbed5..e355d066818 100644 --- a/forge-gui/res/cardsfolder/q/quest_for_the_holy_relic.txt +++ b/forge-gui/res/cardsfolder/q/quest_for_the_holy_relic.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/r/reins_of_the_vinesteed.txt b/forge-gui/res/cardsfolder/r/reins_of_the_vinesteed.txt index 0cb835bfc00..8c08090b25c 100644 --- a/forge-gui/res/cardsfolder/r/reins_of_the_vinesteed.txt +++ b/forge-gui/res/cardsfolder/r/reins_of_the_vinesteed.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/r/retether.txt b/forge-gui/res/cardsfolder/r/retether.txt index c6ad72c0d21..34cfa25ddda 100644 --- a/forge-gui/res/cardsfolder/r/retether.txt +++ b/forge-gui/res/cardsfolder/r/retether.txt @@ -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.) diff --git a/forge-gui/res/cardsfolder/s/simic_guildmage.txt b/forge-gui/res/cardsfolder/s/simic_guildmage.txt index 6fb9ae985e2..07f29e1f518 100644 --- a/forge-gui/res/cardsfolder/s/simic_guildmage.txt +++ b/forge-gui/res/cardsfolder/s/simic_guildmage.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/s/spellweaver_volute.txt b/forge-gui/res/cardsfolder/s/spellweaver_volute.txt index 5a01c8e9542..e1081dc4eb3 100644 --- a/forge-gui/res/cardsfolder/s/spellweaver_volute.txt +++ b/forge-gui/res/cardsfolder/s/spellweaver_volute.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/s/steam_vines.txt b/forge-gui/res/cardsfolder/s/steam_vines.txt index 04433e410e9..b6b0c2ebed0 100644 --- a/forge-gui/res/cardsfolder/s/steam_vines.txt +++ b/forge-gui/res/cardsfolder/s/steam_vines.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/s/stonehewer_giant.txt b/forge-gui/res/cardsfolder/s/stonehewer_giant.txt index 0c1c429637d..369a8eaa609 100644 --- a/forge-gui/res/cardsfolder/s/stonehewer_giant.txt +++ b/forge-gui/res/cardsfolder/s/stonehewer_giant.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/s/storm_herald.txt b/forge-gui/res/cardsfolder/s/storm_herald.txt index 759c719da4d..68e008dfb54 100755 --- a/forge-gui/res/cardsfolder/s/storm_herald.txt +++ b/forge-gui/res/cardsfolder/s/storm_herald.txt @@ -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 diff --git a/forge-gui/res/cardsfolder/v/vulshok_battlemaster.txt b/forge-gui/res/cardsfolder/v/vulshok_battlemaster.txt index f073e1a0ea7..efee7751d55 100644 --- a/forge-gui/res/cardsfolder/v/vulshok_battlemaster.txt +++ b/forge-gui/res/cardsfolder/v/vulshok_battlemaster.txt @@ -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 -Oracle:Haste\nWhen Vulshok Battlemaster enters the battlefield, attach all Equipment on the battlefield to it. (Control of the Equipment doesn't change.) \ No newline at end of file +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.) diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectEntitiesFromList.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectEntitiesFromList.java index 1002456e4d3..96d5984f60c 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectEntitiesFromList.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectEntitiesFromList.java @@ -44,7 +44,7 @@ public class InputSelectEntitiesFromList 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())); } diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java index 22dc0f90c4e..40b9e87ff34 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java @@ -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 { diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 198dad9d762..ed954dbe188 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -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 { diff --git a/forge-gui/src/main/java/forge/player/TargetSelection.java b/forge-gui/src/main/java/forge/player/TargetSelection.java index e7bc19651f6..5b4c9d6e592 100644 --- a/forge-gui/src/main/java/forge/player/TargetSelection.java +++ b/forge-gui/src/main/java/forge/player/TargetSelection.java @@ -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;