diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 9891b166767..8d5ea58fb29 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -1142,7 +1142,7 @@ public abstract class GameState { } else if (info.startsWith("SummonSick")) { c.setSickness(true); } else if (info.startsWith("FaceDown")) { - c.setState(CardStateName.FaceDown, true); + c.turnFaceDown(true); if (info.endsWith("Manifested")) { c.setManifested(true); } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 0d76421a264..58e76fbc9fb 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -367,8 +367,7 @@ public class PlayerControllerAi extends PlayerController { @Override public boolean apply(Card card) { // need a custom predicate here since Volrath's Shapeshifter may have a different name OTB - return card.getName().equals("Volrath's Shapeshifter") - || card.getStates().contains(CardStateName.OriginalText) && card.getState(CardStateName.OriginalText).getName().equals("Volrath's Shapeshifter"); + return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter"); } }).isEmpty()) { int bestValue = 0; diff --git a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java index 6d2618ee7f0..e4758b174eb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java @@ -12,7 +12,6 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import java.util.List; @@ -21,7 +20,6 @@ public class CloneAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); final Game game = source.getGame(); @@ -39,27 +37,13 @@ public class CloneAi extends SpellAbilityAi { // TODO - add some kind of check for during human turn to answer // "Can I use this to block something?" + if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) { + return false; + } + PhaseHandler phase = game.getPhaseHandler(); - // don't use instant speed clone abilities outside computers - // Combat_Begin step - if (!phase.is(PhaseType.COMBAT_BEGIN) - && phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa) - && !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) { - return false; - } - // don't use instant speed clone abilities outside humans - // Combat_Declare_Attackers_InstantAbility step - if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) { - return false; - } - - // don't activate during main2 unless this effect is permanent - if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) { - return false; - } - - if (null == tgt) { + if (!sa.usesTargeting()) { final List defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); boolean bFlag = false; @@ -131,7 +115,7 @@ public class CloneAi extends SpellAbilityAi { *

* cloneTgtAI. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @return a boolean. @@ -155,7 +139,7 @@ public class CloneAi extends SpellAbilityAi { // a good target return false; } - + /* (non-Javadoc) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) */ @@ -178,7 +162,7 @@ public class CloneAi extends SpellAbilityAi { /* * (non-Javadoc) - * + * * @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, * forge.game.spellability.SpellAbility, java.lang.Iterable, boolean, * forge.game.player.Player) @@ -186,9 +170,13 @@ public class CloneAi extends SpellAbilityAi { @Override protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) { + final Card host = sa.getHostCard(); final Player ctrl = host.getController(); + final Card cloneTarget = getCloneTarget(sa); + final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer()); + final boolean isVesuva = "Vesuva".equals(host.getName()); final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary" @@ -198,7 +186,8 @@ public class CloneAi extends SpellAbilityAi { if (!newOptions.isEmpty()) { options = newOptions; } - Card choice = ComputerUtilCard.getBestAI(options); + Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options); + if (isVesuva && "Vesuva".equals(choice.getName())) { choice = null; } @@ -206,4 +195,44 @@ public class CloneAi extends SpellAbilityAi { return choice; } + protected Card getCloneTarget(final SpellAbility sa) { + final Card host = sa.getHostCard(); + Card tgtCard = host; + if (sa.hasParam("CloneTarget")) { + final List cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa); + if (!cloneTargets.isEmpty()) { + tgtCard = cloneTargets.get(0); + } + } else if (sa.hasParam("Choices") && sa.usesTargeting()) { + tgtCard = sa.getTargets().getFirstTargetedCard(); + } + + return tgtCard; + } + + /* + * (non-Javadoc) + * @see forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player, forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler) + */ + protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { + // don't use instant speed clone abilities outside computers + // Combat_Begin step + if (!ph.is(PhaseType.COMBAT_BEGIN) + && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa) + && !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) { + return false; + } + + // don't use instant speed clone abilities outside humans + // Combat_Declare_Attackers_InstantAbility step + if (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.isPlayerTurn(ai) || ph.getCombat().getAttackers().isEmpty()) { + return false; + } + + // don't activate during main2 unless this effect is permanent + if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) { + return false; + } + return true; + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java index 94a878c8afe..db3c4be7014 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java @@ -4,7 +4,6 @@ import com.google.common.collect.Maps; import forge.ai.ComputerUtil; import forge.ai.ComputerUtilMana; import forge.ai.SpellAbilityAi; -import forge.card.CardStateName; import forge.game.Game; import forge.game.card.Card; import forge.game.card.CardCollectionView; @@ -104,7 +103,7 @@ public class ManifestAi extends SpellAbilityAi { // check to ensure that there are no replacement effects that prevent creatures ETBing from library // (e.g. Grafdigger's Cage) Card topCopy = CardUtil.getLKICopy(library.getFirst()); - topCopy.setState(CardStateName.FaceDown, false); + topCopy.turnFaceDownNoUpdate(); topCopy.setManifested(true); final Map repParams = Maps.newHashMap(); diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 2ab4d392c64..f90e21007f2 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -19,10 +19,7 @@ import forge.game.GameObjectMap; import forge.game.GameRules; import forge.game.Match; import forge.game.StaticEffect; -import forge.game.card.Card; -import forge.game.card.CardFactory; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CounterType; +import forge.game.card.*; import forge.game.card.token.TokenInfo; import forge.game.combat.Combat; import forge.game.keyword.KeywordInterface; @@ -267,6 +264,7 @@ public class GameCopier { System.err.println(sa.toString()); } } + return newCard; } @@ -295,6 +293,7 @@ public class GameCopier { newCard.setChangedCardTypes(c.getChangedCardTypesMap()); newCard.setChangedCardKeywords(c.getChangedCardKeywords()); + // TODO: Is this correct? Does it not duplicate keywords from enchantments and such? for (KeywordInterface kw : c.getHiddenExtrinsicKeywords()) newCard.addHiddenExtrinsicKeyword(kw); @@ -305,7 +304,7 @@ public class GameCopier { if (c.isFaceDown()) { boolean isCreature = newCard.isCreature(); boolean hasManaCost = !newCard.getManaCost().isNoCost(); - newCard.setState(CardStateName.FaceDown, true); + newCard.turnFaceDown(true); if (c.isManifested()) { newCard.setManifested(true); // TODO: Should be able to copy other abilities... @@ -335,6 +334,11 @@ public class GameCopier { } } + newCard.setFlipped(c.isFlipped()); + for (Map.Entry e : c.getCloneStates().entrySet()) { + newCard.addCloneState(e.getValue().copy(newCard, true), e.getKey()); + } + Map counters = c.getCounters(); if (!counters.isEmpty()) { newCard.setCounters(Maps.newEnumMap(counters)); diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java index e1b57b93bbe..106670042d7 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -194,6 +194,8 @@ public class GameSimulator { System.out.println(); } final SpellAbility playingSa = sa; + + simGame.copyLastState(); ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() { @Override public void run() { diff --git a/forge-core/src/main/java/forge/card/CardStateName.java b/forge-core/src/main/java/forge/card/CardStateName.java index fef6020ddac..b7333a7de23 100644 --- a/forge-core/src/main/java/forge/card/CardStateName.java +++ b/forge-core/src/main/java/forge/card/CardStateName.java @@ -5,13 +5,12 @@ public enum CardStateName { Original, FaceDown, Flipped, - Cloner, Transformed, Meld, - Cloned, LeftSplit, RightSplit, - OriginalText; // backup state for cards like Volrath's Shapeshifter + + ; /** * TODO: Write javadoc for this method. diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 36657751ce2..d565428ca6b 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -901,10 +901,8 @@ public class Game { for (Card c : p.getAllCards()) { if (c.hasSVar("NeedsOrderedGraveyard")) { return true; - } else if (c.getStates().contains(CardStateName.OriginalText)) { - if (c.getState(CardStateName.OriginalText).hasSVar("NeedsOrderedGraveyard")) { - return true; - } + } else if (c.getOriginalState(CardStateName.Original).hasSVar("NeedsOrderedGraveyard")) { + return true; } } for (Card c : p.getOpponents().getCardsIn(ZoneType.Battlefield)) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index ffa159ca135..9b1577b1c68 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -193,26 +193,9 @@ public class GameAction { } if (!c.isToken()) { - if (c.isCloned()) { - c.switchStates(CardStateName.Original, CardStateName.Cloner, false); - c.setState(CardStateName.Original, false); - c.clearStates(CardStateName.Cloner, false); - if (c.isFlipCard()) { - c.clearStates(CardStateName.Flipped, false); - } - if (c.getStates().contains(CardStateName.OriginalText)) { - c.clearStates(CardStateName.OriginalText, false); - c.removeSVar("GainingTextFrom"); - c.removeSVar("GainingTextFromTimestamp"); - } - c.updateStateForView(); - } else if (c.getStates().contains(CardStateName.OriginalText)) { - // Volrath's Shapeshifter - CardFactory.copyState(c, CardStateName.OriginalText, c, CardStateName.Original, false); - c.setState(CardStateName.Original, false); - c.clearStates(CardStateName.OriginalText, false); - c.removeSVar("GainingTextFrom"); - c.removeSVar("GainingTextFromTimestamp"); + if (c.isCloned() || c.hasTextChangeState()) { + c.removeCloneStates(); + c.removeTextChangeStates(); c.updateStateForView(); } @@ -287,18 +270,10 @@ public class GameAction { // not to battlefield anymore! toBattlefield = false; - if (copied.isCloned()) { - copied.switchStates(CardStateName.Original, CardStateName.Cloner, false); - copied.setState(CardStateName.Original, false); - copied.clearStates(CardStateName.Cloner, false); - if (copied.isFlipCard()) { - copied.clearStates(CardStateName.Flipped, false); - } - if (copied.getStates().contains(CardStateName.OriginalText)) { - copied.clearStates(CardStateName.OriginalText, false); - copied.removeSVar("GainingTextFrom"); - copied.removeSVar("GainingTextFromTimestamp"); - } + if (c.isCloned() || c.hasTextChangeState()) { + c.removeCloneStates(); + c.removeTextChangeStates(); + c.updateStateForView(); } if (copied.getCurrentStateName() != CardStateName.Original) { @@ -858,6 +833,7 @@ public class GameAction { final Map affectedPerAbility = Maps.newHashMap(); for (final StaticAbilityLayer layer : StaticAbilityLayer.CONTINUOUS_LAYERS) { + List toAdd = Lists.newArrayList(); for (final StaticAbility stAb : staticAbilities) { final CardCollectionView previouslyAffected = affectedPerAbility.get(stAb); final CardCollectionView affectedHere; @@ -869,8 +845,18 @@ public class GameAction { } else { affectedHere = previouslyAffected; stAb.applyContinuousAbility(layer, previouslyAffected); - } + } + if (affectedHere != null) { + for (final Card c : affectedHere) { + for (final StaticAbility st2 : c.getStaticAbilities()) { + if (!staticAbilities.contains(st2)) { + toAdd.add(st2); + } + } + } + } } + staticAbilities.addAll(toAdd); } for (final CardCollectionView affected : affectedPerAbility.values()) { diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java index b2c925c748e..0429f08b24f 100644 --- a/forge-game/src/main/java/forge/game/StaticEffect.java +++ b/forge-game/src/main/java/forge/game/StaticEffect.java @@ -998,6 +998,10 @@ public class StaticEffect { affectedCard.removeWithFlash(getTimestamp()); } + if (params.containsKey("GainTextOf")) { + affectedCard.removeTextChangeState(getTimestamp()); + } + affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering } return affectedCards; 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 dac590fefe5..ab5e1eea5eb 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 @@ -2,7 +2,6 @@ package forge.game.ability.effects; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; -import forge.card.CardStateName; import forge.game.Game; import forge.game.GameActionUtil; import forge.game.ability.AbilityUtils; @@ -178,7 +177,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { movedCard.setExiledWith(host); } if (sa.hasParam("ExileFaceDown")) { - movedCard.setState(CardStateName.FaceDown, true); + movedCard.turnFaceDown(true); } if (sa.hasParam("Tapped")) { movedCard.setTapped(true); 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 1f17e125d87..fdcab6ed8dd 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 @@ -5,10 +5,8 @@ import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - import forge.GameCommand; import forge.card.CardStateName; -import forge.card.CardType; import forge.game.Game; import forge.game.GameEntity; import forge.game.GameObject; @@ -29,12 +27,12 @@ import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.Aggregates; -import forge.util.TextUtil; -import forge.util.collect.*; import forge.util.Lang; import forge.util.MessageUtil; +import forge.util.TextUtil; +import forge.util.collect.FCollection; +import forge.util.collect.FCollectionView; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -542,7 +540,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard.updateStateForView(); } if (sa.hasParam("FaceDown")) { - movedCard.setState(CardStateName.FaceDown, true); + movedCard.turnFaceDown(true); } if (sa.hasParam("Attacking")) { // What should they attack? @@ -592,7 +590,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("ExileFaceDown")) { - movedCard.setState(CardStateName.FaceDown, true); + movedCard.turnFaceDown(true); } if (sa.hasParam("TrackDiscarded")) { @@ -1050,24 +1048,22 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger if (sa.hasParam("FaceDown") && ZoneType.Battlefield.equals(destination)) { - c.setState(CardStateName.FaceDown, true); + c.turnFaceDown(true); // set New Pt doesn't work because this values need to be copyable for clone effects - if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")) { - if (sa.hasParam("FaceDownPower")) { - c.setBasePower(AbilityUtils.calculateAmount( - source, sa.getParam("FaceDownPower"), sa)); - } - if (sa.hasParam("FaceDownToughness")) { - c.setBaseToughness(AbilityUtils.calculateAmount( - source, sa.getParam("FaceDownToughness"), sa)); - } + if (sa.hasParam("FaceDownPower")) { + c.setBasePower(AbilityUtils.calculateAmount( + source, sa.getParam("FaceDownPower"), sa)); + } + if (sa.hasParam("FaceDownToughness")) { + c.setBaseToughness(AbilityUtils.calculateAmount( + source, sa.getParam("FaceDownToughness"), sa)); } if (sa.hasParam("FaceDownAddType")) { - CardType t = new CardType(c.getCurrentState().getType()); - t.addAll(Arrays.asList(sa.getParam("FaceDownAddType").split(","))); - c.getCurrentState().setType(t); + for (String type : sa.getParam("FaceDownAddType").split(",")) { + c.addType(type); + } } if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness") @@ -1091,7 +1087,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { // need to do that again? if (sa.hasParam("FaceDown") && !ZoneType.Battlefield.equals(destination)) { - movedCard.setState(CardStateName.FaceDown, true); + movedCard.turnFaceDown(true); } movedCard.setTimestamp(ts); } @@ -1105,7 +1101,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard.setExiledWith(host); } if (sa.hasParam("ExileFaceDown")) { - movedCard.setState(CardStateName.FaceDown, true); + movedCard.turnFaceDown(true); } } else { 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 ea0cc83331a..dfde4a019ad 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 @@ -1,26 +1,16 @@ package forge.game.ability.effects; import forge.GameCommand; -import forge.card.CardStateName; -import forge.card.MagicColor; -import forge.card.mana.ManaCost; import forge.game.Game; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.*; import forge.game.event.GameEventCardStatsChanged; import forge.game.player.Player; -import forge.game.replacement.ReplacementEffect; import forge.game.spellability.SpellAbility; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; import forge.game.zone.ZoneType; -import java.util.Arrays; import java.util.List; -import java.util.Map; public class CloneEffect extends SpellAbilityEffect { // TODO update this method @@ -57,7 +47,6 @@ public class CloneEffect extends SpellAbilityEffect { final Card host = sa.getHostCard(); final Player activator = sa.getActivatingPlayer(); Card tgtCard = host; - final Map origSVars = host.getSVars(); final Game game = activator.getGame(); // find cloning source i.e. thing to be copied @@ -68,9 +57,19 @@ public class CloneEffect extends SpellAbilityEffect { if (sa.hasParam("ChoiceZone")) { choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone")); } - CardCollectionView choices = game.getCardsIn(choiceZone); + CardCollection choices = new CardCollection(game.getCardsIn(choiceZone)); + + // choices need to be filtered by LastState Battlefield or Graveyard + // if a Clone enters the field as other cards it could clone, + // the clone should not be able to clone them + if (choiceZone.equals(ZoneType.Battlefield)) { + choices.retainAll(sa.getLastStateBattlefield()); + } else if (choiceZone.equals(ZoneType.Graveyard)) { + choices.retainAll(sa.getLastStateGraveyard()); + } + choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host); - + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card "; cardToCopy = activator.getController().chooseSingleEntityForEffect(choices, sa, title, false); } else if (sa.hasParam("Defined")) { @@ -91,9 +90,14 @@ public class CloneEffect extends SpellAbilityEffect { } // find target of cloning i.e. card becoming a clone - final List cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa); - if (!cloneTargets.isEmpty()) { - tgtCard = cloneTargets.get(0); + if (sa.hasParam("CloneTarget")) { + final List cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa); + if (!cloneTargets.isEmpty()) { + tgtCard = cloneTargets.get(0); + game.getTriggerHandler().clearInstrinsicActiveTriggers(tgtCard, null); + } + } else if (sa.hasParam("Choices") && sa.usesTargeting()) { + tgtCard = sa.getTargets().getFirstTargetedCard(); game.getTriggerHandler().clearInstrinsicActiveTriggers(tgtCard, null); } @@ -103,96 +107,12 @@ public class CloneEffect extends SpellAbilityEffect { } } - // determine the image to be used for the clone - String imageFileName = cardToCopy.getGame().getRules().canCloneUseTargetsImage() ? tgtCard.getImageKey() : cardToCopy.getImageKey(); - if (sa.hasParam("ImageSource")) { // Allow the image to be stipulated by using a defined card source - List cloneImgSources = AbilityUtils.getDefinedCards(host, sa.getParam("ImageSource"), sa); - if (!cloneImgSources.isEmpty()) { - imageFileName = cloneImgSources.get(0).getImageKey(); - } - } + final Long ts = game.getNextTimestamp(); + tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts); - final boolean keepName = sa.hasParam("KeepName"); - final String newName = sa.getParamOrDefault("NewName", null); - final String originalName = tgtCard.getName(); - final boolean copyingSelf = (tgtCard == cardToCopy); - final boolean isTransformed = cardToCopy.getCurrentStateName() == CardStateName.Transformed || cardToCopy.getCurrentStateName() == CardStateName.Meld || cardToCopy.getCurrentStateName() == CardStateName.Flipped; - final CardStateName origState = isTransformed || cardToCopy.isFaceDown() ? CardStateName.Original : cardToCopy.getCurrentStateName(); - - if (!copyingSelf) { - if (tgtCard.isCloned()) { // cloning again - tgtCard.switchStates(CardStateName.Cloner, origState, false); - tgtCard.setState(origState, false); - tgtCard.clearStates(CardStateName.Cloner, false); - } - // add "Cloner" state to clone - tgtCard.addAlternateState(CardStateName.Cloner, false); - tgtCard.switchStates(origState, CardStateName.Cloner, false); - tgtCard.setState(origState, false); - } else { - //copy Original state to Cloned - tgtCard.addAlternateState(CardStateName.Cloned, false); - tgtCard.switchStates(origState, CardStateName.Cloned, false); - if (tgtCard.isFlipCard()) { - tgtCard.setState(CardStateName.Original, false); - } - } - - CardFactory.copyCopiableCharacteristics(cardToCopy, tgtCard); - - // add extra abilities as granted by the copy effect - addExtraCharacteristics(tgtCard, sa, origSVars); - - // set the host card for copied replacement effects - // needed for copied xPaid ETB effects (for the copy, xPaid = 0) - for (final ReplacementEffect rep : tgtCard.getReplacementEffects()) { - final SpellAbility newSa = rep.getOverridingAbility(); - if (newSa != null) { - newSa.setOriginalHost(cardToCopy); - } - } - - // set the host card for copied spellabilities - for (final SpellAbility newSa : tgtCard.getSpellAbilities()) { - newSa.setOriginalHost(cardToCopy); - } - - // restore name if it should be unchanged - // this should only be used for Sakashima the Impostor Avatar - if (keepName) { - tgtCard.setName(originalName); - } - if (newName != null) { - tgtCard.setName(newName); - } - - // If target is a flip card, also set characteristics of the flipped - // state. - if (cardToCopy.isFlipCard()) { - final CardState flippedState = tgtCard.getState(CardStateName.Flipped); - if (keepName) { - flippedState.setName(originalName); - } - if (newName != null) { - tgtCard.setName(newName); - } - //keep the Clone card image for the cloned card - flippedState.setImageKey(imageFileName); - } - - //Clean up copy of cloned state - if (copyingSelf) { - tgtCard.clearStates(CardStateName.Cloned, false); - } - - //game.getTriggerHandler().registerActiveTrigger(tgtCard, false); - - //keep the Clone card image for the cloned card - if (cardToCopy.isFlipCard() && tgtCard.getCurrentStateName() != CardStateName.Flipped) { - //for a flip card that isn't flipped, load the original image - tgtCard.setImageKey(cardToCopy.getImageKey(CardStateName.Original)); - } else { - tgtCard.setImageKey(imageFileName); + // set ETB tapped of clone + if (sa.hasParam("IntoPlayTapped")) { + tgtCard.setTapped(true); } tgtCard.updateStateForView(); @@ -213,10 +133,7 @@ public class CloneEffect extends SpellAbilityEffect { @Override public void run() { - if (cloneCard.isCloned()) { - cloneCard.setState(CardStateName.Cloner, false); - cloneCard.switchStates(CardStateName.Cloner, origState, false); - cloneCard.clearStates(CardStateName.Cloner, false); + if (cloneCard.removeCloneState(ts)) { cloneCard.updateStateForView(); game.fireEvent(new GameEventCardStatsChanged(cloneCard)); } @@ -233,148 +150,11 @@ public class CloneEffect extends SpellAbilityEffect { else if (duration.equals("UntilUnattached")) { sa.getHostCard().addUnattachCommand(unclone); } + else if (duration.equals("UntilFacedown")) { + sa.getHostCard().addFacedownCommand(unclone); + } } game.fireEvent(new GameEventCardStatsChanged(tgtCard)); } // cloneResolve - private static void addExtraCharacteristics(final Card tgtCard, final SpellAbility sa, final Map origSVars) { - // additional types to clone - if (sa.hasParam("AddTypes")) { - for (final String type : Arrays.asList(sa.getParam("AddTypes").split(","))) { - tgtCard.addType(type); - } - } - - // triggers to add to clone - if (sa.hasParam("AddTriggers")) { - for (final String s : Arrays.asList(sa.getParam("AddTriggers").split(","))) { - if (origSVars.containsKey(s)) { - final String actualTrigger = origSVars.get(s); - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, tgtCard, true); - tgtCard.addTrigger(parsedTrigger); - } - } - } - - // SVars to add to clone - if (sa.hasParam("AddSVars")) { - for (final String s : Arrays.asList(sa.getParam("AddSVars").split(","))) { - if (origSVars.containsKey(s)) { - final String actualsVar = origSVars.get(s); - tgtCard.setSVar(s, actualsVar); - } - } - } - - // abilities to add to clone - if (sa.hasParam("AddAbilities")) { - for (final String s : Arrays.asList(sa.getParam("AddAbilities").split(","))) { - if (origSVars.containsKey(s)) { - final String actualAbility = origSVars.get(s); - final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, tgtCard); - tgtCard.addSpellAbility(grantedAbility); - } - } - } - - // keywords to add to clone - - if (sa.hasParam("AddKeywords")) { - final List keywords = Arrays.asList(sa.getParam("AddKeywords").split(" & ")); - // allow SVar substitution for keywords - for (int i = 0; i < keywords.size(); i++) { - String k = keywords.get(i); - if (origSVars.containsKey(k)) { - keywords.add("\"" + k + "\""); - keywords.remove(k); - } - k = keywords.get(i); - - tgtCard.addIntrinsicKeyword(k); - } - } - - // set ETB tapped of clone - if (sa.hasParam("IntoPlayTapped")) { - tgtCard.setTapped(true); - } - - // set power of clone - if (sa.hasParam("SetPower")) { - String rhs = sa.getParam("SetPower"); - int power = Integer.MAX_VALUE; - try { - power = Integer.parseInt(rhs); - } catch (final NumberFormatException e) { - power = CardFactoryUtil.xCount(tgtCard, tgtCard.getSVar(rhs)); - } - for (StaticAbility sta : tgtCard.getStaticAbilities()) { - Map params = sta.getMapParams(); - if (params.containsKey("CharacteristicDefining") && params.containsKey("SetPower")) - tgtCard.removeStaticAbility(sta); - } - tgtCard.setBasePower(power); - } - - // set toughness of clone - if (sa.hasParam("SetToughness")) { - String rhs = sa.getParam("SetToughness"); - int toughness = Integer.MAX_VALUE; - try { - toughness = Integer.parseInt(rhs); - } catch (final NumberFormatException e) { - toughness = CardFactoryUtil.xCount(tgtCard, tgtCard.getSVar(rhs)); - } - for (StaticAbility sta : tgtCard.getStaticAbilities()) { - Map params = sta.getMapParams(); - if (params.containsKey("CharacteristicDefining") && params.containsKey("SetToughness")) - tgtCard.removeStaticAbility(sta); - } - tgtCard.setBaseToughness(toughness); - } - - // colors to be added or changed to - String shortColors = ""; - if (sa.hasParam("Colors")) { - final String colors = sa.getParam("Colors"); - if (colors.equals("ChosenColor")) { - shortColors = CardUtil.getShortColorsString(tgtCard.getChosenColors()); - } else { - shortColors = CardUtil.getShortColorsString(Arrays.asList(colors.split(","))); - } - } - if (sa.hasParam("OverwriteColors")) { - tgtCard.setColor(shortColors); - } else { - // TODO: this actually doesn't work for some reason (and fiddling with timestamps doesn't seem to fix it). - // No cards currently use this, but if some ever do, this code will require tweaking. - tgtCard.addColor(shortColors, true, tgtCard.getTimestamp()); - } - - if (sa.hasParam("Embalm") && tgtCard.isEmbalmed()) { - tgtCard.addType("Zombie"); - tgtCard.setColor(MagicColor.WHITE); - tgtCard.setManaCost(ManaCost.NO_COST); - } - if (sa.hasParam("Eternalize") && tgtCard.isEternalized()) { - tgtCard.addType("Zombie"); - tgtCard.setColor(MagicColor.BLACK); - tgtCard.setManaCost(ManaCost.NO_COST); - tgtCard.setBasePower(4); - tgtCard.setBaseToughness(4); - } - - if (sa.hasParam("GainThisAbility")) { - SpellAbility root = sa.getRootAbility(); - - if (root.isTrigger() && root.getTrigger() != null) { - tgtCard.addTrigger(root.getTrigger().copy(tgtCard, false)); - } else if (root.isReplacementAbility()) { - tgtCard.addReplacementEffect(root.getReplacementEffect().copy(tgtCard, false)); - } else { - tgtCard.addSpellAbility(root.copy(tgtCard, false)); - } - } - } - } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index a0e9cf35eb0..2b3e9aff85f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -2,16 +2,11 @@ package forge.game.ability.effects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import forge.ImageKeys; import forge.StaticData; import forge.card.CardRulesPredicates; -import forge.card.CardType; -import forge.card.MagicColor; -import forge.card.mana.ManaCost; import forge.game.Game; import forge.game.GameEntity; import forge.game.ability.AbilityUtils; @@ -19,16 +14,12 @@ import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollectionView; import forge.game.card.CardFactory; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.token.TokenInfo; import forge.game.combat.Combat; import forge.game.event.GameEventCombatChanged; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.staticability.StaticAbility; -import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerHandler; import forge.game.zone.ZoneType; import forge.item.PaperCard; import forge.util.Aggregates; @@ -259,153 +250,29 @@ public class CopyPermanentEffect extends SpellAbilityEffect { private Card getProtoType(final SpellAbility sa, final Card original) { final Card host = sa.getHostCard(); - final List keywords = Lists.newArrayList(); - final List types = Lists.newArrayList(); - final List svars = Lists.newArrayList(); - final List triggers = Lists.newArrayList(); - boolean asNonLegendary = false; + final Player newOwner = sa.getActivatingPlayer(); + int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); + final Card copy = new Card(id, original.getPaperCard(), host.getGame()); + copy.setOwner(newOwner); + copy.setSetCode(original.getSetCode()); - if (sa.hasParam("Keywords")) { - keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & "))); - } - if (sa.hasParam("AddTypes")) { - types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & "))); - } - if (sa.hasParam("NonLegendary")) { - asNonLegendary = true; - } - if (sa.hasParam("AddSVars")) { - svars.addAll(Arrays.asList(sa.getParam("AddSVars").split(" & "))); - } - if (sa.hasParam("Triggers")) { - triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(" & "))); + if (sa.hasParam("Embalm")) { + copy.setEmbalmed(true); } - final Card copy = CardFactory.copyCopiableCharacteristics(original, sa.getActivatingPlayer()); + if (sa.hasParam("Eternalize")) { + copy.setEternalized(true); + } + + copy.setStates(CardFactory.getCloneStates(original, copy, sa)); + // force update the now set State + copy.setState(copy.getCurrentStateName(), true, true); copy.setToken(true); - copy.setCopiedPermanent(original); - // add keywords from sa - for (final String kw : keywords) { - copy.addIntrinsicKeyword(kw); - } - if (asNonLegendary) { - copy.removeType(CardType.Supertype.Legendary); - } - if (sa.hasParam("SetCreatureTypes")) { - copy.setCreatureTypes(ImmutableList.copyOf(sa.getParam("SetCreatureTypes").split(" "))); - } - - if (sa.hasParam("SetColor")) { - copy.setColor(MagicColor.fromName(sa.getParam("SetColor"))); - } - - for (final String type : types) { - copy.addType(type); - } - for (final String svar : svars) { - String actualsVar = host.getSVar(svar); - String name = svar; - if (actualsVar.startsWith("SVar:")) { - actualsVar = actualsVar.split("SVar:")[1]; - name = actualsVar.split(":")[0]; - actualsVar = actualsVar.split(":")[1]; - } - copy.setSVar(name, actualsVar); - } - for (final String s : triggers) { - final String actualTrigger = host.getSVar(s); - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, copy, true); - copy.addTrigger(parsedTrigger); - } - - // set power of clone - if (sa.hasParam("SetPower")) { - String rhs = sa.getParam("SetPower"); - int power = Integer.MAX_VALUE; - try { - power = Integer.parseInt(rhs); - } catch (final NumberFormatException e) { - power = CardFactoryUtil.xCount(copy, copy.getSVar(rhs)); - } - copy.setBasePower(power); - } - - // set toughness of clone - if (sa.hasParam("SetToughness")) { - String rhs = sa.getParam("SetToughness"); - int toughness = Integer.MAX_VALUE; - try { - toughness = Integer.parseInt(rhs); - } catch (final NumberFormatException e) { - toughness = CardFactoryUtil.xCount(copy, copy.getSVar(rhs)); - } - copy.setBaseToughness(toughness); - } if (sa.hasParam("AtEOTTrig")) { addSelfTrigger(sa, sa.getParam("AtEOTTrig"), copy); } - if (sa.hasParam("Embalm")) { - copy.addType("Zombie"); - copy.setColor(MagicColor.WHITE); - copy.setManaCost(ManaCost.NO_COST); - copy.setEmbalmed(true); - - String name = TextUtil.fastReplace( - TextUtil.fastReplace(copy.getName(), ",", ""), - " ", "_").toLowerCase(); - copy.setImageKey(ImageKeys.getTokenKey("embalm_" + name)); - } - - if (sa.hasParam("Eternalize")) { - copy.addType("Zombie"); - copy.setColor(MagicColor.BLACK); - copy.setManaCost(ManaCost.NO_COST); - copy.setBasePower(4); - copy.setBaseToughness(4); - copy.setEternalized(true); - - String name = TextUtil.fastReplace( - TextUtil.fastReplace(copy.getName(), ",", ""), - " ", "_").toLowerCase(); - copy.setImageKey(ImageKeys.getTokenKey("eternalize_" + name)); - } - - // remove some characteristic static abilties - for (StaticAbility sta : copy.getStaticAbilities()) { - if (!sta.hasParam("CharacteristicDefining")) { - continue; - } - if (sa.hasParam("SetPower") || sa.hasParam("Eternalize")) { - if (sta.hasParam("SetPower")) - copy.removeStaticAbility(sta); - } - if (sa.hasParam("SetToughness") || sa.hasParam("Eternalize")) { - if (sta.hasParam("SetToughness")) - copy.removeStaticAbility(sta); - } - if (sa.hasParam("SetCreatureTypes")) { - // currently only Changeling and similar should be affected by that - // other cards using AddType$ ChosenType should not - if (sta.hasParam("AddType") && "AllCreatureTypes".equals(sta.getParam("AddType"))) { - copy.removeStaticAbility(sta); - } - } - if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize")) { - if (sta.hasParam("SetColor")) { - copy.removeStaticAbility(sta); - } - } - } - if (sa.hasParam("SetCreatureTypes")) { - copy.removeIntrinsicKeyword("Changeling"); - } - if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize")) { - copy.removeIntrinsicKeyword("Devoid"); - } - - copy.updateStateForView(); return copy; } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index c1b9dca94da..9e5448cd3a8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -1,6 +1,5 @@ package forge.game.ability.effects; -import forge.card.CardStateName; import forge.game.Game; import forge.game.GameActionUtil; import forge.game.ability.AbilityUtils; @@ -318,7 +317,7 @@ public class DigEffect extends SpellAbilityEffect { } if (sa.hasParam("ExileFaceDown")) { - c.setState(CardStateName.FaceDown, true); + c.turnFaceDown(true); } if (sa.hasParam("Imprint")) { host.addImprintedCard(c); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java index 72085b71795..a770165c299 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java @@ -7,7 +7,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.StaticData; import forge.card.CardRulesPredicates; -import forge.card.CardStateName; import forge.card.ColorSet; import forge.card.MagicColor; import forge.game.Game; @@ -49,8 +48,10 @@ public class PlayLandVariantEffect extends SpellAbilityEffect { for (byte i = 0; i < MagicColor.WUBRG.length; i++) { if (color.hasAnyColor(MagicColor.WUBRG[i])) { landNames.add(MagicColor.Constant.BASIC_LANDS.get(i)); + landNames.add(MagicColor.Constant.SNOW_LANDS.get(i)); } } + final Predicate cp = Predicates.compose(new Predicate() { @Override public boolean apply(final String name) { @@ -69,15 +70,9 @@ public class PlayLandVariantEffect extends SpellAbilityEffect { random = CardFactory.getCard(ran, activator, game); } - final String imageFileName = game.getRules().canCloneUseTargetsImage() ? source.getImageKey() : random.getImageKey(); - source.addAlternateState(CardStateName.Cloner, false); - source.switchStates(CardStateName.Original, CardStateName.Cloner, false); - source.setState(CardStateName.Original, false); + source.addCloneState(CardFactory.getCloneStates(random, source, sa), game.getNextTimestamp()); source.updateStateForView(); - final CardStateName stateToCopy = random.getCurrentStateName(); - CardFactory.copyState(random, stateToCopy, source, source.getCurrentStateName()); - source.setImageKey(imageFileName); - + source.setController(activator, 0); game.getAction().moveTo(activator.getZone(ZoneType.Battlefield), source, sa); 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 e28a56cd997..525a3c754dd 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -88,7 +88,6 @@ public class Card extends GameEntity implements Comparable { private final Map states = Maps.newEnumMap(CardStateName.class); private CardState currentState; private CardStateName currentStateName = CardStateName.Original; - private CardStateName preFaceDownState = CardStateName.Original; private ZoneType castFrom = null; private SpellAbility castSA = null; @@ -119,6 +118,8 @@ public class Card extends GameEntity implements Comparable { private final Map changedCardTypes = Maps.newTreeMap(); private final Map changedCardKeywords = Maps.newTreeMap(); private final Map changedCardColors = Maps.newTreeMap(); + private final NavigableMap clonedStates = Maps.newTreeMap(); + private final NavigableMap textChangeStates = Maps.newTreeMap(); // changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps private final CardChangedWords changedTextColors = new CardChangedWords(); @@ -170,6 +171,11 @@ public class Card extends GameEntity implements Comparable { private boolean madness = false; private boolean madnessWithoutCast = false; + private boolean flipped = false; + private boolean facedown = false; + // set for transform and meld, needed for clone effects + private boolean backside = false; + private boolean phasedOut = false; private boolean directlyPhasedOut = true; @@ -235,6 +241,7 @@ public class Card extends GameEntity implements Comparable { private final List changeControllerCommandList = Lists.newArrayList(); private final List unattachCommandList = Lists.newArrayList(); private final List faceupCommandList = Lists.newArrayList(); + private final List facedownCommandList = Lists.newArrayList(); private final List staticCommandList = Lists.newArrayList(); private final static ImmutableList storableSVars = ImmutableList.of("ChosenX"); @@ -302,25 +309,10 @@ public class Card extends GameEntity implements Comparable { } public boolean changeToState(final CardStateName state) { - CardStateName cur = currentStateName; - - if (!setState(state, true)) { - return false; + if (hasState(state)) { + return setState(state, true); } - - if ((cur == CardStateName.Original && state == CardStateName.Transformed) - || (cur == CardStateName.Transformed && state == CardStateName.Original)) { - - // Clear old dfc trigger from the trigger handler - getGame().getTriggerHandler().clearInstrinsicActiveTriggers(this, null); - getGame().getTriggerHandler().registerActiveTrigger(this, false); - Map runParams = Maps.newHashMap(); - runParams.put("Transformer", this); - getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false); - this.incrementTransformedTimestamp(); - } - - return true; + return false; } public long getTransformedTimestamp() { return transformedTimestamp; } @@ -365,24 +357,76 @@ public class Card extends GameEntity implements Comparable { } return null; } + public CardState getState(final CardStateName state) { + return getState(state, false); + } + public CardState getState(final CardStateName state, boolean skipTextChange) { + if (!skipTextChange) { + CardCloneStates txtStates = getLastTextChangeState(); + if (txtStates != null) { + return txtStates.get(state); + } + } + CardCloneStates clStates = getLastClonedState(); + if (clStates == null) { + return getOriginalState(state); + } else { + return clStates.get(state); + } + } + + public boolean hasState(final CardStateName state) { + if (state == CardStateName.FaceDown) { + return true; + } + CardCloneStates clStates = getLastClonedState(); + if (clStates == null) { + return states.containsKey(state); + } else { + return clStates.containsKey(state); + } + } + + public CardState getOriginalState(final CardStateName state) { if (!states.containsKey(state) && state == CardStateName.FaceDown) { states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this)); } return states.get(state); } + public boolean setState(final CardStateName state, boolean updateView) { - if (!states.containsKey(state)) { - if (state == CardStateName.FaceDown) { - // The face-down state is created lazily only when needed. - states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this)); + return setState(state, updateView, false); + } + public boolean setState(final CardStateName state, boolean updateView, boolean forceUpdate) { + CardCloneStates textChangeStates = getLastTextChangeState(); + + if (textChangeStates != null) { + if (!textChangeStates.containsKey(state)) { + throw new RuntimeException(getName() + " tried to switch to non-existant text change state \"" + state + "\"!"); + //return false; // Nonexistant state. + } + } else { + CardCloneStates cloneStates = getLastClonedState(); + if (cloneStates != null) { + if (!cloneStates.containsKey(state)) { + throw new RuntimeException(getName() + " tried to switch to non-existant cloned state \"" + state + "\"!"); + //return false; // Nonexistant state. + } } else { - System.out.println(getName() + " tried to switch to non-existant state \"" + state + "\"!"); - return false; // Nonexistant state. + if (!states.containsKey(state)) { + if (state == CardStateName.FaceDown) { + // The face-down state is created lazily only when needed. + states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this)); + } else { + System.out.println(getName() + " tried to switch to non-existant state \"" + state + "\"!"); + return false; // Nonexistant state. + } + } } } - if (state.equals(currentStateName)) { + if (state.equals(currentStateName) && !forceUpdate) { return false; } @@ -392,7 +436,7 @@ public class Card extends GameEntity implements Comparable { } currentStateName = state; - currentState = states.get(state); + currentState = getState(state); // update the host for static abilities for (StaticAbility sa : currentState.getStaticAbilities()) { @@ -432,6 +476,14 @@ public class Card extends GameEntity implements Comparable { return currentStateName; } + // use by CopyPermament + public void setStates(Map map) { + states.clear(); + states.putAll(map); + } + + // was only used for Clone Effects + @Deprecated public void switchStates(final CardStateName from, final CardStateName to, boolean updateView) { final CardState tmp = states.get(from); states.put(from, states.get(to)); @@ -445,7 +497,7 @@ public class Card extends GameEntity implements Comparable { } public final void addAlternateState(final CardStateName state, final boolean updateView) { - states.put(state, new CardState(view.createAlternateState(state), this)); + states.put(state, new CardState(this, state)); if (updateView) { view.updateState(this); } @@ -483,10 +535,6 @@ public class Card extends GameEntity implements Comparable { currentState.getView().updateType(currentState); } - public void setPreFaceDownState(CardStateName preCharacteristic) { - preFaceDownState = preCharacteristic; - } - public boolean changeCardState(final String mode, final String customState) { if (mode == null) return changeToState(CardStateName.smartValueOf(customState)); @@ -501,26 +549,43 @@ public class Card extends GameEntity implements Comparable { return false; } - CardStateName destState = oldState == CardStateName.Transformed ? CardStateName.Original : CardStateName.Transformed; + backside = !backside; - return changeToState(destState); + boolean result = changeToState(backside ? CardStateName.Transformed : CardStateName.Original); + + // do the Transform trigger there, it can also happen if the resulting state doesn't change + + // Clear old dfc trigger from the trigger handler + getGame().getTriggerHandler().clearInstrinsicActiveTriggers(this, null); + getGame().getTriggerHandler().registerActiveTrigger(this, false); + Map runParams = Maps.newHashMap(); + runParams.put("Transformer", this); + getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false); + incrementTransformedTimestamp(); + + return result; } else if (mode.equals("Flip") && isFlipCard()) { - CardStateName destState = oldState == CardStateName.Flipped ? CardStateName.Original : CardStateName.Flipped; - return changeToState(destState); + // 709.4. Flipping a permanent is a one-way process. + if (isFlipped()) { + return false; + } + + flipped = true; + + // a facedown card does flip but the state doesn't change + if (isFaceDown()) { + return false; + } + + return changeToState(CardStateName.Flipped); } else if (mode.equals("TurnFace")) { - if (oldState == CardStateName.Original) { - // Reset cloned state if Vesuvan Shapeshifter - if (isCloned() && getState(CardStateName.Cloner).getName().equals("Vesuvan Shapeshifter")) { - switchStates(CardStateName.Cloner, CardStateName.Original, false); - setState(CardStateName.Original, false); - clearStates(CardStateName.Cloner, false); - } + if (oldState == CardStateName.Original || oldState == CardStateName.Flipped) { return turnFaceDown(); - } else if (oldState == CardStateName.FaceDown) { + } else if (isFaceDown()) { return turnFaceUp(); } - } else if (mode.equals("Meld") && hasAlternateState()) { + } else if (mode.equals("Meld") && isMeldable()) { return changeToState(CardStateName.Meld); } return false; @@ -543,7 +608,6 @@ public class Card extends GameEntity implements Comparable { setController(p, game.getNextTimestamp()); // Mark this card as "manifested" - setPreFaceDownState(CardStateName.Original); setManifested(true); Card c = game.getAction().moveToPlay(this, p, sa); @@ -564,14 +628,17 @@ public class Card extends GameEntity implements Comparable { public boolean turnFaceDown(boolean override) { if (override || (!isDoubleFaced() && !isMeldable())) { - preFaceDownState = currentStateName; - return setState(CardStateName.FaceDown, true); + facedown = true; + if (setState(CardStateName.FaceDown, true)) { + runFacedownCommands(); + return true; + } } return false; } public boolean turnFaceDownNoUpdate() { - preFaceDownState = currentStateName; + facedown = true; return setState(CardStateName.FaceDown, false); } @@ -580,16 +647,22 @@ public class Card extends GameEntity implements Comparable { } public boolean turnFaceUp(boolean manifestPaid, boolean runTriggers) { - if (currentStateName == CardStateName.FaceDown) { - if (manifestPaid && this.isManifested() && !this.getRules().getType().isCreature()) { + if (isFaceDown()) { + if (manifestPaid && isManifested() && !getRules().getType().isCreature()) { // If we've manifested a non-creature and we're demanifesting disallow it // Unless this creature also has a Morph ability - return false; } - boolean result = setState(preFaceDownState, true); + boolean result; + if (isFlipped() && isFlipCard()) { + result = setState(CardStateName.Flipped, true); + } else { + result = setState(CardStateName.Original, true); + } + + facedown = false; // need to run faceup commands, currently // it does cleanup the modified facedown state if (result) { @@ -619,10 +692,15 @@ public class Card extends GameEntity implements Comparable { return false; } - CardStateName oldState = getCurrentStateName(); - CardStateName destState = oldState == CardStateName.Transformed ? CardStateName.Original : CardStateName.Transformed; + CardStateName destState = backside ? CardStateName.Original : CardStateName.Transformed; - if (isInPlay() && !getState(destState).getType().isPermanent()) { + // below only when in play + if (!isInPlay()) { + return true; + } + + // use Original State for the transform check + if (!getOriginalState(destState).getType().isPermanent()) { return false; } @@ -653,8 +731,7 @@ public class Card extends GameEntity implements Comparable { } public final boolean isInAlternateState() { - return currentStateName != CardStateName.Original - && currentStateName != CardStateName.Cloned; + return currentStateName != CardStateName.Original; } public final boolean hasAlternateState() { @@ -664,34 +741,31 @@ public class Card extends GameEntity implements Comparable { int numStates = states.keySet().size(); - // OriginalText is a technical state used for backup purposes by cards - // like Volrath's Shapeshifter. It's not a directly playable card state, - // so ignore it - if (states.containsKey(CardStateName.OriginalText)) { - numStates--; - } - return numStates > threshold; } public final boolean isDoubleFaced() { - return states.containsKey(CardStateName.Transformed); + return getRules() != null && getRules().getSplitType() == CardSplitType.Transform; } public final boolean isMeldable() { - return states.containsKey(CardStateName.Meld); + return getRules() != null && getRules().getSplitType() == CardSplitType.Meld; } public final boolean isFlipCard() { - return states.containsKey(CardStateName.Flipped); + return hasState(CardStateName.Flipped); } public final boolean isSplitCard() { - return states.containsKey(CardStateName.LeftSplit); + return getRules() != null && getRules().getSplitType() == CardSplitType.Split; + } + + public final boolean isBackSide() { + return backside; } public boolean isCloned() { - return states.containsKey(CardStateName.Cloner); + return !clonedStates.isEmpty(); } public static List getStorableSVars() { @@ -2396,7 +2470,20 @@ public class Card extends GameEntity implements Comparable { } public final boolean isFaceDown() { - return currentStateName == CardStateName.FaceDown; + //return currentStateName == CardStateName.FaceDown; + return facedown; + } + + public final void setFaceDown(boolean value) { + facedown = value; + } + + public final boolean isFlipped() { + return flipped; + } + + public final void setFlipped(boolean value) { + flipped = value; } public final void setCanCounter(final boolean b) { @@ -2415,6 +2502,7 @@ public class Card extends GameEntity implements Comparable { for (final GameCommand c : etbCommandList) { c.run(); } + etbCommandList.clear(); } public final void addLeavesPlayCommand(final GameCommand c) { @@ -2425,6 +2513,7 @@ public class Card extends GameEntity implements Comparable { for (final GameCommand c : leavePlayCommandList) { c.run(); } + leavePlayCommandList.clear(); } public final void addUntapCommand(final GameCommand c) { @@ -2439,6 +2528,10 @@ public class Card extends GameEntity implements Comparable { faceupCommandList.add(c); } + public final void addFacedownCommand(final GameCommand c) { + facedownCommandList.add(c); + } + public final void runUnattachCommands() { for (final GameCommand c : unattachCommandList) { c.run(); @@ -2449,6 +2542,14 @@ public class Card extends GameEntity implements Comparable { for (final GameCommand c : faceupCommandList) { c.run(); } + faceupCommandList.clear(); + } + + public final void runFacedownCommands() { + for (final GameCommand c : facedownCommandList) { + c.run(); + } + facedownCommandList.clear(); } public final void addChangeControllerCommand(final GameCommand c) { @@ -2459,6 +2560,7 @@ public class Card extends GameEntity implements Comparable { for (final GameCommand c : changeControllerCommandList) { c.run(); } + changeControllerCommandList.clear(); } public final void setSickness(boolean sickness0) { @@ -2900,11 +3002,9 @@ public class Card extends GameEntity implements Comparable { public final void setColor(final String color) { currentState.setColor(color); - currentState.getView().updateColors(this); } public final void setColor(final byte color) { currentState.setColor(color); - currentState.getView().updateColors(this); } public final ColorSet determineColor() { @@ -2977,6 +3077,113 @@ public class Card extends GameEntity implements Comparable { return getLatestPT().getRight(); } + public final void addCloneState(CardCloneStates states, final long timestamp) { + clonedStates.put(timestamp, states); + updateCloneState(true); + } + + public final boolean removeCloneState(final long timestamp) { + if (clonedStates.remove(timestamp) != null) { + updateCloneState(true); + return true; + } + return false; + } + + public final boolean removeCloneState(final CardTraitBase ctb) { + boolean changed = false; + List toRemove = Lists.newArrayList(); + for (final Entry e : clonedStates.entrySet()) { + if (ctb.equals(e.getValue().getSource())) { + toRemove.add(e.getKey()); + changed = true; + } + } + for (final Long l : toRemove) { + clonedStates.remove(l); + } + if (changed) { + updateCloneState(true); + } + + return changed; + } + + public final Card getCloner() { + CardCloneStates clStates = getLastClonedState(); + if (clStates == null) { + return null; + } + return clStates.getHost(); + } + + public final void removeCloneStates() { + clonedStates.clear(); + } + + public final Map getCloneStates() { + return clonedStates; + } + + public final void setCloneStates(Map val) { + clonedStates.clear(); + clonedStates.putAll(val); + updateCloneState(true); + } + + private final void updateCloneState(final boolean updateView) { + if (isFaceDown()) { + setState(CardStateName.FaceDown, updateView, true); + } else { + setState(getFaceupCardStateName(), updateView, true); + } + } + + public final CardStateName getFaceupCardStateName() { + if (isFlipped() && hasState(CardStateName.Flipped)) { + return CardStateName.Flipped; + } else if (backside && isDoubleFaced()) { + return CardStateName.Transformed; + } else if (backside && isMeldable()) { + return CardStateName.Meld; + } else { + return CardStateName.Original; + } + } + + private final CardCloneStates getLastClonedState() { + if (clonedStates.isEmpty()) { + return null; + } + return clonedStates.lastEntry().getValue(); + } + + public final void addTextChangeState(CardCloneStates states, final long timestamp) { + textChangeStates.put(timestamp, states); + updateCloneState(true); + } + + public final boolean removeTextChangeState(final long timestamp) { + if (textChangeStates.remove(timestamp) != null) { + updateCloneState(true); + return true; + } + return false; + } + public final void removeTextChangeStates() { + textChangeStates.clear(); + } + + private final CardCloneStates getLastTextChangeState() { + if (textChangeStates.isEmpty()) { + return null; + } + return textChangeStates.lastEntry().getValue(); + } + + public final boolean hasTextChangeState() { + return !textChangeStates.isEmpty(); + } /** * * Get the latest set Power and Toughness of this Card. @@ -5575,10 +5782,7 @@ public class Card extends GameEntity implements Comparable { if (isFaceDown()) { lkicheck = true; source = CardUtil.getLKICopy(source); - - // TODO need to be changed with CloneRewrite and FaceDownState? source.turnFaceUp(false, false); - source.getCurrentState().copyFrom(getState(CardStateName.Original), true); } if (lkicheck) { diff --git a/forge-game/src/main/java/forge/game/card/CardCloneStates.java b/forge-game/src/main/java/forge/game/card/CardCloneStates.java new file mode 100644 index 00000000000..eb3a85762e7 --- /dev/null +++ b/forge-game/src/main/java/forge/game/card/CardCloneStates.java @@ -0,0 +1,59 @@ +package forge.game.card; + +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.Maps; +import forge.card.CardStateName; +import forge.game.CardTraitBase; + +import java.util.Map; + +public class CardCloneStates extends ForwardingMap { + + private Map dataMap = Maps.newEnumMap(CardStateName.class); + + private Card origin = null; + private CardTraitBase ctb = null; + + public CardCloneStates(Card origin, CardTraitBase sa) { + super(); + this.origin = origin; + this.ctb = sa; + } + + public Card getOrigin() { + return origin; + } + + public CardTraitBase getSource() { + return ctb; + } + + public Card getHost() { + return ctb.getHostCard(); + } + + @Override + protected Map delegate() { + return dataMap; + } + + public CardState get(CardStateName key) { + if (dataMap.containsKey(key)) { + return super.get(key); + } + CardState original = super.get(CardStateName.Original); + // need to copy it so the view has the right state name + CardState result = new CardState(original.getCard(), key); + result.copyFrom(original, false); + dataMap.put(key, result); + return result; + } + + public CardCloneStates copy(final Card host, final boolean lki) { + CardCloneStates result = new CardCloneStates(origin, ctb); + for (Map.Entry e : dataMap.entrySet()) { + result.put(e.getKey(), e.getValue().copy(host, e.getKey(), lki)); + } + return result; + } +} diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 82db7497427..51a8660cd42 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -6,36 +6,44 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package forge.game.card; import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; + +import forge.ImageKeys; import forge.StaticData; import forge.card.*; import forge.card.mana.ManaCost; +import forge.game.CardTraitBase; import forge.game.Game; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.cost.Cost; import forge.game.player.Player; +import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.spellability.*; +import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.trigger.WrappedAbility; import forge.game.zone.ZoneType; import forge.item.IPaperCard; import forge.item.PaperCard; +import forge.util.TextUtil; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -44,7 +52,7 @@ import java.util.Map.Entry; *

* AbstractCardFactory class. *

- * + * * TODO The map field contains Card instances that have not gone through * getCard2, and thus lack abilities. However, when a new Card is requested via * getCard, it is this map's values that serve as the templates for the values @@ -54,7 +62,7 @@ import java.util.Map.Entry; * only one or the other. We may experiment in the future with using * allCard-type values for the map instead of the less complete ones that exist * there today. - * + * * @author Forge * @version $Id$ */ @@ -63,7 +71,7 @@ public class CardFactory { *

* copyCard. *

- * + * * @param in * a {@link forge.game.card.Card} object. * @return a {@link forge.game.card.Card} object. @@ -71,12 +79,16 @@ public class CardFactory { public final static Card copyCard(final Card in, boolean assignNewId) { Card out; if (!(in.isToken() || in.getCopiedPermanent() != null)) { - out = assignNewId ? getCard(in.getPaperCard(), in.getOwner(), in.getGame()) + out = assignNewId ? getCard(in.getPaperCard(), in.getOwner(), in.getGame()) : getCard(in.getPaperCard(), in.getOwner(), in.getId(), in.getGame()); } else { // token out = CardFactory.copyStats(in, in.getController(), assignNewId); out.setToken(true); + // need to copy this values for the tokens + out.setEmbalmed(in.isEmbalmed()); + out.setEternalized(in.isEternalized()); + // add abilities //for (SpellAbility sa : in.getIntrinsicSpellAbilities()) { // out.addSpellAbility(sa); @@ -101,6 +113,7 @@ public class CardFactory { out.addImprintedCard(o); } out.setCommander(in.isCommander()); + //out.setFaceDown(in.isFaceDown()); return out; @@ -110,16 +123,16 @@ public class CardFactory { *

* copyCardWithChangedStats *

- * + * * This method copies the card together with certain temporarily changed stats of the card * (namely, changed color, changed types, changed keywords). - * + * * copyCardWithChangedStats must NOT be used for ordinary card copy operations because * according to MTG rules the changed text (including keywords, types) is not copied over * to cards cloned by another card. However, this method is useful, for example, for certain * triggers that demand the latest information about the changes to the card which is lost * when the card changes its zone after GameAction::changeZone is called. - * + * * @param in * a {@link forge.game.card.Card} object. * @param assignNewId @@ -128,7 +141,7 @@ public class CardFactory { */ public static final Card copyCardWithChangedStats(final Card in, boolean assignNewId) { Card out = copyCard(in, assignNewId); - + // Copy changed color, type, keyword arrays (useful for some triggers that require // information about the latest state of the card as it left the battlefield) out.setChangedCardColors(in.getChangedCardColors()); @@ -197,7 +210,7 @@ public class CardFactory { * creates a copy of the Spell/ability `sa`, and puts it on the stack. * if sa is a spell, that spell's host is also copied. *

- * + * * @param source * a {@link forge.game.card.Card} object. The card doing the copying. * @param original @@ -297,7 +310,7 @@ public class CardFactory { c.setRarity(cp.getRarity()); c.setState(CardStateName.Original, false); } - + return c; } @@ -357,7 +370,7 @@ public class CardFactory { StringBuilder saSB = new StringBuilder(); saSB.append("AB$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | AnyPlayer$ True | ActivationZone$ Command | "); - saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn."); + saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn."); card.setSVar("RolledWalk", "DB$ Planeswalk | Cost$ 0"); Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true); @@ -389,7 +402,7 @@ public class CardFactory { if (st == CardSplitType.Split) { card.addAlternateState(CardStateName.LeftSplit, false); card.setState(CardStateName.LeftSplit, false); - } + } readCardFace(card, rules.getMainPart()); @@ -402,7 +415,7 @@ public class CardFactory { readCardFace(card, StaticData.instance().getCommonCards().getRules(rules.getMeldWith()).getOtherPart()); } } - + if (card.isInAlternateState()) { card.setState(CardStateName.Original, false); } @@ -434,7 +447,7 @@ public class CardFactory { for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true)); for (String s : face.getStaticAbilities()) c.addStaticAbility(s); for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true)); - + for (Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); // keywords not before variables @@ -463,7 +476,7 @@ public class CardFactory { // SpellPermanent only for Original State if (c.getCurrentStateName() == CardStateName.Original) { - // this is the "default" spell for permanents like creatures and artifacts + // this is the "default" spell for permanents like creatures and artifacts if (c.isPermanent() && !c.isAura() && !c.isLand()) { c.addSpellAbility(new SpellPermanent(c)); } @@ -484,7 +497,7 @@ public class CardFactory { final Card c = new Card(id, from.getPaperCard(), from.getGame()); c.setOwner(newOwner); c.setSetCode(from.getSetCode()); - + copyCopiableCharacteristics(from, c); return c; } @@ -492,7 +505,7 @@ public class CardFactory { /** * Copy the copiable characteristics of one card to another, taking the * states of both cards into account. - * + * * @param from the {@link Card} to copy from. * @param to the {@link Card} to copy to. */ @@ -532,10 +545,10 @@ public class CardFactory { * This amounts to making a full copy of the card, including the current * state. *

- * + * * @param in * the {@link forge.game.card.Card} to be copied. - * @param newOwner + * @param newOwner * the {@link forge.game.player.Player} to be the owner of the newly * created Card. * @return a new {@link forge.game.card.Card}. @@ -546,24 +559,24 @@ public class CardFactory { id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); } final Card c = new Card(id, in.getPaperCard(), in.getGame()); - + c.setOwner(newOwner); c.setSetCode(in.getSetCode()); - + for (final CardStateName state : in.getStates()) { CardFactory.copyState(in, state, c, state); } - + c.setState(in.getCurrentStateName(), false); c.setRules(in.getRules()); - + return c; } // copyStats() /** * Copy characteristics of a particular state of one card to those of a * (possibly different) state of another. - * + * * @param from * the {@link Card} to copy from. * @param fromState @@ -600,7 +613,7 @@ public class CardFactory { } to.setDescription(from.getOriginalDescription()); to.setStackDescription(from.getOriginalStackDescription()); - + if (from.getSubAbility() != null) { to.setSubAbility((AbilitySub) from.getSubAbility().copy(host, p, lki)); } @@ -638,7 +651,7 @@ public class CardFactory { /** * Copy triggered ability - * + * * return a wrapped ability */ public static SpellAbility getCopiedTriggeredAbility(final SpellAbility sa) { @@ -687,5 +700,233 @@ public class CardFactory { return wrapperAbility; } + public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) { + final Card host = sa.getHostCard(); + final Map origSVars = host.getSVars(); + final List types = Lists.newArrayList(); + final List keywords = Lists.newArrayList(); + List creatureTypes = null; + final CardCloneStates result = new CardCloneStates(in, sa); + + final String newName = sa.getParamOrDefault("NewName", null); + String shortColors = ""; + + if (sa.hasParam("AddTypes")) { + types.addAll(Arrays.asList(sa.getParam("AddTypes").split(","))); + } + + if (sa.hasParam("AddKeywords")) { + keywords.addAll(Arrays.asList(sa.getParam("AddKeywords").split(" & "))); + } + + if (sa.hasParam("SetColor")) { + shortColors = CardUtil.getShortColorsString(Arrays.asList(sa.getParam("SetColor").split(","))); + } + + if (sa.hasParam("SetCreatureTypes")) { + creatureTypes = ImmutableList.copyOf(sa.getParam("SetCreatureTypes").split(" ")); + } + + // TODO handle Volrath's Shapeshifter + + if (in.isFaceDown()) { + // if something is cloning a facedown card, it only clones the + // facedown state into original + final CardState ret = new CardState(out, CardStateName.Original); + ret.copyFrom(in.getState(CardStateName.FaceDown, true), false); + result.put(CardStateName.Original, ret); + } else if (in.isFlipCard()) { + // if something is cloning a flip card, copy both original and + // flipped state + final CardState ret1 = new CardState(out, CardStateName.Original); + ret1.copyFrom(in.getState(CardStateName.Original, true), false); + result.put(CardStateName.Original, ret1); + + final CardState ret2 = new CardState(out, CardStateName.Flipped); + ret2.copyFrom(in.getState(CardStateName.Flipped, true), false); + result.put(CardStateName.Flipped, ret2); + } else { + // in all other cases just copy the current state to original + final CardState ret = new CardState(out, CardStateName.Original); + ret.copyFrom(in.getState(in.getCurrentStateName(), true), false); + result.put(CardStateName.Original, ret); + } + + // update all states, both for flip cards + for (Map.Entry e : result.entrySet()) { + final CardState originalState = out.getState(e.getKey()); + final CardState state = e.getValue(); + // update the names for the states + if (sa.hasParam("KeepName")) { + state.setName(originalState.getName()); + } else if (newName != null) { + state.setName(newName); + } + + if (sa.hasParam("SetColor")) { + state.setColor(shortColors); + } + + if (sa.hasParam("NonLegendary")) { + state.removeType(CardType.Supertype.Legendary); + } + + for (final String type : types) { + state.addType(type); + } + + if (creatureTypes != null) { + state.setCreatureTypes(creatureTypes); + } + + state.addIntrinsicKeywords(keywords); + + if (sa.hasParam("SetPower")) { + state.setBasePower(Integer.parseInt(sa.getParam("SetPower"))); + } + if (sa.hasParam("SetToughness")) { + state.setBaseToughness(Integer.parseInt(sa.getParam("SetToughness"))); + } + + + // triggers to add to clone + if (sa.hasParam("AddTriggers")) { + for (final String s : Arrays.asList(sa.getParam("AddTriggers").split(","))) { + if (origSVars.containsKey(s)) { + final String actualTrigger = origSVars.get(s); + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true); + state.addTrigger(parsedTrigger); + } + } + } + + // SVars to add to clone + if (sa.hasParam("AddSVars") || sa.hasParam("GainTextSVars")) { + final String str = sa.getParamOrDefault("GainTextSVars", sa.getParam("AddSVars")); + for (final String s : Arrays.asList(str.split(","))) { + if (origSVars.containsKey(s)) { + final String actualsVar = origSVars.get(s); + state.setSVar(s, actualsVar); + } + } + } + + // abilities to add to clone + if (sa.hasParam("AddAbilities") || sa.hasParam("GainTextAbilities")) { + final String str = sa.getParamOrDefault("GainTextAbilities", sa.getParam("AddAbilities")); + for (final String s : Arrays.asList(str.split(","))) { + if (origSVars.containsKey(s)) { + final String actualAbility = origSVars.get(s); + final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, out); + state.addSpellAbility(grantedAbility); + } + } + } + + + if (sa.hasParam("GainThisAbility") && (sa instanceof SpellAbility)) { + SpellAbility root = ((SpellAbility) sa).getRootAbility(); + + if (root.isTrigger() && root.getTrigger() != null) { + state.addTrigger(root.getTrigger().copy(out, false)); + } else if (root.isReplacementAbility()) { + state.addReplacementEffect(root.getReplacementEffect().copy(out, false)); + } else { + state.addSpellAbility(root.copy(out, false)); + } + } + + // Special Rules for Embalm and Eternalize + if (sa.hasParam("Embalm") && out.isEmbalmed()) { + state.addType("Zombie"); + state.setColor(MagicColor.WHITE); + state.setManaCost(ManaCost.NO_COST); + + String name = TextUtil.fastReplace( + TextUtil.fastReplace(host.getName(), ",", ""), + " ", "_").toLowerCase(); + state.setImageKey(ImageKeys.getTokenKey("embalm_" + name)); + } + + if (sa.hasParam("Eternalize") && out.isEternalized()) { + state.addType("Zombie"); + state.setColor(MagicColor.BLACK); + state.setManaCost(ManaCost.NO_COST); + state.setBasePower(4); + state.setBaseToughness(4); + + String name = TextUtil.fastReplace( + TextUtil.fastReplace(host.getName(), ",", ""), + " ", "_").toLowerCase(); + state.setImageKey(ImageKeys.getTokenKey("eternalize_" + name)); + } + + // set the host card for copied replacement effects + // needed for copied xPaid ETB effects (for the copy, xPaid = 0) + for (final ReplacementEffect rep : state.getReplacementEffects()) { + final SpellAbility newSa = rep.getOverridingAbility(); + if (newSa != null && newSa.getOriginalHost() == null) { + newSa.setOriginalHost(in); + } + } + + // set the host card for copied spellabilities, if they are not set yet + for (final SpellAbility newSa : state.getSpellAbilities()) { + if (newSa.getOriginalHost() == null) { + newSa.setOriginalHost(in); + } + } + + if (sa.hasParam("GainTextOf")) { + state.setSetCode(originalState.getSetCode()); + state.setRarity(originalState.getRarity()); + state.setImageKey(originalState.getImageKey()); + } + + // remove some characteristic static abilties + for (StaticAbility sta : state.getStaticAbilities()) { + if (!sta.hasParam("CharacteristicDefining")) { + continue; + } + + if (sa.hasParam("SetPower") || sa.hasParam("Eternalize")) { + if (sta.hasParam("SetPower")) + state.removeStaticAbility(sta); + } + if (sa.hasParam("SetToughness") || sa.hasParam("Eternalize")) { + if (sta.hasParam("SetToughness")) + state.removeStaticAbility(sta); + } + if (sa.hasParam("SetCreatureTypes")) { + // currently only Changeling and similar should be affected by that + // other cards using AddType$ ChosenType should not + if (sta.hasParam("AddType") && "AllCreatureTypes".equals(sta.getParam("AddType"))) { + state.removeStaticAbility(sta); + } + } + if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize")) { + if (sta.hasParam("SetColor")) { + state.removeStaticAbility(sta); + } + } + } + + // remove some keywords + if (sa.hasParam("SetCreatureTypes")) { + state.removeIntrinsicKeyword("Changeling"); + } + if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize")) { + state.removeIntrinsicKeyword("Devoid"); + } + + for (SpellAbility ab : state.getSpellAbilities()) { + ab.getRestrictions().resetTurnActivations(); + } + } + + // Dont copy the facedown state, make new one + result.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(out)); + return result; + } } // end class AbstractCardFactory 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 92febed99fc..3d69ca554b3 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -101,16 +101,18 @@ public class CardFactoryUtil { @Override public void resolve() { - Card c = hostCard.getGame().getAction().moveToPlay(hostCard, this); - c.setPreFaceDownState(CardStateName.Original); + hostCard.getGame().getAction().moveToPlay(hostCard, this); + //c.setPreFaceDownState(CardStateName.Original); } @Override public boolean canPlay() { CardStateName stateBackup = hostCard.getCurrentStateName(); - hostCard.setState(CardStateName.FaceDown, false); + boolean face = hostCard.isFaceDown(); + hostCard.turnFaceDownNoUpdate(); boolean success = super.canPlay(); hostCard.setState(stateBackup, false); + hostCard.setFaceDown(face); return success; } }; diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index ae4f942ff2b..fad721c5487 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -526,7 +526,12 @@ public class CardState extends GameObject { } } - + public CardState copy(final Card host, CardStateName name, final boolean lki) { + CardState result = new CardState(host, name); + result.copyFrom(this, lki); + return result; + } + public CardRarity getRarity() { return rarity; } diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 972e9e2ec30..5c76488ec6c 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -227,12 +227,17 @@ public final class CardUtil { // used for the purpose of cards that care about the zone the card was known to be in last newCopy.setLastKnownZone(in.getLastKnownZone()); - newCopy.getCurrentState().copyFrom(in.getState(in.getCurrentStateName()), true); + newCopy.getCurrentState().copyFrom(in.getState(in.getFaceupCardStateName()), true); + if (in.isFaceDown()) { + newCopy.turnFaceDownNoUpdate(); + } + /* if (in.isCloned()) { newCopy.addAlternateState(CardStateName.Cloner, false); newCopy.getState(CardStateName.Cloner).copyFrom(in.getState(CardStateName.Cloner), true); } + //*/ newCopy.setType(new CardType(in.getType())); newCopy.setToken(in.isToken()); @@ -327,7 +332,7 @@ public final class CardUtil { final CardType type = new CardType(); type.add("Creature"); - final CardState ret = new CardState(c.getView().createAlternateState(CardStateName.FaceDown), c); + final CardState ret = new CardState(c, CardStateName.FaceDown); ret.setBasePower(2); ret.setBaseToughness(2); diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 4da63486f80..7120fa84372 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -113,7 +113,7 @@ public class CardView extends GameEntityView { } public boolean isFaceDown() { - return getCurrentState().getState() == CardStateName.FaceDown; + return get(TrackableProperty.Facedown);// getCurrentState().getState() == CardStateName.FaceDown; } public boolean isFlipCard() { @@ -121,16 +121,18 @@ public class CardView extends GameEntityView { } public boolean isFlipped() { - return getCurrentState().getState() == CardStateName.Flipped; + return get(TrackableProperty.Flipped); // getCurrentState().getState() == CardStateName.Flipped; } public boolean isSplitCard() { return get(TrackableProperty.SplitCard); } + /* public boolean isTransformed() { return getCurrentState().getState() == CardStateName.Transformed; } + //*/ public boolean isAttacking() { return get(TrackableProperty.Attacking); @@ -627,7 +629,9 @@ public class CardView extends GameEntityView { set(TrackableProperty.SplitCard, isSplitCard); set(TrackableProperty.FlipCard, c.isFlipCard()); - CardStateView cloner = CardView.getState(c, CardStateName.Cloner); + final Card cloner = c.getCloner(); + + //CardStateView cloner = CardView.getState(c, CardStateName.Cloner); set(TrackableProperty.Cloner, cloner == null ? null : cloner.getName() + " (" + cloner.getId() + ")"); CardState currentState = c.getCurrentState(); @@ -699,7 +703,7 @@ public class CardView extends GameEntityView { if (name.isEmpty()) { CardStateView alternate = getAlternateState(); if (alternate != null) { - if (this.getCurrentState().getState() == CardStateName.FaceDown) { + if (isFaceDown()) { return "Face-down card (H" + getHiddenId() + ")"; } else { return getAlternateState().getName() + " (" + getId() + ")"; diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index b1d671b8660..f28a7e2a768 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -42,7 +42,7 @@ public class CostAdjustment { boolean isStateChangeToFaceDown = false; if (sa.isSpell() && ((Spell) sa).isCastFaceDown()) { // Turn face down to apply cost modifiers correctly - host.setState(CardStateName.FaceDown, false); + host.turnFaceDownNoUpdate(); isStateChangeToFaceDown = true; } // isSpell @@ -83,6 +83,7 @@ public class CostAdjustment { // Reset card state (if changed) if (isStateChangeToFaceDown) { host.setState(CardStateName.Original, false); + host.setFaceDown(false); } return result; } diff --git a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java b/forge-game/src/main/java/forge/game/spellability/TargetChoices.java index a3514ff75b3..6391ab5b509 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetChoices.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetChoices.java @@ -151,6 +151,11 @@ public class TargetChoices implements Cloneable { return sb.toString(); } + @Override + public final String toString() { + return this.getTargetedString(); + } + public final boolean isTargetingAnyCard() { return !targetCards.isEmpty(); } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index 517f1f7b0db..ad5f8602410 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -19,7 +19,6 @@ package forge.game.staticability; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import forge.card.CardStateName; import forge.card.MagicColor; import forge.game.CardTraitBase; import forge.game.Game; @@ -200,23 +199,6 @@ public class StaticAbility extends CardTraitBase implements Comparable saToRemove = Lists.newArrayList(); - for (SpellAbility saTemp : affectedCard.getSpellAbilities()) { - if (saTemp.isTemporary()) { - saToRemove.add(saTemp); - } - } - for (SpellAbility saRem : saToRemove) { - affectedCard.removeSpellAbility(saRem); - } - CardFactory.copyState(affectedCard, CardStateName.OriginalText, affectedCard, CardStateName.Original, false); - } - - // TODO: find a better way to ascertain that the card will essentially try to copy its exact duplicate - // (e.g. Volrath's Shapeshifter copying the text of another pristine Volrath's Shapeshifter), since the - // check by name may fail in case one of the cards is modified in some way while the other is not - // (probably not very relevant for Volrath's Shapeshifter itself since it copies text on cards in GY). - if (gainTextSource != null && !gainTextSource.getCurrentState().getName().equals(affectedCard.getCurrentState().getName())) { - if (!affectedCard.getStates().contains(CardStateName.OriginalText)) { - // Remember the original text first in case it hasn't been done yet - CardFactory.copyState(affectedCard, CardStateName.Original, affectedCard, CardStateName.OriginalText, false); - } - - CardFactory.copyState(gainTextSource, CardStateName.Original, affectedCard, CardStateName.Original, false); - - // Do not clone the set code and rarity from the target card - affectedCard.getState(CardStateName.Original).setSetCode(affectedCard.getState(CardStateName.OriginalText).getSetCode()); - affectedCard.getState(CardStateName.Original).setRarity(affectedCard.getState(CardStateName.OriginalText).getRarity()); - - // Enable this in case Volrath's original image is to be used - affectedCard.getState(CardStateName.Original).setImageKey(affectedCard.getState(CardStateName.OriginalText).getImageKey()); - - // Volrath's Shapeshifter shapeshifting ability needs to be added onto the new text - if (params.containsKey("GainedTextHasThisStaticAbility")) { - affectedCard.getCurrentState().addStaticAbility(stAb); - } - - // Add the ability "{2}: Discard a card" for Volrath's Shapeshifter - // TODO: Make this generic so that other SAs can be added onto custom cards if need be - if (params.containsKey("GainVolrathsDiscardAbility")) { - String abDiscard = "AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card."; - SpellAbility ab = AbilityFactory.getAbility(abDiscard, affectedCard); - affectedCard.addSpellAbility(ab); - } - - // Remember the name and the timestamp of the card we're gaining text from, so we don't modify - // the card too aggressively when unnecessary - affectedCard.setSVar("GainingTextFrom", String.valueOf(gainTextSource.getName())); - affectedCard.setSVar("GainingTextFromTimestamp", String.valueOf(gainTextSource.getTimestamp())); - } + if (gainTextSource != null) { + affectedCard.addTextChangeState( + CardFactory.getCloneStates(gainTextSource, affectedCard, stAb), se.getTimestamp() + ); } } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityLayer.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityLayer.java index 606ca597fbe..290b220c2b8 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityLayer.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityLayer.java @@ -3,6 +3,8 @@ package forge.game.staticability; import com.google.common.collect.ImmutableList; public enum StaticAbilityLayer { + /** Layer 1 for control-changing effects. */ + COPY, /** Layer 2 for control-changing effects. */ CONTROL, @@ -35,5 +37,5 @@ public enum StaticAbilityLayer { RULES; public final static ImmutableList CONTINUOUS_LAYERS = - ImmutableList.of(CONTROL, TEXT, TYPE, COLOR, ABILITIES1, ABILITIES2, CHARACTERISTIC, SETPT, MODIFYPT, RULES); + ImmutableList.of(COPY, CONTROL, TEXT, TYPE, COLOR, ABILITIES1, ABILITIES2, CHARACTERISTIC, SETPT, MODIFYPT, RULES); } diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index e4ce43685cd..016ab697d17 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -232,7 +232,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable implements IHasGam if (parentPath != null) { fullPath = parentPath + key.toString(); } - String finalFullPath = fullPath; + final String finalFullPath = fullPath; GuiUtils.addMenuItem(menu, key.toString(), null, new Runnable() { @Override public void run() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java index 40a447be764..57684af6622 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VStack.java @@ -17,28 +17,9 @@ */ package forge.screens.match.views; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; - -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.ScrollPaneConstants; -import javax.swing.SwingUtilities; -import javax.swing.border.EmptyBorder; - -import net.miginfocom.swing.MigLayout; import forge.CachedCardImage; import forge.card.CardDetailUtil; import forge.card.CardDetailUtil.DetailColors; -import forge.card.CardStateName; import forge.game.GameView; import forge.game.card.CardView.CardStateView; import forge.game.spellability.StackItemView; @@ -46,13 +27,23 @@ import forge.gui.framework.DragCell; import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; import forge.gui.framework.IVDoc; -import forge.screens.match.controllers.CStack; import forge.screens.match.controllers.CDock.ArcState; +import forge.screens.match.controllers.CStack; import forge.toolbox.FMouseAdapter; import forge.toolbox.FScrollPanel; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinnedTextArea; import forge.util.collect.FCollectionView; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; /** * Assembles Swing components of stack report. @@ -257,7 +248,7 @@ public class VStack implements IVDoc { // TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards. final CardStateView curState = item.getSourceCard().getCurrentState(); - final boolean isFaceDown = curState.getState() == CardStateName.FaceDown; + final boolean isFaceDown = item.getSourceCard().isFaceDown(); final DetailColors color = isFaceDown ? CardDetailUtil.DetailColors.FACE_DOWN : CardDetailUtil.getBorderColor(curState, true); // otherwise doesn't work correctly for face down Morphs setBackground(new Color(color.r, color.g, color.b)); setForeground(FSkin.getHighContrastColor(getBackground())); diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java index 0d15d62f5f4..63f48d0e7f8 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java @@ -5,6 +5,7 @@ import forge.ai.ComputerUtilAbility; import forge.card.CardStateName; import forge.card.MagicColor; import forge.game.Game; +import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CounterType; @@ -173,7 +174,7 @@ public class GameSimulatorTest extends SimulationTestCase { Game game = initAndCreateGame(); Player p = game.getPlayers().get(1); Card ripper = createCard("Ruthless Ripper", p); - ripper.setState(CardStateName.FaceDown, true); + ripper.turnFaceDownNoUpdate(); p.getZone(ZoneType.Battlefield).add(ripper); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); @@ -1696,4 +1697,238 @@ public class GameSimulatorTest extends SimulationTestCase { // One cards drawn assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 1); } + + + public void testCloneTransform() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + Player p2 = game.getPlayers().get(1); + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + + final String outLawName = "Kruin Outlaw"; + final String hillGiantName = "Elite Vanguard"; + final String terrorName = "Terror of Kruin Pass"; + + Card outlaw = addCard(outLawName, p2); + Card giant = addCard(hillGiantName, p); + + assertFalse(outlaw.isCloned()); + assertTrue(outlaw.isDoubleFaced()); + assertTrue(outlaw.hasState(CardStateName.Transformed)); + assertTrue(outlaw.canTransform()); + assertFalse(outlaw.isBackSide()); + + assertFalse(giant.isDoubleFaced()); + assertFalse(giant.canTransform()); + + addCard("Forest", p); + addCard("Forest", p); + addCard("Forest", p); + addCard("Forest", p); + addCard("Island", p); + + Card cytoCard = addCardToZone("Cytoshape", p, ZoneType.Hand); + SpellAbility cytoSA = cytoCard.getFirstSpellAbility(); + + Card moonmist = addCardToZone("Moonmist", p, ZoneType.Hand); + SpellAbility moonmistSA = moonmist.getFirstSpellAbility(); + + cytoSA.getTargets().add(outlaw); + + GameSimulator sim = createSimulator(game, p); + int score = sim.simulateSpellAbility(cytoSA).value; + + assertTrue(score > 0); + + Game simGame = sim.getSimulatedGameState(); + + assertTrue(countCardsWithName(simGame, outLawName) == 0); + assertTrue(countCardsWithName(simGame, hillGiantName) == 2); + assertTrue(countCardsWithName(simGame, terrorName) == 0); + + Card clonedOutLaw = (Card)sim.getGameCopier().find(outlaw); + + assertTrue(clonedOutLaw.isCloned()); + assertTrue(clonedOutLaw.isDoubleFaced()); + assertFalse(clonedOutLaw.hasState(CardStateName.Transformed)); + assertTrue(clonedOutLaw.canTransform()); + assertFalse(clonedOutLaw.isBackSide()); + + assertTrue(clonedOutLaw.getName().equals(hillGiantName)); + + assertTrue(clonedOutLaw.isDoubleFaced()); + + score = sim.simulateSpellAbility(moonmistSA).value; + assertTrue(score > 0); + + simGame = sim.getSimulatedGameState(); + + assertTrue(countCardsWithName(simGame, outLawName) == 0); + assertTrue(countCardsWithName(simGame, hillGiantName) == 2); + assertTrue(countCardsWithName(simGame, terrorName) == 0); + + Card transformOutLaw = (Card)sim.getGameCopier().find(outlaw); + + assertTrue(transformOutLaw.isCloned()); + assertTrue(transformOutLaw.isDoubleFaced()); + assertTrue(transformOutLaw.hasState(CardStateName.Transformed)); + assertTrue(transformOutLaw.canTransform()); + assertTrue(transformOutLaw.isBackSide()); + + assertTrue(transformOutLaw.getName().equals(hillGiantName)); + + // need to clean up the clone state + simGame.getPhaseHandler().devAdvanceToPhase(PhaseType.CLEANUP); + + assertTrue(countCardsWithName(simGame, outLawName) == 0); + assertTrue(countCardsWithName(simGame, hillGiantName) == 1); + assertTrue(countCardsWithName(simGame, terrorName) == 1); + + assertFalse(transformOutLaw.isCloned()); + assertTrue(transformOutLaw.isDoubleFaced()); + assertTrue(transformOutLaw.hasState(CardStateName.Transformed)); + assertTrue(transformOutLaw.canTransform()); + assertTrue(transformOutLaw.isBackSide()); + + assertTrue(transformOutLaw.getName().equals(terrorName)); + } + + public void testVolrathsShapeshifter() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + + Card volrath = addCard("Volrath's Shapeshifter", p); + + // 1. Assert that Volrath has the Discard ability + SpellAbility discard = findSAWithPrefix(volrath, "{2}"); + assertTrue(discard != null && discard.getApi() == ApiType.Discard); + + // 2. Copy the text from a creature + addCardToZone("Abattoir Ghoul", p, ZoneType.Graveyard); + game.getAction().checkStateEffects(true); + + assertTrue(volrath.getName().equals("Abattoir Ghoul")); + assertTrue(volrath.getNetPower() == 3); + assertTrue(volrath.getNetToughness() == 2); + assertTrue(volrath.hasKeyword(Keyword.FIRST_STRIKE)); + + SpellAbility discardAfterCopy = findSAWithPrefix(volrath, "{2}"); + assertTrue(discardAfterCopy != null && discardAfterCopy.getApi() == ApiType.Discard); + + // 3. Revert back to not copying any text + addCardToZone("Plains", p, ZoneType.Graveyard); + game.getAction().checkStateEffects(true); + + assertTrue(volrath.getName().equals("Volrath's Shapeshifter")); + assertTrue(volrath.getNetPower() == 0); + assertTrue(volrath.getNetToughness() == 1); + assertTrue(volrath.getKeywords().isEmpty()); + + SpellAbility discardAfterRevert = findSAWithPrefix(volrath, "{2}"); + assertTrue(discardAfterRevert != null && discardAfterRevert.getApi() == ApiType.Discard); + } + + + @SuppressWarnings("unused") + public void broken_testCloneDimir() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + + // add enough cards to hand to flip Jushi + for (int i = 0; i < 9; i++) { + addCardToZone("Plains", p, ZoneType.Hand); + addCardToZone("Plains", p, ZoneType.Library); + addCard("Swamp", p); + addCard("Island", p); + } + + Card dimirdg = addCard("Dimir Doppelganger", p); + // so T can be paid + dimirdg.setSickness(false); + SpellAbility saDimirClone = findSAWithPrefix(dimirdg, "{1}{U}{B}"); + + assertTrue(saDimirClone != null && saDimirClone.getApi() == ApiType.ChangeZone); + + Card jushi = addCardToZone("Jushi Apprentice", p, ZoneType.Graveyard); + Card bear = addCardToZone("Runeclaw Bear", p, ZoneType.Graveyard); + Card nezumi = addCardToZone("Nezumi Shortfang", p, ZoneType.Graveyard); + + // Clone Jushi first + saDimirClone.getTargets().add(jushi); + GameSimulator sim = createSimulator(game, p); + int score = sim.simulateSpellAbility(saDimirClone).value; + assertTrue(score > 0); + + Card dimirdgAfterCopy1 = (Card)sim.getGameCopier().find(dimirdg); + assertTrue(dimirdgAfterCopy1.getName().equals("Jushi Apprentice")); + assertTrue(dimirdgAfterCopy1.getNetPower() == 1); + assertTrue(dimirdgAfterCopy1.getNetToughness() == 2); + assertTrue(dimirdgAfterCopy1.isFlipCard()); + assertFalse(dimirdgAfterCopy1.isFlipped()); + assertFalse(dimirdgAfterCopy1.getType().isLegendary()); + + bear = (Card)sim.getGameCopier().find(bear); + + // make new simulator so new SpellAbility is found + Game simGame = sim.getSimulatedGameState(); + sim = createSimulator(simGame, p); + + Player copiedPlayer = (Player)sim.getGameCopier().find(p); + int handSize = copiedPlayer.getCardsIn(ZoneType.Hand).size(); + assertTrue(handSize == 9); + + SpellAbility draw = findSAWithPrefix(dimirdgAfterCopy1, "{2}{U}"); + score = sim.simulateSpellAbility(draw).value; + assertTrue(score > 0); + + copiedPlayer = (Player)sim.getGameCopier().find(p); + handSize = copiedPlayer.getCardsIn(ZoneType.Hand).size(); + assertTrue(handSize == 10); + + simGame = sim.getSimulatedGameState(); + + bear = (Card)sim.getGameCopier().find(bear); + + // make new simulator so new SpellAbility is found + simGame = sim.getSimulatedGameState(); + sim = createSimulator(simGame, p); + + //bear = (Card)sim.getGameCopier().find(bear); + + simGame = sim.getSimulatedGameState(); + + Card dimirdgAfterFlip1 = (Card)sim.getGameCopier().find(dimirdgAfterCopy1); + + assertTrue(dimirdgAfterFlip1.getName().equals("Tomoya the Revealer")); + assertTrue(dimirdgAfterFlip1.getNetPower() == 2); + assertTrue(dimirdgAfterFlip1.getNetToughness() == 3); + assertTrue(dimirdgAfterFlip1.isFlipped()); + assertTrue(dimirdgAfterFlip1.getType().isLegendary()); + + saDimirClone = findSAWithPrefix(dimirdgAfterCopy1, "{1}{U}{B}"); + // Clone Bear first + saDimirClone.resetTargets(); + saDimirClone.getTargets().add(bear); + + score = sim.simulateSpellAbility(saDimirClone).value; + assertTrue(score > 0); + + Card dimirdgAfterCopy2 = (Card)sim.getGameCopier().find(dimirdgAfterCopy1); + + //System.out.println(sim.getSimulatedGameState().getCardsIn(ZoneType.Battlefield)); + + System.out.println(dimirdgAfterCopy2.getName()); + System.out.println(dimirdgAfterCopy2.getCloneStates()); + System.out.println(dimirdgAfterCopy2.getOriginalState(CardStateName.Original).getName()); + System.out.println(dimirdgAfterCopy2.isFlipCard()); + System.out.println(dimirdgAfterCopy2.isFlipped()); + + assertTrue(dimirdgAfterCopy2.getName().equals("Runeclaw Bear")); + assertTrue(dimirdgAfterCopy2.getNetPower() == 2); + assertTrue(dimirdgAfterCopy2.getNetToughness() == 2); + assertTrue(dimirdgAfterCopy2.isFlipped()); + assertFalse(dimirdgAfterCopy2.getType().isLegendary()); + } } diff --git a/forge-gui-mobile/src/forge/card/CardImageRenderer.java b/forge-gui-mobile/src/forge/card/CardImageRenderer.java index ed41f40d96c..19d47ce7057 100644 --- a/forge-gui-mobile/src/forge/card/CardImageRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardImageRenderer.java @@ -85,7 +85,7 @@ public class CardImageRenderer { //determine colors for borders final List borderColors; - final boolean isFaceDown = card.getCurrentState().getState() == CardStateName.FaceDown; + final boolean isFaceDown = card.isFaceDown(); if (isFaceDown) { borderColors = ImmutableList.of(DetailColors.FACE_DOWN); } @@ -371,7 +371,7 @@ public class CardImageRenderer { //determine colors for borders final List borderColors; - final boolean isFaceDown = card.getCurrentState().getState() == CardStateName.FaceDown; + final boolean isFaceDown = card.isFaceDown(); if (isFaceDown) { borderColors = ImmutableList.of(DetailColors.FACE_DOWN); } diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index 5e34e757ca9..c393b17b522 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -213,7 +213,7 @@ public class CardRenderer { return cardArt; } - public static FImageComplex getAftermathSecondCardArt(String imageKey) { + public static FImageComplex getAftermathSecondCardArt(final String imageKey) { FImageComplex cardArt = cardArtCache.get("Aftermath_second_"+imageKey); if (cardArt == null) { Texture image = new CachedCardImage(imageKey) { @@ -439,7 +439,7 @@ public class CardRenderer { // TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards. final CardStateView details = card.getCurrentState(); - final boolean isFaceDown = details.getState() == CardStateName.FaceDown; + final boolean isFaceDown = card.isFaceDown(); final DetailColors borderColor = isFaceDown ? CardDetailUtil.DetailColors.FACE_DOWN : CardDetailUtil.getBorderColor(details, canShow); // canShow doesn't work here for face down Morphs Color color = FSkinColor.fromRGB(borderColor.r, borderColor.g, borderColor.b); color = FSkinColor.tintColor(Color.WHITE, color, CardRenderer.PT_BOX_TINT); diff --git a/forge-gui-mobile/src/forge/screens/match/views/VStack.java b/forge-gui-mobile/src/forge/screens/match/views/VStack.java index 8e73b88a83f..675f23716df 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VStack.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VStack.java @@ -19,10 +19,8 @@ import forge.card.CardRenderer; import forge.card.CardZoom; import forge.card.CardDetailUtil.DetailColors; import forge.card.CardRenderer.CardStackPosition; -import forge.card.CardStateName; import forge.game.GameView; import forge.game.card.CardView; -import forge.game.card.CardView.CardStateView; import forge.game.player.PlayerView; import forge.game.spellability.StackItemView; import forge.game.zone.ZoneType; @@ -242,8 +240,7 @@ public class VStack extends FDropDown { } // TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards. - final CardStateView curState = card.getCurrentState(); - final boolean isFaceDown = curState.getState() == CardStateName.FaceDown; + final boolean isFaceDown = card.isFaceDown(); final DetailColors color = isFaceDown ? CardDetailUtil.DetailColors.FACE_DOWN : CardDetailUtil.getBorderColor(card.getCurrentState(), true); // otherwise doesn't work correctly for face down Morphs backColor = FSkinColor.fromRGB(color.r, color.g, color.b); foreColor = FSkinColor.getHighContrastColor(backColor); diff --git a/forge-gui/res/cardsfolder/c/cytoshape.txt b/forge-gui/res/cardsfolder/c/cytoshape.txt index 68b6a7f92d6..ffc4844319b 100644 --- a/forge-gui/res/cardsfolder/c/cytoshape.txt +++ b/forge-gui/res/cardsfolder/c/cytoshape.txt @@ -1,9 +1,7 @@ Name:Cytoshape ManaCost:1 G U Types:Instant -A:SP$ ChooseCard | Cost$ 1 G U | Defined$ You | Amount$ 1 | Choices$ Creature.nonLegendary | Mandatory$ True | SubAbility$ Pump4Tgt | RememberChosen$ True | AILogic$ Clone | SpellDescription$ Choose a nonlegendary creature on the battlefield. Target creature becomes a copy of that creature until end of turn. -SVar:Pump4Tgt:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Choose target creature | AILogic$ Pump | SubAbility$ ShapeTgt | StackDescription$ None -SVar:ShapeTgt:DB$ Clone | Defined$ Remembered | CloneTarget$ ParentTarget | Duration$ UntilEndOfTurn +A:SP$ Clone | Cost$ 1 G U | Choices$ Creature.nonLegendary | ChoiceTitle$ Choose a nonlegendary creature to copy | ValidTgts$ Creature | TgtPrompt$ Choose target creature to become a copy | Duration$ UntilEndOfTurn | StackDescription$ SpellDescription | SpellDescription$ Choose a nonlegendary creature on the battlefield. Target creature becomes a copy of that creature until end of turn. AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/cytoshape.jpg Oracle:Choose a nonlegendary creature on the battlefield. Target creature becomes a copy of that creature until end of turn. diff --git a/forge-gui/res/cardsfolder/e/evil_twin.txt b/forge-gui/res/cardsfolder/e/evil_twin.txt index bf5228f1a30..683191e7ed6 100644 --- a/forge-gui/res/cardsfolder/e/evil_twin.txt +++ b/forge-gui/res/cardsfolder/e/evil_twin.txt @@ -3,9 +3,8 @@ ManaCost:2 U B Types:Creature Shapeshifter PT:0/0 # Make Svars for granting abilities and triggers on clones distinct to avoid SVars getting overwritten when cloning a clone -K:ETBReplacement:Copy:ChooseCreature:Optional -SVar:ChooseCreature:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Creature.Other | SubAbility$ DBCopy | RememberChosen$ True | AILogic$ AtLeast1 | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any creature on the battlefield, except it gains "{U}{B}, {T}: Destroy target creature with the same name as this creature." -SVar:DBCopy:DB$ Clone | Defined$ Remembered | AddAbilities$ EvilTwin +K:ETBReplacement:Copy:DBCopy:Optional +SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | AddAbilities$ EvilTwin | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any creature on the battlefield, except it gains "{U}{B}, {T}: Destroy target creature with the same name as this creature." SVar:EvilTwin:AB$Destroy | Cost$ U B T | ValidTgts$ Creature.sameName | TgtPrompt$ Select target creature with the same name. | SpellDescription$ Destroy target creature with the same name as this creature. SVar:Picture:http://www.wizards.com/global/images/magic/general/evil_twin.jpg Oracle:You may have Evil Twin enter the battlefield as a copy of any creature on the battlefield, except it gains "{U}{B}, {T}: Destroy target creature with the same name as this creature." diff --git a/forge-gui/res/cardsfolder/h/heat_shimmer.txt b/forge-gui/res/cardsfolder/h/heat_shimmer.txt index e32258cfd2c..5f3aff8e3ec 100644 --- a/forge-gui/res/cardsfolder/h/heat_shimmer.txt +++ b/forge-gui/res/cardsfolder/h/heat_shimmer.txt @@ -1,6 +1,6 @@ Name:Heat Shimmer ManaCost:2 R Types:Sorcery -A:SP$ CopyPermanent | Cost$ 2 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | Keywords$ Haste | AtEOTTrig$ Exile | SpellDescription$ Create a token that's a copy of target creature. That token has haste and "At the beginning of the end step, exile this permanent." +A:SP$ CopyPermanent | Cost$ 2 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | AddKeywords$ Haste | AtEOTTrig$ Exile | SpellDescription$ Create a token that's a copy of target creature. That token has haste and "At the beginning of the end step, exile this permanent." SVar:Picture:http://www.wizards.com/global/images/magic/general/heat_shimmer.jpg Oracle:Create a token that's a copy of target creature. That token has haste and "At the beginning of the end step, exile this permanent." diff --git a/forge-gui/res/cardsfolder/h/helm_of_the_host.txt b/forge-gui/res/cardsfolder/h/helm_of_the_host.txt index b4bfcde0f91..5a8fb1941f3 100644 --- a/forge-gui/res/cardsfolder/h/helm_of_the_host.txt +++ b/forge-gui/res/cardsfolder/h/helm_of_the_host.txt @@ -3,7 +3,7 @@ ManaCost:4 Types:Legendary Artifact Equipment K:Equip:5 T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, create a token that's a copy of equipped creature, except the token isn't legendary if equipped creature is legendary. That token gains haste. -SVar:TrigCopy:DB$ CopyPermanent | Defined$ Equipped | Keywords$ Haste | NonLegendary$ True +SVar:TrigCopy:DB$ CopyPermanent | Defined$ Equipped | AddKeywords$ Haste | NonLegendary$ True DeckHas:Ability$Token SVar:Picture:http://www.wizards.com/global/images/magic/general/helm_of_the_host.jpg Oracle:At the beginning of combat on your turn, create a token that's a copy of equipped creature, except the token isn't legendary if equipped creature is legendary. That token gains haste.\nEquip {5} diff --git a/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt b/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt index 9a51bb0461f..e2102a880df 100644 --- a/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt +++ b/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt @@ -3,6 +3,6 @@ ManaCost:2 R R R Types:Legendary Creature Goblin Shaman PT:2/2 K:Haste -A:AB$ CopyPermanent | Cost$ T | ValidTgts$ Creature.nonLegendary+YouCtrl | TgtPrompt$ Select target nonlegendary creature you control | Keywords$ Haste | AtEOT$ Sacrifice | AILogic$ BeforeCombat | SpellDescription$ Create a token that's a copy of target nonlegendary creature you control. That token has haste. Sacrifice it at the beginning of the next end step. +A:AB$ CopyPermanent | Cost$ T | ValidTgts$ Creature.nonLegendary+YouCtrl | TgtPrompt$ Select target nonlegendary creature you control | AddKeywords$ Haste | AtEOT$ Sacrifice | AILogic$ BeforeCombat | SpellDescription$ Create a token that's a copy of target nonlegendary creature you control. That token has haste. Sacrifice it at the beginning of the next end step. SVar:Picture:http://resources.wizards.com/magic/cards/chk/en-us/card50321.jpg Oracle:Haste\n{T}: Create a token that's a copy of target nonlegendary creature you control. That token has haste. Sacrifice it at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/k/kindred_charge.txt b/forge-gui/res/cardsfolder/k/kindred_charge.txt index 4340bce64b5..d1f0c78e657 100644 --- a/forge-gui/res/cardsfolder/k/kindred_charge.txt +++ b/forge-gui/res/cardsfolder/k/kindred_charge.txt @@ -3,6 +3,6 @@ ManaCost:4 R R Types:Sorcery A:SP$ ChooseType | Cost$ 4 R R | Defined$ You | Type$ Creature | SubAbility$ DBRepeatEach | AILogic$ MostProminentComputerControls | SpellDescription$ Choose a creature type. For each creature you control of the chosen type, create a token that's a copy of that creature. Those tokens gain haste. Exile them at the beginning of the next end step. SVar:DBRepeatEach:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Creature.ChosenType+YouCtrl | Zone$ Battlefield | RepeatSubAbility$ DBClone -SVar:DBClone:DB$ CopyPermanent | Defined$ Imprinted | Keywords$ Haste | NumCopies$ 1 | AtEOT$ Exile +SVar:DBClone:DB$ CopyPermanent | Defined$ Imprinted | AddKeywords$ Haste | NumCopies$ 1 | AtEOT$ Exile SVar:Picture:http://www.wizards.com/global/images/magic/general/kindred_charge.jpg Oracle:Choose a creature type. For each creature you control of the chosen type, create a token that's a copy of that creature. Those tokens gain haste. Exile them at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/m/minion_reflector.txt b/forge-gui/res/cardsfolder/m/minion_reflector.txt index 3cb7dc87829..f739e01c834 100644 --- a/forge-gui/res/cardsfolder/m/minion_reflector.txt +++ b/forge-gui/res/cardsfolder/m/minion_reflector.txt @@ -2,7 +2,7 @@ Name:Minion Reflector ManaCost:5 Types:Artifact T:Mode$ ChangesZone | ValidCard$ Creature.nonToken+YouCtrl | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigCopy | OptionalDecider$ You | TriggerDescription$ Whenever a nontoken creature enters the battlefield under your control, you may pay {2}. If you do, create a token that's a copy of that creature. That token has haste and "At the beginning of the end step, sacrifice this permanent." -SVar:TrigCopy:AB$ CopyPermanent | Cost$ 2 | Defined$ TriggeredCard | Keywords$ Haste | AtEOTTrig$ Sacrifice +SVar:TrigCopy:AB$ CopyPermanent | Cost$ 2 | Defined$ TriggeredCard | AddKeywords$ Haste | AtEOTTrig$ Sacrifice SVar:BuffedBy:Creature SVar:Picture:http://www.wizards.com/global/images/magic/general/minion_reflector.jpg Oracle:Whenever a nontoken creature enters the battlefield under your control, you may pay {2}. If you do, create a token that's a copy of that creature. That token has haste and "At the beginning of the end step, sacrifice this permanent." diff --git a/forge-gui/res/cardsfolder/s/saheeli_the_gifted.txt b/forge-gui/res/cardsfolder/s/saheeli_the_gifted.txt index 8073eccc48a..7d7d1ba19c1 100644 --- a/forge-gui/res/cardsfolder/s/saheeli_the_gifted.txt +++ b/forge-gui/res/cardsfolder/s/saheeli_the_gifted.txt @@ -10,6 +10,6 @@ SVar:RemoveEffect:DB$ ChangeZone | Origin$ Command | Destination$ Exile SVar:X:Count$Valid Artifact.YouCtrl A:AB$ RepeatEach | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Ultimate$ True | RepeatSubAbility$ DBClone | RepeatCards$ Artifact.YouCtrl | SpellDescription$ For each artifact you control, create a token that's a copy of it. Those tokens gain haste. Exile those tokens at the beginning of the next end step. -SVar:DBClone:DB$ CopyPermanent | Defined$ Remembered | Keywords$ Haste | AtEOT$ Exile +SVar:DBClone:DB$ CopyPermanent | Defined$ Remembered | AddKeywords$ Haste | AtEOT$ Exile K:CARDNAME can be your commander. Oracle:[+1]: Create a 1/1 colorless Servo artifact creature token.\n[+1]: The next spell you cast this turn costs {1} less to cast for each artifact you control as you cast it.\n[-7]: For each artifact you control, create a token that's a copy of it. Those tokens gain haste. Exile those tokens at the beginning of the next end step.\nSaheeli, the Gifted can be your commander. diff --git a/forge-gui/res/cardsfolder/s/soul_separator.txt b/forge-gui/res/cardsfolder/s/soul_separator.txt index fd1ea72b844..31189c6281f 100644 --- a/forge-gui/res/cardsfolder/s/soul_separator.txt +++ b/forge-gui/res/cardsfolder/s/soul_separator.txt @@ -2,8 +2,8 @@ Name:Soul Separator ManaCost:3 Types:Artifact A:AB$ ChangeZone | Cost$ 5 T Sac<1/CARDNAME> | RememberLKI$ True | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Creature.YouCtrl | ChangeNum$ 1 | SubAbility$ DBCopy | SpellDescription$ Exile target creature card from your graveyard. Create a token that's a copy of that card, except it's 1/1, it's a Spirit in addition to its other types, and it has flying. Create a black Zombie creature token with power equal to that card's power and toughness equal to that card's toughness. -SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | SetPower$ 1 | SetToughness$ 1 | AddTypes$ Spirit | Keywords$ Flying | SubAbility$ DBToken -SVar:DBToken:DB$ Token | LegacyImage$ b x x zombie emn | TokenScript$ b_x_x_zombie | TokenPower$ X | TokenToughness$ Y | TokenOwner$ You | TokenAmount$ 1 | References$ X,Y | SubAbility$ DBCleanup +SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | SetPower$ 1 | SetToughness$ 1 | AddTypes$ Spirit | AddKeywords$ Flying | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenImage$ b x y zombie EMN | TokenName$ Zombie | TokenTypes$ Creature,Zombie | TokenPower$ X | TokenToughness$ Y | TokenColors$ Black | TokenOwner$ You | TokenAmount$ 1 | References$ X,Y | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:RememberedLKI$CardPower SVar:Y:RememberedLKI$CardToughness diff --git a/forge-gui/res/cardsfolder/s/splinter_twin.txt b/forge-gui/res/cardsfolder/s/splinter_twin.txt index 8c14bee5aa6..0e4598a6087 100644 --- a/forge-gui/res/cardsfolder/s/splinter_twin.txt +++ b/forge-gui/res/cardsfolder/s/splinter_twin.txt @@ -4,7 +4,7 @@ Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 2 R R | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddAbility$ ABCopy | Description$ Enchanted creature has "{T}: Create a token that's a copy of this creature. That token has haste. Exile it at the beginning of the next end step." -SVar:ABCopy:AB$ CopyPermanent | Cost$ T | Defined$ Self | Keywords$ Haste | AtEOT$ Exile | SpellDescription$ Create a token that's a copy of this creature. That token has haste. Exile it at the beginning of the next end step. +SVar:ABCopy:AB$ CopyPermanent | Cost$ T | Defined$ Self | AddKeywords$ Haste | AtEOT$ Exile | SpellDescription$ Create a token that's a copy of this creature. That token has haste. Exile it at the beginning of the next end step. SVar:NonStackingAttachEffect:True SVar:Picture:http://www.wizards.com/global/images/magic/general/splinter_twin.jpg Oracle:Enchant creature\nEnchanted creature has "{T}: Create a token that's a copy of this creature. That token has haste. Exile it at the beginning of the next end step." diff --git a/forge-gui/res/cardsfolder/t/twinflame.txt b/forge-gui/res/cardsfolder/t/twinflame.txt index 1b5e67bdf5d..cc79e0f2db9 100644 --- a/forge-gui/res/cardsfolder/t/twinflame.txt +++ b/forge-gui/res/cardsfolder/t/twinflame.txt @@ -2,7 +2,7 @@ Name:Twinflame ManaCost:1 R Types:Sorcery K:Strive:2 R -A:SP$ CopyPermanent | Cost$ 1 R | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | TargetMin$ 0 | TargetMax$ MaxTargets | Keywords$ Haste | AtEOT$ Exile | References$ MaxTargets | SpellDescription$ Choose any number of target creatures you control. For each of them, create a token that's a copy of that creature. Those tokens have haste. Exile them at the beginning of the next end step. +A:SP$ CopyPermanent | Cost$ 1 R | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | TargetMin$ 0 | TargetMax$ MaxTargets | AddKeywords$ Haste | AtEOT$ Exile | References$ MaxTargets | SpellDescription$ Choose any number of target creatures you control. For each of them, create a token that's a copy of that creature. Those tokens have haste. Exile them at the beginning of the next end step. SVar:MaxTargets:Count$Valid Creature.YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/twinflame.jpg Oracle:Strive — Twinflame costs {2}{R} more to cast for each target beyond the first.\nChoose any number of target creatures you control. For each of them, create a token that's a copy of that creature. Those tokens have haste. Exile them at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/v/vesuvan_shapeshifter.txt b/forge-gui/res/cardsfolder/v/vesuvan_shapeshifter.txt index 14af6bc37b7..c992da26df0 100644 --- a/forge-gui/res/cardsfolder/v/vesuvan_shapeshifter.txt +++ b/forge-gui/res/cardsfolder/v/vesuvan_shapeshifter.txt @@ -3,12 +3,10 @@ ManaCost:3 U U Types:Creature Shapeshifter PT:0/0 K:Morph:1 U -K:ETBReplacement:Copy:ChooseCreature:Optional -# Make SVars for granting abilities and triggers on clones distinct to avoid SVars getting overwritten when cloning a clone -SVar:ChooseCreature:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Creature.Other | SubAbility$ DBCopy | AILogic$ Clone | RememberChosen$ True | SpellDescription$ As CARDNAME enters the battlefield or is turned face up, you may choose another creature on the battlefield. If you do, until CARDNAME is turned face down, it becomes a copy of that creature and gains "At the beginning of your upkeep, you may turn this creature face down." -SVar:DBCopy:DB$ Clone | Defined$ Remembered | AddTriggers$ VesShapeUpkeepTrig | AddSVars$ VesShapeTurn,VesShapeUpkeepTrig +K:ETBReplacement:Copy:DBCopy:Optional +SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | AddTriggers$ VesShapeUpkeepTrig | AddSVars$ VesShapeTurn,VesShapeUpkeepTrig | Duration$ UntilFacedown | SpellDescription$ As CARDNAME enters the battlefield or is turned face up, you may choose another creature on the battlefield. If you do, until CARDNAME is turned face down, it becomes a copy of that creature and gains "At the beginning of your upkeep, you may turn this creature face down." SVar:VesShapeUpkeepTrig:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ VesShapeTurn | OptionalDecider$ You | TriggerDescription$ At the beginning of your upkeep, you may turn CARDNAME face down. -SVar:VesShapeTurn:DB$ SetState | Defined$ Self | Mode$ TurnFace | ResetClone$ True -R:Event$ TurnFaceUp | ValidCard$ Card.Self | Optional$ True | ReplaceWith$ ChooseCreature | ActiveZones$ Battlefield | Description$ As CARDNAME is turned face up, you may choose another creature on the battlefield. If you do, until CARDNAME is turned face down, it becomes a copy of that creature and gains "At the beginning of your upkeep, you may turn this creature face down." +SVar:VesShapeTurn:DB$ SetState | Defined$ Self | Mode$ TurnFace +R:Event$ TurnFaceUp | ValidCard$ Card.Self | Optional$ True | ReplaceWith$ DBCopy | ActiveZones$ Battlefield | Secondary$ True | Description$ As CARDNAME is turned face up, you may choose another creature on the battlefield. If you do, until CARDNAME is turned face down, it becomes a copy of that creature and gains "At the beginning of your upkeep, you may turn this creature face down." SVar:Picture:http://www.wizards.com/global/images/magic/general/vesuvan_shapeshifter.jpg Oracle:As Vesuvan Shapeshifter enters the battlefield or is turned face up, you may choose another creature on the battlefield. If you do, until Vesuvan Shapeshifter is turned face down, it becomes a copy of that creature and gains "At the beginning of your upkeep, you may turn this creature face down."\nMorph {1}{U} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.) diff --git a/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt b/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt index 04275abc291..88eb1fac559 100644 --- a/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt +++ b/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt @@ -3,7 +3,11 @@ ManaCost:1 U U Types:Creature Shapeshifter PT:0/1 A:AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card. -S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainTextOf$ Creature.TopGraveyard+YouCtrl | GainedTextHasThisStaticAbility$ True | GainVolrathsDiscardAbility$ True | Description$ ORIGINALTEXTONLY:As long as the top card of your graveyard is a creature card, CARDNAME has the full text of that card and has the text "{2}: Discard a card." (CARDNAME has that card's name, mana cost, color, types, abilities, power, and toughness.) +S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainTextOf$ Creature.TopGraveyard+YouCtrl | GainTextAbilities$ VolrathDiscard | Description$ As long as the top card of your graveyard is a creature card, CARDNAME has the full text of that card and has the text "{2}: Discard a card." (CARDNAME has that card's name, mana cost, color, types, abilities, power, and toughness.) + +SVar:VolrathDiscard:AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card. + + SVar:NeedsOrderedGraveyard:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/volraths_shapeshifter.jpg -Oracle:As long as the top card of your graveyard is a creature card, Volrath's Shapeshifter has the full text of that card and has the text "{2}: Discard a card." (Volrath's Shapeshifter has that card's name, mana cost, color, types, abilities, power, and toughness.)\n{2}: Discard a card. \ No newline at end of file +Oracle:As long as the top card of your graveyard is a creature card, Volrath's Shapeshifter has the full text of that card and has the text "{2}: Discard a card." (Volrath's Shapeshifter has that card's name, mana cost, color, types, abilities, power, and toughness.)\n{2}: Discard a card. diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index ed4a467e112..f9e0bb75419 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -19,7 +19,6 @@ import com.google.common.collect.Sets; import forge.FThreads; import forge.assets.FSkinProp; -import forge.card.CardStateName; import forge.game.GameView; import forge.game.card.CardView; import forge.game.card.CardView.CardStateView; @@ -178,8 +177,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { switch (altState.getState()) { case Original: - final CardStateView currentState = cv.getCurrentState(); - if (currentState.getState() == CardStateName.FaceDown) { + if (cv.isFaceDown()) { return getCurrentPlayer() == null || cv.canFaceDownBeShownToAny(getLocalPlayers()); } return true; //original can always be shown if not a face down that can't be shown