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
- *
+ *
* @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