Merge branch 'cloneRewrite' into 'master'

Clone rewrite

See merge request core-developers/forge!1294
This commit is contained in:
Michael Kamensky
2019-04-24 03:01:13 +00:00
51 changed files with 1101 additions and 765 deletions

View File

@@ -1142,7 +1142,7 @@ public abstract class GameState {
} else if (info.startsWith("SummonSick")) { } else if (info.startsWith("SummonSick")) {
c.setSickness(true); c.setSickness(true);
} else if (info.startsWith("FaceDown")) { } else if (info.startsWith("FaceDown")) {
c.setState(CardStateName.FaceDown, true); c.turnFaceDown(true);
if (info.endsWith("Manifested")) { if (info.endsWith("Manifested")) {
c.setManifested(true); c.setManifested(true);
} }

View File

@@ -367,8 +367,7 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public boolean apply(Card card) { public boolean apply(Card card) {
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB // need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
return card.getName().equals("Volrath's Shapeshifter") return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter");
|| card.getStates().contains(CardStateName.OriginalText) && card.getState(CardStateName.OriginalText).getName().equals("Volrath's Shapeshifter");
} }
}).isEmpty()) { }).isEmpty()) {
int bestValue = 0; int bestValue = 0;

View File

@@ -12,7 +12,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import java.util.List; import java.util.List;
@@ -21,7 +20,6 @@ public class CloneAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Game game = source.getGame(); 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 // TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?" // "Can I use this to block something?"
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
return false;
}
PhaseHandler phase = game.getPhaseHandler(); 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 if (!sa.usesTargeting()) {
// 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) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false; boolean bFlag = false;
@@ -186,9 +170,13 @@ public class CloneAi extends SpellAbilityAi {
@Override @Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer) { Player targetedPlayer) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Player ctrl = host.getController(); 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 boolean isVesuva = "Vesuva".equals(host.getName());
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary" final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
@@ -198,7 +186,8 @@ public class CloneAi extends SpellAbilityAi {
if (!newOptions.isEmpty()) { if (!newOptions.isEmpty()) {
options = newOptions; options = newOptions;
} }
Card choice = ComputerUtilCard.getBestAI(options); Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
if (isVesuva && "Vesuva".equals(choice.getName())) { if (isVesuva && "Vesuva".equals(choice.getName())) {
choice = null; choice = null;
} }
@@ -206,4 +195,44 @@ public class CloneAi extends SpellAbilityAi {
return choice; return choice;
} }
protected Card getCloneTarget(final SpellAbility sa) {
final Card host = sa.getHostCard();
Card tgtCard = host;
if (sa.hasParam("CloneTarget")) {
final List<Card> 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;
}
} }

View File

@@ -4,7 +4,6 @@ import com.google.common.collect.Maps;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; 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 // check to ensure that there are no replacement effects that prevent creatures ETBing from library
// (e.g. Grafdigger's Cage) // (e.g. Grafdigger's Cage)
Card topCopy = CardUtil.getLKICopy(library.getFirst()); Card topCopy = CardUtil.getLKICopy(library.getFirst());
topCopy.setState(CardStateName.FaceDown, false); topCopy.turnFaceDownNoUpdate();
topCopy.setManifested(true); topCopy.setManifested(true);
final Map<String, Object> repParams = Maps.newHashMap(); final Map<String, Object> repParams = Maps.newHashMap();

View File

@@ -19,10 +19,7 @@ import forge.game.GameObjectMap;
import forge.game.GameRules; import forge.game.GameRules;
import forge.game.Match; import forge.game.Match;
import forge.game.StaticEffect; import forge.game.StaticEffect;
import forge.game.card.Card; import forge.game.card.*;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CounterType;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
@@ -267,6 +264,7 @@ public class GameCopier {
System.err.println(sa.toString()); System.err.println(sa.toString());
} }
} }
return newCard; return newCard;
} }
@@ -295,6 +293,7 @@ public class GameCopier {
newCard.setChangedCardTypes(c.getChangedCardTypesMap()); newCard.setChangedCardTypes(c.getChangedCardTypesMap());
newCard.setChangedCardKeywords(c.getChangedCardKeywords()); newCard.setChangedCardKeywords(c.getChangedCardKeywords());
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such? // TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
for (KeywordInterface kw : c.getHiddenExtrinsicKeywords()) for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
newCard.addHiddenExtrinsicKeyword(kw); newCard.addHiddenExtrinsicKeyword(kw);
@@ -305,7 +304,7 @@ public class GameCopier {
if (c.isFaceDown()) { if (c.isFaceDown()) {
boolean isCreature = newCard.isCreature(); boolean isCreature = newCard.isCreature();
boolean hasManaCost = !newCard.getManaCost().isNoCost(); boolean hasManaCost = !newCard.getManaCost().isNoCost();
newCard.setState(CardStateName.FaceDown, true); newCard.turnFaceDown(true);
if (c.isManifested()) { if (c.isManifested()) {
newCard.setManifested(true); newCard.setManifested(true);
// TODO: Should be able to copy other abilities... // TODO: Should be able to copy other abilities...
@@ -335,6 +334,11 @@ public class GameCopier {
} }
} }
newCard.setFlipped(c.isFlipped());
for (Map.Entry<Long, CardCloneStates> e : c.getCloneStates().entrySet()) {
newCard.addCloneState(e.getValue().copy(newCard, true), e.getKey());
}
Map<CounterType, Integer> counters = c.getCounters(); Map<CounterType, Integer> counters = c.getCounters();
if (!counters.isEmpty()) { if (!counters.isEmpty()) {
newCard.setCounters(Maps.newEnumMap(counters)); newCard.setCounters(Maps.newEnumMap(counters));

View File

@@ -194,6 +194,8 @@ public class GameSimulator {
System.out.println(); System.out.println();
} }
final SpellAbility playingSa = sa; final SpellAbility playingSa = sa;
simGame.copyLastState();
ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() { ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@@ -5,13 +5,12 @@ public enum CardStateName {
Original, Original,
FaceDown, FaceDown,
Flipped, Flipped,
Cloner,
Transformed, Transformed,
Meld, Meld,
Cloned,
LeftSplit, LeftSplit,
RightSplit, RightSplit,
OriginalText; // backup state for cards like Volrath's Shapeshifter
;
/** /**
* TODO: Write javadoc for this method. * TODO: Write javadoc for this method.

View File

@@ -901,12 +901,10 @@ public class Game {
for (Card c : p.getAllCards()) { for (Card c : p.getAllCards()) {
if (c.hasSVar("NeedsOrderedGraveyard")) { if (c.hasSVar("NeedsOrderedGraveyard")) {
return true; return true;
} else if (c.getStates().contains(CardStateName.OriginalText)) { } else if (c.getOriginalState(CardStateName.Original).hasSVar("NeedsOrderedGraveyard")) {
if (c.getState(CardStateName.OriginalText).hasSVar("NeedsOrderedGraveyard")) {
return true; return true;
} }
} }
}
for (Card c : p.getOpponents().getCardsIn(ZoneType.Battlefield)) { for (Card c : p.getOpponents().getCardsIn(ZoneType.Battlefield)) {
// Bone Dancer is important when an opponent has it active on the battlefield // Bone Dancer is important when an opponent has it active on the battlefield
if ("opponent".equalsIgnoreCase(c.getSVar("NeedsOrderedGraveyard"))) { if ("opponent".equalsIgnoreCase(c.getSVar("NeedsOrderedGraveyard"))) {

View File

@@ -193,26 +193,9 @@ public class GameAction {
} }
if (!c.isToken()) { if (!c.isToken()) {
if (c.isCloned()) { if (c.isCloned() || c.hasTextChangeState()) {
c.switchStates(CardStateName.Original, CardStateName.Cloner, false); c.removeCloneStates();
c.setState(CardStateName.Original, false); c.removeTextChangeStates();
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");
c.updateStateForView(); c.updateStateForView();
} }
@@ -287,18 +270,10 @@ public class GameAction {
// not to battlefield anymore! // not to battlefield anymore!
toBattlefield = false; toBattlefield = false;
if (copied.isCloned()) { if (c.isCloned() || c.hasTextChangeState()) {
copied.switchStates(CardStateName.Original, CardStateName.Cloner, false); c.removeCloneStates();
copied.setState(CardStateName.Original, false); c.removeTextChangeStates();
copied.clearStates(CardStateName.Cloner, false); c.updateStateForView();
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 (copied.getCurrentStateName() != CardStateName.Original) { if (copied.getCurrentStateName() != CardStateName.Original) {
@@ -858,6 +833,7 @@ public class GameAction {
final Map<StaticAbility, CardCollectionView> affectedPerAbility = Maps.newHashMap(); final Map<StaticAbility, CardCollectionView> affectedPerAbility = Maps.newHashMap();
for (final StaticAbilityLayer layer : StaticAbilityLayer.CONTINUOUS_LAYERS) { for (final StaticAbilityLayer layer : StaticAbilityLayer.CONTINUOUS_LAYERS) {
List<StaticAbility> toAdd = Lists.newArrayList();
for (final StaticAbility stAb : staticAbilities) { for (final StaticAbility stAb : staticAbilities) {
final CardCollectionView previouslyAffected = affectedPerAbility.get(stAb); final CardCollectionView previouslyAffected = affectedPerAbility.get(stAb);
final CardCollectionView affectedHere; final CardCollectionView affectedHere;
@@ -870,8 +846,18 @@ public class GameAction {
affectedHere = previouslyAffected; affectedHere = previouslyAffected;
stAb.applyContinuousAbility(layer, 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()) { for (final CardCollectionView affected : affectedPerAbility.values()) {
if (affected != null) { if (affected != null) {

View File

@@ -998,6 +998,10 @@ public class StaticEffect {
affectedCard.removeWithFlash(getTimestamp()); affectedCard.removeWithFlash(getTimestamp());
} }
if (params.containsKey("GainTextOf")) {
affectedCard.removeTextChangeState(getTimestamp());
}
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
} }
return affectedCards; return affectedCards;

View File

@@ -2,7 +2,6 @@ package forge.game.ability.effects;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -178,7 +177,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
movedCard.setExiledWith(host); movedCard.setExiledWith(host);
} }
if (sa.hasParam("ExileFaceDown")) { if (sa.hasParam("ExileFaceDown")) {
movedCard.setState(CardStateName.FaceDown, true); movedCard.turnFaceDown(true);
} }
if (sa.hasParam("Tapped")) { if (sa.hasParam("Tapped")) {
movedCard.setTapped(true); movedCard.setTapped(true);

View File

@@ -5,10 +5,8 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.GameCommand; import forge.GameCommand;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.CardType;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
@@ -29,12 +27,12 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.TextUtil;
import forge.util.collect.*;
import forge.util.Lang; import forge.util.Lang;
import forge.util.MessageUtil; 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.List;
import java.util.Map; import java.util.Map;
@@ -542,7 +540,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard.updateStateForView(); movedCard.updateStateForView();
} }
if (sa.hasParam("FaceDown")) { if (sa.hasParam("FaceDown")) {
movedCard.setState(CardStateName.FaceDown, true); movedCard.turnFaceDown(true);
} }
if (sa.hasParam("Attacking")) { if (sa.hasParam("Attacking")) {
// What should they attack? // What should they attack?
@@ -592,7 +590,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("ExileFaceDown")) { if (sa.hasParam("ExileFaceDown")) {
movedCard.setState(CardStateName.FaceDown, true); movedCard.turnFaceDown(true);
} }
if (sa.hasParam("TrackDiscarded")) { if (sa.hasParam("TrackDiscarded")) {
@@ -1050,10 +1048,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown") && ZoneType.Battlefield.equals(destination)) { 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 // 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")) { if (sa.hasParam("FaceDownPower")) {
c.setBasePower(AbilityUtils.calculateAmount( c.setBasePower(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownPower"), sa)); source, sa.getParam("FaceDownPower"), sa));
@@ -1062,12 +1059,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
c.setBaseToughness(AbilityUtils.calculateAmount( c.setBaseToughness(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownToughness"), sa)); source, sa.getParam("FaceDownToughness"), sa));
} }
}
if (sa.hasParam("FaceDownAddType")) { if (sa.hasParam("FaceDownAddType")) {
CardType t = new CardType(c.getCurrentState().getType()); for (String type : sa.getParam("FaceDownAddType").split(",")) {
t.addAll(Arrays.asList(sa.getParam("FaceDownAddType").split(","))); c.addType(type);
c.getCurrentState().setType(t); }
} }
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness") if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
@@ -1091,7 +1087,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// need to do that again? // need to do that again?
if (sa.hasParam("FaceDown") && !ZoneType.Battlefield.equals(destination)) { if (sa.hasParam("FaceDown") && !ZoneType.Battlefield.equals(destination)) {
movedCard.setState(CardStateName.FaceDown, true); movedCard.turnFaceDown(true);
} }
movedCard.setTimestamp(ts); movedCard.setTimestamp(ts);
} }
@@ -1105,7 +1101,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard.setExiledWith(host); movedCard.setExiledWith(host);
} }
if (sa.hasParam("ExileFaceDown")) { if (sa.hasParam("ExileFaceDown")) {
movedCard.setState(CardStateName.FaceDown, true); movedCard.turnFaceDown(true);
} }
} }
else { else {

View File

@@ -1,26 +1,16 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.GameCommand; import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.*; import forge.game.card.*;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility; 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.game.zone.ZoneType;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
public class CloneEffect extends SpellAbilityEffect { public class CloneEffect extends SpellAbilityEffect {
// TODO update this method // TODO update this method
@@ -57,7 +47,6 @@ public class CloneEffect extends SpellAbilityEffect {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
Card tgtCard = host; Card tgtCard = host;
final Map<String, String> origSVars = host.getSVars();
final Game game = activator.getGame(); final Game game = activator.getGame();
// find cloning source i.e. thing to be copied // find cloning source i.e. thing to be copied
@@ -68,7 +57,17 @@ public class CloneEffect extends SpellAbilityEffect {
if (sa.hasParam("ChoiceZone")) { if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("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); choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card "; String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card ";
@@ -91,11 +90,16 @@ public class CloneEffect extends SpellAbilityEffect {
} }
// find target of cloning i.e. card becoming a clone // find target of cloning i.e. card becoming a clone
if (sa.hasParam("CloneTarget")) {
final List<Card> cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa); final List<Card> cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa);
if (!cloneTargets.isEmpty()) { if (!cloneTargets.isEmpty()) {
tgtCard = cloneTargets.get(0); tgtCard = cloneTargets.get(0);
game.getTriggerHandler().clearInstrinsicActiveTriggers(tgtCard, null); game.getTriggerHandler().clearInstrinsicActiveTriggers(tgtCard, null);
} }
} else if (sa.hasParam("Choices") && sa.usesTargeting()) {
tgtCard = sa.getTargets().getFirstTargetedCard();
game.getTriggerHandler().clearInstrinsicActiveTriggers(tgtCard, null);
}
if (sa.hasParam("CloneZone")) { if (sa.hasParam("CloneZone")) {
if (!tgtCard.getZone().is(ZoneType.smartValueOf(sa.getParam("CloneZone")))) { if (!tgtCard.getZone().is(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
@@ -103,96 +107,12 @@ public class CloneEffect extends SpellAbilityEffect {
} }
} }
// determine the image to be used for the clone final Long ts = game.getNextTimestamp();
String imageFileName = cardToCopy.getGame().getRules().canCloneUseTargetsImage() ? tgtCard.getImageKey() : cardToCopy.getImageKey(); tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
if (sa.hasParam("ImageSource")) { // Allow the image to be stipulated by using a defined card source
List<Card> cloneImgSources = AbilityUtils.getDefinedCards(host, sa.getParam("ImageSource"), sa);
if (!cloneImgSources.isEmpty()) {
imageFileName = cloneImgSources.get(0).getImageKey();
}
}
final boolean keepName = sa.hasParam("KeepName"); // set ETB tapped of clone
final String newName = sa.getParamOrDefault("NewName", null); if (sa.hasParam("IntoPlayTapped")) {
final String originalName = tgtCard.getName(); tgtCard.setTapped(true);
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);
} }
tgtCard.updateStateForView(); tgtCard.updateStateForView();
@@ -213,10 +133,7 @@ public class CloneEffect extends SpellAbilityEffect {
@Override @Override
public void run() { public void run() {
if (cloneCard.isCloned()) { if (cloneCard.removeCloneState(ts)) {
cloneCard.setState(CardStateName.Cloner, false);
cloneCard.switchStates(CardStateName.Cloner, origState, false);
cloneCard.clearStates(CardStateName.Cloner, false);
cloneCard.updateStateForView(); cloneCard.updateStateForView();
game.fireEvent(new GameEventCardStatsChanged(cloneCard)); game.fireEvent(new GameEventCardStatsChanged(cloneCard));
} }
@@ -233,148 +150,11 @@ public class CloneEffect extends SpellAbilityEffect {
else if (duration.equals("UntilUnattached")) { else if (duration.equals("UntilUnattached")) {
sa.getHostCard().addUnattachCommand(unclone); sa.getHostCard().addUnattachCommand(unclone);
} }
else if (duration.equals("UntilFacedown")) {
sa.getHostCard().addFacedownCommand(unclone);
}
} }
game.fireEvent(new GameEventCardStatsChanged(tgtCard)); game.fireEvent(new GameEventCardStatsChanged(tgtCard));
} // cloneResolve } // cloneResolve
private static void addExtraCharacteristics(final Card tgtCard, final SpellAbility sa, final Map<String, String> 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<String> 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<String, String> 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<String, String> 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));
}
}
}
} }

View File

@@ -2,16 +2,11 @@ package forge.game.ability.effects;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ImageKeys;
import forge.StaticData; import forge.StaticData;
import forge.card.CardRulesPredicates; import forge.card.CardRulesPredicates;
import forge.card.CardType;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -19,16 +14,12 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardFactory; import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged; import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; 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.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.Aggregates; import forge.util.Aggregates;
@@ -259,153 +250,29 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
private Card getProtoType(final SpellAbility sa, final Card original) { private Card getProtoType(final SpellAbility sa, final Card original) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final List<String> keywords = Lists.newArrayList(); final Player newOwner = sa.getActivatingPlayer();
final List<String> types = Lists.newArrayList(); int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
final List<String> svars = Lists.newArrayList(); final Card copy = new Card(id, original.getPaperCard(), host.getGame());
final List<String> triggers = Lists.newArrayList(); copy.setOwner(newOwner);
boolean asNonLegendary = false; copy.setSetCode(original.getSetCode());
if (sa.hasParam("Keywords")) { if (sa.hasParam("Embalm")) {
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & "))); copy.setEmbalmed(true);
}
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(" & ")));
} }
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.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")) { if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), copy); 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; return copy;
} }
} }

View File

@@ -1,6 +1,5 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -318,7 +317,7 @@ public class DigEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("ExileFaceDown")) { if (sa.hasParam("ExileFaceDown")) {
c.setState(CardStateName.FaceDown, true); c.turnFaceDown(true);
} }
if (sa.hasParam("Imprint")) { if (sa.hasParam("Imprint")) {
host.addImprintedCard(c); host.addImprintedCard(c);

View File

@@ -7,7 +7,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.StaticData; import forge.StaticData;
import forge.card.CardRulesPredicates; import forge.card.CardRulesPredicates;
import forge.card.CardStateName;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
@@ -49,8 +48,10 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
for (byte i = 0; i < MagicColor.WUBRG.length; i++) { for (byte i = 0; i < MagicColor.WUBRG.length; i++) {
if (color.hasAnyColor(MagicColor.WUBRG[i])) { if (color.hasAnyColor(MagicColor.WUBRG[i])) {
landNames.add(MagicColor.Constant.BASIC_LANDS.get(i)); landNames.add(MagicColor.Constant.BASIC_LANDS.get(i));
landNames.add(MagicColor.Constant.SNOW_LANDS.get(i));
} }
} }
final Predicate<PaperCard> cp = Predicates.compose(new Predicate<String>() { final Predicate<PaperCard> cp = Predicates.compose(new Predicate<String>() {
@Override @Override
public boolean apply(final String name) { public boolean apply(final String name) {
@@ -69,14 +70,8 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
random = CardFactory.getCard(ran, activator, game); random = CardFactory.getCard(ran, activator, game);
} }
final String imageFileName = game.getRules().canCloneUseTargetsImage() ? source.getImageKey() : random.getImageKey(); source.addCloneState(CardFactory.getCloneStates(random, source, sa), game.getNextTimestamp());
source.addAlternateState(CardStateName.Cloner, false);
source.switchStates(CardStateName.Original, CardStateName.Cloner, false);
source.setState(CardStateName.Original, false);
source.updateStateForView(); source.updateStateForView();
final CardStateName stateToCopy = random.getCurrentStateName();
CardFactory.copyState(random, stateToCopy, source, source.getCurrentStateName());
source.setImageKey(imageFileName);
source.setController(activator, 0); source.setController(activator, 0);
game.getAction().moveTo(activator.getZone(ZoneType.Battlefield), source, sa); game.getAction().moveTo(activator.getZone(ZoneType.Battlefield), source, sa);

View File

@@ -88,7 +88,6 @@ public class Card extends GameEntity implements Comparable<Card> {
private final Map<CardStateName, CardState> states = Maps.newEnumMap(CardStateName.class); private final Map<CardStateName, CardState> states = Maps.newEnumMap(CardStateName.class);
private CardState currentState; private CardState currentState;
private CardStateName currentStateName = CardStateName.Original; private CardStateName currentStateName = CardStateName.Original;
private CardStateName preFaceDownState = CardStateName.Original;
private ZoneType castFrom = null; private ZoneType castFrom = null;
private SpellAbility castSA = null; private SpellAbility castSA = null;
@@ -119,6 +118,8 @@ public class Card extends GameEntity implements Comparable<Card> {
private final Map<Long, CardChangedType> changedCardTypes = Maps.newTreeMap(); private final Map<Long, CardChangedType> changedCardTypes = Maps.newTreeMap();
private final Map<Long, KeywordsChange> changedCardKeywords = Maps.newTreeMap(); private final Map<Long, KeywordsChange> changedCardKeywords = Maps.newTreeMap();
private final Map<Long, CardColor> changedCardColors = Maps.newTreeMap(); private final Map<Long, CardColor> changedCardColors = Maps.newTreeMap();
private final NavigableMap<Long, CardCloneStates> clonedStates = Maps.newTreeMap();
private final NavigableMap<Long, CardCloneStates> textChangeStates = Maps.newTreeMap();
// changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps // changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps
private final CardChangedWords changedTextColors = new CardChangedWords(); private final CardChangedWords changedTextColors = new CardChangedWords();
@@ -170,6 +171,11 @@ public class Card extends GameEntity implements Comparable<Card> {
private boolean madness = false; private boolean madness = false;
private boolean madnessWithoutCast = 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 phasedOut = false;
private boolean directlyPhasedOut = true; private boolean directlyPhasedOut = true;
@@ -235,6 +241,7 @@ public class Card extends GameEntity implements Comparable<Card> {
private final List<GameCommand> changeControllerCommandList = Lists.newArrayList(); private final List<GameCommand> changeControllerCommandList = Lists.newArrayList();
private final List<GameCommand> unattachCommandList = Lists.newArrayList(); private final List<GameCommand> unattachCommandList = Lists.newArrayList();
private final List<GameCommand> faceupCommandList = Lists.newArrayList(); private final List<GameCommand> faceupCommandList = Lists.newArrayList();
private final List<GameCommand> facedownCommandList = Lists.newArrayList();
private final List<Object[]> staticCommandList = Lists.newArrayList(); private final List<Object[]> staticCommandList = Lists.newArrayList();
private final static ImmutableList<String> storableSVars = ImmutableList.of("ChosenX"); private final static ImmutableList<String> storableSVars = ImmutableList.of("ChosenX");
@@ -302,27 +309,12 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
public boolean changeToState(final CardStateName state) { public boolean changeToState(final CardStateName state) {
CardStateName cur = currentStateName; if (hasState(state)) {
return setState(state, true);
if (!setState(state, true)) { }
return false; return false;
} }
if ((cur == CardStateName.Original && state == CardStateName.Transformed)
|| (cur == CardStateName.Transformed && state == CardStateName.Original)) {
// Clear old dfc trigger from the trigger handler
getGame().getTriggerHandler().clearInstrinsicActiveTriggers(this, null);
getGame().getTriggerHandler().registerActiveTrigger(this, false);
Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Transformer", this);
getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false);
this.incrementTransformedTimestamp();
}
return true;
}
public long getTransformedTimestamp() { return transformedTimestamp; } public long getTransformedTimestamp() { return transformedTimestamp; }
public void incrementTransformedTimestamp() { this.transformedTimestamp++; } public void incrementTransformedTimestamp() { this.transformedTimestamp++; }
@@ -365,13 +357,63 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
return null; return null;
} }
public CardState getState(final CardStateName state) { public CardState getState(final CardStateName state) {
return getState(state, false);
}
public CardState getState(final CardStateName state, boolean skipTextChange) {
if (!skipTextChange) {
CardCloneStates txtStates = getLastTextChangeState();
if (txtStates != null) {
return txtStates.get(state);
}
}
CardCloneStates clStates = getLastClonedState();
if (clStates == null) {
return getOriginalState(state);
} else {
return clStates.get(state);
}
}
public boolean hasState(final CardStateName state) {
if (state == CardStateName.FaceDown) {
return true;
}
CardCloneStates clStates = getLastClonedState();
if (clStates == null) {
return states.containsKey(state);
} else {
return clStates.containsKey(state);
}
}
public CardState getOriginalState(final CardStateName state) {
if (!states.containsKey(state) && state == CardStateName.FaceDown) { if (!states.containsKey(state) && state == CardStateName.FaceDown) {
states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this)); states.put(CardStateName.FaceDown, CardUtil.getFaceDownCharacteristic(this));
} }
return states.get(state); return states.get(state);
} }
public boolean setState(final CardStateName state, boolean updateView) { public boolean setState(final CardStateName state, boolean updateView) {
return setState(state, updateView, false);
}
public boolean setState(final CardStateName state, boolean updateView, boolean forceUpdate) {
CardCloneStates textChangeStates = getLastTextChangeState();
if (textChangeStates != null) {
if (!textChangeStates.containsKey(state)) {
throw new RuntimeException(getName() + " tried to switch to non-existant text change state \"" + state + "\"!");
//return false; // Nonexistant state.
}
} else {
CardCloneStates cloneStates = getLastClonedState();
if (cloneStates != null) {
if (!cloneStates.containsKey(state)) {
throw new RuntimeException(getName() + " tried to switch to non-existant cloned state \"" + state + "\"!");
//return false; // Nonexistant state.
}
} else {
if (!states.containsKey(state)) { if (!states.containsKey(state)) {
if (state == CardStateName.FaceDown) { if (state == CardStateName.FaceDown) {
// The face-down state is created lazily only when needed. // The face-down state is created lazily only when needed.
@@ -381,8 +423,10 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; // Nonexistant state. return false; // Nonexistant state.
} }
} }
}
}
if (state.equals(currentStateName)) { if (state.equals(currentStateName) && !forceUpdate) {
return false; return false;
} }
@@ -392,7 +436,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
currentStateName = state; currentStateName = state;
currentState = states.get(state); currentState = getState(state);
// update the host for static abilities // update the host for static abilities
for (StaticAbility sa : currentState.getStaticAbilities()) { for (StaticAbility sa : currentState.getStaticAbilities()) {
@@ -432,6 +476,14 @@ public class Card extends GameEntity implements Comparable<Card> {
return currentStateName; return currentStateName;
} }
// use by CopyPermament
public void setStates(Map<CardStateName, CardState> map) {
states.clear();
states.putAll(map);
}
// was only used for Clone Effects
@Deprecated
public void switchStates(final CardStateName from, final CardStateName to, boolean updateView) { public void switchStates(final CardStateName from, final CardStateName to, boolean updateView) {
final CardState tmp = states.get(from); final CardState tmp = states.get(from);
states.put(from, states.get(to)); states.put(from, states.get(to));
@@ -445,7 +497,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
public final void addAlternateState(final CardStateName state, final boolean updateView) { public final void addAlternateState(final CardStateName state, final boolean updateView) {
states.put(state, new CardState(view.createAlternateState(state), this)); states.put(state, new CardState(this, state));
if (updateView) { if (updateView) {
view.updateState(this); view.updateState(this);
} }
@@ -483,10 +535,6 @@ public class Card extends GameEntity implements Comparable<Card> {
currentState.getView().updateType(currentState); currentState.getView().updateType(currentState);
} }
public void setPreFaceDownState(CardStateName preCharacteristic) {
preFaceDownState = preCharacteristic;
}
public boolean changeCardState(final String mode, final String customState) { public boolean changeCardState(final String mode, final String customState) {
if (mode == null) if (mode == null)
return changeToState(CardStateName.smartValueOf(customState)); return changeToState(CardStateName.smartValueOf(customState));
@@ -501,26 +549,43 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
CardStateName destState = oldState == CardStateName.Transformed ? CardStateName.Original : CardStateName.Transformed; backside = !backside;
return changeToState(destState); boolean result = changeToState(backside ? CardStateName.Transformed : CardStateName.Original);
// do the Transform trigger there, it can also happen if the resulting state doesn't change
// Clear old dfc trigger from the trigger handler
getGame().getTriggerHandler().clearInstrinsicActiveTriggers(this, null);
getGame().getTriggerHandler().registerActiveTrigger(this, false);
Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Transformer", this);
getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false);
incrementTransformedTimestamp();
return result;
} else if (mode.equals("Flip") && isFlipCard()) { } else if (mode.equals("Flip") && isFlipCard()) {
CardStateName destState = oldState == CardStateName.Flipped ? CardStateName.Original : CardStateName.Flipped; // 709.4. Flipping a permanent is a one-way process.
return changeToState(destState); if (isFlipped()) {
} else if (mode.equals("TurnFace")) { return false;
if (oldState == CardStateName.Original) {
// Reset cloned state if Vesuvan Shapeshifter
if (isCloned() && getState(CardStateName.Cloner).getName().equals("Vesuvan Shapeshifter")) {
switchStates(CardStateName.Cloner, CardStateName.Original, false);
setState(CardStateName.Original, false);
clearStates(CardStateName.Cloner, false);
} }
flipped = true;
// a facedown card does flip but the state doesn't change
if (isFaceDown()) {
return false;
}
return changeToState(CardStateName.Flipped);
} else if (mode.equals("TurnFace")) {
if (oldState == CardStateName.Original || oldState == CardStateName.Flipped) {
return turnFaceDown(); return turnFaceDown();
} else if (oldState == CardStateName.FaceDown) { } else if (isFaceDown()) {
return turnFaceUp(); return turnFaceUp();
} }
} else if (mode.equals("Meld") && hasAlternateState()) { } else if (mode.equals("Meld") && isMeldable()) {
return changeToState(CardStateName.Meld); return changeToState(CardStateName.Meld);
} }
return false; return false;
@@ -543,7 +608,6 @@ public class Card extends GameEntity implements Comparable<Card> {
setController(p, game.getNextTimestamp()); setController(p, game.getNextTimestamp());
// Mark this card as "manifested" // Mark this card as "manifested"
setPreFaceDownState(CardStateName.Original);
setManifested(true); setManifested(true);
Card c = game.getAction().moveToPlay(this, p, sa); Card c = game.getAction().moveToPlay(this, p, sa);
@@ -564,14 +628,17 @@ public class Card extends GameEntity implements Comparable<Card> {
public boolean turnFaceDown(boolean override) { public boolean turnFaceDown(boolean override) {
if (override || (!isDoubleFaced() && !isMeldable())) { if (override || (!isDoubleFaced() && !isMeldable())) {
preFaceDownState = currentStateName; facedown = true;
return setState(CardStateName.FaceDown, true); if (setState(CardStateName.FaceDown, true)) {
runFacedownCommands();
return true;
}
} }
return false; return false;
} }
public boolean turnFaceDownNoUpdate() { public boolean turnFaceDownNoUpdate() {
preFaceDownState = currentStateName; facedown = true;
return setState(CardStateName.FaceDown, false); return setState(CardStateName.FaceDown, false);
} }
@@ -580,16 +647,22 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
public boolean turnFaceUp(boolean manifestPaid, boolean runTriggers) { public boolean turnFaceUp(boolean manifestPaid, boolean runTriggers) {
if (currentStateName == CardStateName.FaceDown) { if (isFaceDown()) {
if (manifestPaid && this.isManifested() && !this.getRules().getType().isCreature()) { if (manifestPaid && isManifested() && !getRules().getType().isCreature()) {
// If we've manifested a non-creature and we're demanifesting disallow it // If we've manifested a non-creature and we're demanifesting disallow it
// Unless this creature also has a Morph ability // Unless this creature also has a Morph ability
return false; return false;
} }
boolean result = setState(preFaceDownState, true); boolean result;
if (isFlipped() && isFlipCard()) {
result = setState(CardStateName.Flipped, true);
} else {
result = setState(CardStateName.Original, true);
}
facedown = false;
// need to run faceup commands, currently // need to run faceup commands, currently
// it does cleanup the modified facedown state // it does cleanup the modified facedown state
if (result) { if (result) {
@@ -619,10 +692,15 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
CardStateName oldState = getCurrentStateName(); CardStateName destState = backside ? CardStateName.Original : CardStateName.Transformed;
CardStateName destState = oldState == CardStateName.Transformed ? CardStateName.Original : CardStateName.Transformed;
if (isInPlay() && !getState(destState).getType().isPermanent()) { // below only when in play
if (!isInPlay()) {
return true;
}
// use Original State for the transform check
if (!getOriginalState(destState).getType().isPermanent()) {
return false; return false;
} }
@@ -653,8 +731,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
public final boolean isInAlternateState() { public final boolean isInAlternateState() {
return currentStateName != CardStateName.Original return currentStateName != CardStateName.Original;
&& currentStateName != CardStateName.Cloned;
} }
public final boolean hasAlternateState() { public final boolean hasAlternateState() {
@@ -664,34 +741,31 @@ public class Card extends GameEntity implements Comparable<Card> {
int numStates = states.keySet().size(); int numStates = states.keySet().size();
// OriginalText is a technical state used for backup purposes by cards
// like Volrath's Shapeshifter. It's not a directly playable card state,
// so ignore it
if (states.containsKey(CardStateName.OriginalText)) {
numStates--;
}
return numStates > threshold; return numStates > threshold;
} }
public final boolean isDoubleFaced() { public final boolean isDoubleFaced() {
return states.containsKey(CardStateName.Transformed); return getRules() != null && getRules().getSplitType() == CardSplitType.Transform;
} }
public final boolean isMeldable() { public final boolean isMeldable() {
return states.containsKey(CardStateName.Meld); return getRules() != null && getRules().getSplitType() == CardSplitType.Meld;
} }
public final boolean isFlipCard() { public final boolean isFlipCard() {
return states.containsKey(CardStateName.Flipped); return hasState(CardStateName.Flipped);
} }
public final boolean isSplitCard() { public final boolean isSplitCard() {
return states.containsKey(CardStateName.LeftSplit); return getRules() != null && getRules().getSplitType() == CardSplitType.Split;
}
public final boolean isBackSide() {
return backside;
} }
public boolean isCloned() { public boolean isCloned() {
return states.containsKey(CardStateName.Cloner); return !clonedStates.isEmpty();
} }
public static List<String> getStorableSVars() { public static List<String> getStorableSVars() {
@@ -2396,7 +2470,20 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
public final boolean isFaceDown() { public final boolean isFaceDown() {
return currentStateName == CardStateName.FaceDown; //return currentStateName == CardStateName.FaceDown;
return facedown;
}
public final void setFaceDown(boolean value) {
facedown = value;
}
public final boolean isFlipped() {
return flipped;
}
public final void setFlipped(boolean value) {
flipped = value;
} }
public final void setCanCounter(final boolean b) { public final void setCanCounter(final boolean b) {
@@ -2415,6 +2502,7 @@ public class Card extends GameEntity implements Comparable<Card> {
for (final GameCommand c : etbCommandList) { for (final GameCommand c : etbCommandList) {
c.run(); c.run();
} }
etbCommandList.clear();
} }
public final void addLeavesPlayCommand(final GameCommand c) { public final void addLeavesPlayCommand(final GameCommand c) {
@@ -2425,6 +2513,7 @@ public class Card extends GameEntity implements Comparable<Card> {
for (final GameCommand c : leavePlayCommandList) { for (final GameCommand c : leavePlayCommandList) {
c.run(); c.run();
} }
leavePlayCommandList.clear();
} }
public final void addUntapCommand(final GameCommand c) { public final void addUntapCommand(final GameCommand c) {
@@ -2439,6 +2528,10 @@ public class Card extends GameEntity implements Comparable<Card> {
faceupCommandList.add(c); faceupCommandList.add(c);
} }
public final void addFacedownCommand(final GameCommand c) {
facedownCommandList.add(c);
}
public final void runUnattachCommands() { public final void runUnattachCommands() {
for (final GameCommand c : unattachCommandList) { for (final GameCommand c : unattachCommandList) {
c.run(); c.run();
@@ -2449,6 +2542,14 @@ public class Card extends GameEntity implements Comparable<Card> {
for (final GameCommand c : faceupCommandList) { for (final GameCommand c : faceupCommandList) {
c.run(); c.run();
} }
faceupCommandList.clear();
}
public final void runFacedownCommands() {
for (final GameCommand c : facedownCommandList) {
c.run();
}
facedownCommandList.clear();
} }
public final void addChangeControllerCommand(final GameCommand c) { public final void addChangeControllerCommand(final GameCommand c) {
@@ -2459,6 +2560,7 @@ public class Card extends GameEntity implements Comparable<Card> {
for (final GameCommand c : changeControllerCommandList) { for (final GameCommand c : changeControllerCommandList) {
c.run(); c.run();
} }
changeControllerCommandList.clear();
} }
public final void setSickness(boolean sickness0) { public final void setSickness(boolean sickness0) {
@@ -2900,11 +3002,9 @@ public class Card extends GameEntity implements Comparable<Card> {
public final void setColor(final String color) { public final void setColor(final String color) {
currentState.setColor(color); currentState.setColor(color);
currentState.getView().updateColors(this);
} }
public final void setColor(final byte color) { public final void setColor(final byte color) {
currentState.setColor(color); currentState.setColor(color);
currentState.getView().updateColors(this);
} }
public final ColorSet determineColor() { public final ColorSet determineColor() {
@@ -2977,6 +3077,113 @@ public class Card extends GameEntity implements Comparable<Card> {
return getLatestPT().getRight(); return getLatestPT().getRight();
} }
public final void addCloneState(CardCloneStates states, final long timestamp) {
clonedStates.put(timestamp, states);
updateCloneState(true);
}
public final boolean removeCloneState(final long timestamp) {
if (clonedStates.remove(timestamp) != null) {
updateCloneState(true);
return true;
}
return false;
}
public final boolean removeCloneState(final CardTraitBase ctb) {
boolean changed = false;
List<Long> toRemove = Lists.newArrayList();
for (final Entry<Long, CardCloneStates> e : clonedStates.entrySet()) {
if (ctb.equals(e.getValue().getSource())) {
toRemove.add(e.getKey());
changed = true;
}
}
for (final Long l : toRemove) {
clonedStates.remove(l);
}
if (changed) {
updateCloneState(true);
}
return changed;
}
public final Card getCloner() {
CardCloneStates clStates = getLastClonedState();
if (clStates == null) {
return null;
}
return clStates.getHost();
}
public final void removeCloneStates() {
clonedStates.clear();
}
public final Map<Long, CardCloneStates> getCloneStates() {
return clonedStates;
}
public final void setCloneStates(Map<Long, CardCloneStates> val) {
clonedStates.clear();
clonedStates.putAll(val);
updateCloneState(true);
}
private final void updateCloneState(final boolean updateView) {
if (isFaceDown()) {
setState(CardStateName.FaceDown, updateView, true);
} else {
setState(getFaceupCardStateName(), updateView, true);
}
}
public final CardStateName getFaceupCardStateName() {
if (isFlipped() && hasState(CardStateName.Flipped)) {
return CardStateName.Flipped;
} else if (backside && isDoubleFaced()) {
return CardStateName.Transformed;
} else if (backside && isMeldable()) {
return CardStateName.Meld;
} else {
return CardStateName.Original;
}
}
private final CardCloneStates getLastClonedState() {
if (clonedStates.isEmpty()) {
return null;
}
return clonedStates.lastEntry().getValue();
}
public final void addTextChangeState(CardCloneStates states, final long timestamp) {
textChangeStates.put(timestamp, states);
updateCloneState(true);
}
public final boolean removeTextChangeState(final long timestamp) {
if (textChangeStates.remove(timestamp) != null) {
updateCloneState(true);
return true;
}
return false;
}
public final void removeTextChangeStates() {
textChangeStates.clear();
}
private final CardCloneStates getLastTextChangeState() {
if (textChangeStates.isEmpty()) {
return null;
}
return textChangeStates.lastEntry().getValue();
}
public final boolean hasTextChangeState() {
return !textChangeStates.isEmpty();
}
/** /**
* *
* Get the latest set Power and Toughness of this Card. * Get the latest set Power and Toughness of this Card.
@@ -5575,10 +5782,7 @@ public class Card extends GameEntity implements Comparable<Card> {
if (isFaceDown()) { if (isFaceDown()) {
lkicheck = true; lkicheck = true;
source = CardUtil.getLKICopy(source); source = CardUtil.getLKICopy(source);
// TODO need to be changed with CloneRewrite and FaceDownState?
source.turnFaceUp(false, false); source.turnFaceUp(false, false);
source.getCurrentState().copyFrom(getState(CardStateName.Original), true);
} }
if (lkicheck) { if (lkicheck) {

View File

@@ -0,0 +1,59 @@
package forge.game.card;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.game.CardTraitBase;
import java.util.Map;
public class CardCloneStates extends ForwardingMap<CardStateName, CardState> {
private Map<CardStateName, CardState> dataMap = Maps.newEnumMap(CardStateName.class);
private Card origin = null;
private CardTraitBase ctb = null;
public CardCloneStates(Card origin, CardTraitBase sa) {
super();
this.origin = origin;
this.ctb = sa;
}
public Card getOrigin() {
return origin;
}
public CardTraitBase getSource() {
return ctb;
}
public Card getHost() {
return ctb.getHostCard();
}
@Override
protected Map<CardStateName, CardState> delegate() {
return dataMap;
}
public CardState get(CardStateName key) {
if (dataMap.containsKey(key)) {
return super.get(key);
}
CardState original = super.get(CardStateName.Original);
// need to copy it so the view has the right state name
CardState result = new CardState(original.getCard(), key);
result.copyFrom(original, false);
dataMap.put(key, result);
return result;
}
public CardCloneStates copy(final Card host, final boolean lki) {
CardCloneStates result = new CardCloneStates(origin, ctb);
for (Map.Entry<CardStateName, CardState> e : dataMap.entrySet()) {
result.put(e.getKey(), e.getValue().copy(host, e.getKey(), lki));
}
return result;
}
}

View File

@@ -18,24 +18,32 @@
package forge.game.card; package forge.game.card;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ImageKeys;
import forge.StaticData; import forge.StaticData;
import forge.card.*; import forge.card.*;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.spellability.*; import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerHandler;
import forge.game.trigger.WrappedAbility; import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.IPaperCard; import forge.item.IPaperCard;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.TextUtil;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -77,6 +85,10 @@ public class CardFactory {
out = CardFactory.copyStats(in, in.getController(), assignNewId); out = CardFactory.copyStats(in, in.getController(), assignNewId);
out.setToken(true); out.setToken(true);
// need to copy this values for the tokens
out.setEmbalmed(in.isEmbalmed());
out.setEternalized(in.isEternalized());
// add abilities // add abilities
//for (SpellAbility sa : in.getIntrinsicSpellAbilities()) { //for (SpellAbility sa : in.getIntrinsicSpellAbilities()) {
// out.addSpellAbility(sa); // out.addSpellAbility(sa);
@@ -101,6 +113,7 @@ public class CardFactory {
out.addImprintedCard(o); out.addImprintedCard(o);
} }
out.setCommander(in.isCommander()); out.setCommander(in.isCommander());
//out.setFaceDown(in.isFaceDown());
return out; return out;
@@ -687,5 +700,233 @@ public class CardFactory {
return wrapperAbility; return wrapperAbility;
} }
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) {
final Card host = sa.getHostCard();
final Map<String,String> origSVars = host.getSVars();
final List<String> types = Lists.newArrayList();
final List<String> keywords = Lists.newArrayList();
List<String> 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<CardStateName, CardState> 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 } // end class AbstractCardFactory

View File

@@ -101,16 +101,18 @@ public class CardFactoryUtil {
@Override @Override
public void resolve() { public void resolve() {
Card c = hostCard.getGame().getAction().moveToPlay(hostCard, this); hostCard.getGame().getAction().moveToPlay(hostCard, this);
c.setPreFaceDownState(CardStateName.Original); //c.setPreFaceDownState(CardStateName.Original);
} }
@Override @Override
public boolean canPlay() { public boolean canPlay() {
CardStateName stateBackup = hostCard.getCurrentStateName(); CardStateName stateBackup = hostCard.getCurrentStateName();
hostCard.setState(CardStateName.FaceDown, false); boolean face = hostCard.isFaceDown();
hostCard.turnFaceDownNoUpdate();
boolean success = super.canPlay(); boolean success = super.canPlay();
hostCard.setState(stateBackup, false); hostCard.setState(stateBackup, false);
hostCard.setFaceDown(face);
return success; return success;
} }
}; };

View File

@@ -526,6 +526,11 @@ 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() { public CardRarity getRarity() {
return rarity; return rarity;

View File

@@ -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 // used for the purpose of cards that care about the zone the card was known to be in last
newCopy.setLastKnownZone(in.getLastKnownZone()); 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()) { if (in.isCloned()) {
newCopy.addAlternateState(CardStateName.Cloner, false); newCopy.addAlternateState(CardStateName.Cloner, false);
newCopy.getState(CardStateName.Cloner).copyFrom(in.getState(CardStateName.Cloner), true); newCopy.getState(CardStateName.Cloner).copyFrom(in.getState(CardStateName.Cloner), true);
} }
//*/
newCopy.setType(new CardType(in.getType())); newCopy.setType(new CardType(in.getType()));
newCopy.setToken(in.isToken()); newCopy.setToken(in.isToken());
@@ -327,7 +332,7 @@ public final class CardUtil {
final CardType type = new CardType(); final CardType type = new CardType();
type.add("Creature"); 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.setBasePower(2);
ret.setBaseToughness(2); ret.setBaseToughness(2);

View File

@@ -113,7 +113,7 @@ public class CardView extends GameEntityView {
} }
public boolean isFaceDown() { public boolean isFaceDown() {
return getCurrentState().getState() == CardStateName.FaceDown; return get(TrackableProperty.Facedown);// getCurrentState().getState() == CardStateName.FaceDown;
} }
public boolean isFlipCard() { public boolean isFlipCard() {
@@ -121,16 +121,18 @@ public class CardView extends GameEntityView {
} }
public boolean isFlipped() { public boolean isFlipped() {
return getCurrentState().getState() == CardStateName.Flipped; return get(TrackableProperty.Flipped); // getCurrentState().getState() == CardStateName.Flipped;
} }
public boolean isSplitCard() { public boolean isSplitCard() {
return get(TrackableProperty.SplitCard); return get(TrackableProperty.SplitCard);
} }
/*
public boolean isTransformed() { public boolean isTransformed() {
return getCurrentState().getState() == CardStateName.Transformed; return getCurrentState().getState() == CardStateName.Transformed;
} }
//*/
public boolean isAttacking() { public boolean isAttacking() {
return get(TrackableProperty.Attacking); return get(TrackableProperty.Attacking);
@@ -627,7 +629,9 @@ public class CardView extends GameEntityView {
set(TrackableProperty.SplitCard, isSplitCard); set(TrackableProperty.SplitCard, isSplitCard);
set(TrackableProperty.FlipCard, c.isFlipCard()); 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() + ")"); set(TrackableProperty.Cloner, cloner == null ? null : cloner.getName() + " (" + cloner.getId() + ")");
CardState currentState = c.getCurrentState(); CardState currentState = c.getCurrentState();
@@ -699,7 +703,7 @@ public class CardView extends GameEntityView {
if (name.isEmpty()) { if (name.isEmpty()) {
CardStateView alternate = getAlternateState(); CardStateView alternate = getAlternateState();
if (alternate != null) { if (alternate != null) {
if (this.getCurrentState().getState() == CardStateName.FaceDown) { if (isFaceDown()) {
return "Face-down card (H" + getHiddenId() + ")"; return "Face-down card (H" + getHiddenId() + ")";
} else { } else {
return getAlternateState().getName() + " (" + getId() + ")"; return getAlternateState().getName() + " (" + getId() + ")";

View File

@@ -42,7 +42,7 @@ public class CostAdjustment {
boolean isStateChangeToFaceDown = false; boolean isStateChangeToFaceDown = false;
if (sa.isSpell() && ((Spell) sa).isCastFaceDown()) { if (sa.isSpell() && ((Spell) sa).isCastFaceDown()) {
// Turn face down to apply cost modifiers correctly // Turn face down to apply cost modifiers correctly
host.setState(CardStateName.FaceDown, false); host.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true; isStateChangeToFaceDown = true;
} // isSpell } // isSpell
@@ -83,6 +83,7 @@ public class CostAdjustment {
// Reset card state (if changed) // Reset card state (if changed)
if (isStateChangeToFaceDown) { if (isStateChangeToFaceDown) {
host.setState(CardStateName.Original, false); host.setState(CardStateName.Original, false);
host.setFaceDown(false);
} }
return result; return result;
} }

View File

@@ -151,6 +151,11 @@ public class TargetChoices implements Cloneable {
return sb.toString(); return sb.toString();
} }
@Override
public final String toString() {
return this.getTargetedString();
}
public final boolean isTargetingAnyCard() { public final boolean isTargetingAnyCard() {
return !targetCards.isEmpty(); return !targetCards.isEmpty();
} }

View File

@@ -19,7 +19,6 @@ package forge.game.staticability;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
@@ -200,23 +199,6 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
String desc = getParam("Description"); String desc = getParam("Description");
desc = TextUtil.fastReplace(desc, "CARDNAME", this.hostCard.getName()); desc = TextUtil.fastReplace(desc, "CARDNAME", this.hostCard.getName());
if (desc.contains("ORIGINALTEXTONLY:")) {
// Only display the description if the text of the card is not changed via GainTextOf.
desc = TextUtil.fastReplace(desc, "ORIGINALTEXTONLY:", "");
boolean hasOrigText = this.hostCard.getStates().contains(CardStateName.OriginalText);
if (hasOrigText) {
String origName = this.hostCard.getState(CardStateName.OriginalText).getName();
String curName = this.hostCard.getName();
if (origName.equals(curName)) {
return desc;
} else {
return TextUtil.concatNoSpace("^ Text changed (", origName, ") ^");
}
}
}
return desc; return desc;
} else { } else {
return ""; return "";

View File

@@ -20,7 +20,6 @@ package forge.game.staticability;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.GameCommand; import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
@@ -502,67 +501,10 @@ public final class StaticAbilityContinuous {
// Gain text from another card // Gain text from another card
if (layer == StaticAbilityLayer.TEXT) { if (layer == StaticAbilityLayer.TEXT) {
// Make no changes in case the target for the ability is still the same as before if (gainTextSource != null) {
boolean noChange = false; affectedCard.addTextChangeState(
if (gainTextSource != null && affectedCard.hasSVar("GainingTextFrom") && affectedCard.hasSVar("GainingTextFromTimestamp") CardFactory.getCloneStates(gainTextSource, affectedCard, stAb), se.getTimestamp()
&& gainTextSource.getName() == affectedCard.getSVar("GainingTextFrom") );
&& gainTextSource.getTimestamp() == Long.parseLong(affectedCard.getSVar("GainingTextFromTimestamp"))) {
noChange = true;
}
if (!noChange) {
// Restore the original text in case it was remembered before
if (affectedCard.getStates().contains(CardStateName.OriginalText)) {
affectedCard.clearTriggersNew();
List<SpellAbility> 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()));
}
} }
} }

View File

@@ -3,6 +3,8 @@ package forge.game.staticability;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
public enum StaticAbilityLayer { public enum StaticAbilityLayer {
/** Layer 1 for control-changing effects. */
COPY,
/** Layer 2 for control-changing effects. */ /** Layer 2 for control-changing effects. */
CONTROL, CONTROL,
@@ -35,5 +37,5 @@ public enum StaticAbilityLayer {
RULES; RULES;
public final static ImmutableList<StaticAbilityLayer> CONTINUOUS_LAYERS = public final static ImmutableList<StaticAbilityLayer> 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);
} }

View File

@@ -232,7 +232,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
if (!hasLegalTargeting(sp, source)) { if (!hasLegalTargeting(sp, source)) {
game.getGameLog().add(GameLogEntryType.STACK_ADD, source + " - [Couldn't add to stack, failed to target] - " + sp.getDescription()); String str = source + " - [Couldn't add to stack, failed to target] - " + sp.getDescription();
System.err.println(str + sp.getAllTargetChoices());
game.getGameLog().add(GameLogEntryType.STACK_ADD, str);
return; return;
} }

View File

@@ -20,9 +20,16 @@ public enum TrackableProperty {
Owner(TrackableTypes.PlayerViewType), Owner(TrackableTypes.PlayerViewType),
Controller(TrackableTypes.PlayerViewType), Controller(TrackableTypes.PlayerViewType),
Zone(TrackableTypes.EnumType(ZoneType.class)), Zone(TrackableTypes.EnumType(ZoneType.class)),
Flipped(TrackableTypes.BooleanType),
Facedown(TrackableTypes.BooleanType),
//TODO?
Cloner(TrackableTypes.StringType),
Cloned(TrackableTypes.BooleanType), Cloned(TrackableTypes.BooleanType),
FlipCard(TrackableTypes.BooleanType), FlipCard(TrackableTypes.BooleanType),
SplitCard(TrackableTypes.BooleanType), SplitCard(TrackableTypes.BooleanType),
Attacking(TrackableTypes.BooleanType), Attacking(TrackableTypes.BooleanType),
Blocking(TrackableTypes.BooleanType), Blocking(TrackableTypes.BooleanType),
PhasedOut(TrackableTypes.BooleanType), PhasedOut(TrackableTypes.BooleanType),
@@ -47,7 +54,7 @@ public enum TrackableProperty {
EncodedCards(TrackableTypes.CardViewCollectionType), EncodedCards(TrackableTypes.CardViewCollectionType),
GainControlTargets(TrackableTypes.CardViewCollectionType), GainControlTargets(TrackableTypes.CardViewCollectionType),
CloneOrigin(TrackableTypes.CardViewType), CloneOrigin(TrackableTypes.CardViewType),
Cloner(TrackableTypes.StringType),
ImprintedCards(TrackableTypes.CardViewCollectionType), ImprintedCards(TrackableTypes.CardViewCollectionType),
HauntedBy(TrackableTypes.CardViewCollectionType), HauntedBy(TrackableTypes.CardViewCollectionType),
Haunting(TrackableTypes.CardViewType), Haunting(TrackableTypes.CardViewType),

View File

@@ -34,7 +34,6 @@ import forge.card.CardDetailUtil;
import forge.card.CardDetailUtil.DetailColors; import forge.card.CardDetailUtil.DetailColors;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.card.CardRarity; import forge.card.CardRarity;
import forge.card.CardStateName;
import forge.game.GameView; import forge.game.GameView;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardView; import forge.game.card.CardView;
@@ -244,7 +243,7 @@ public class CardDetailPanel extends SkinnedPanel {
setInfoLabel.setBorder(BorderFactory.createLineBorder(foreColor)); setInfoLabel.setBorder(BorderFactory.createLineBorder(foreColor));
} }
if (state.getState() == CardStateName.FaceDown) { if (card.isFaceDown()) {
updateBorder(state, false); // TODO: HACK! A temporary measure until the morphs still leaking color can be fixed properly. updateBorder(state, false); // TODO: HACK! A temporary measure until the morphs still leaking color can be fixed properly.
} else { } else {
updateBorder(state, mayView); updateBorder(state, mayView);

View File

@@ -182,7 +182,7 @@ public final class DeckManager extends ItemManager<DeckProxy> implements IHasGam
if (parentPath != null) { if (parentPath != null) {
fullPath = parentPath + key.toString(); fullPath = parentPath + key.toString();
} }
String finalFullPath = fullPath; final String finalFullPath = fullPath;
GuiUtils.addMenuItem(menu, key.toString(), null, new Runnable() { GuiUtils.addMenuItem(menu, key.toString(), null, new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@@ -17,28 +17,9 @@
*/ */
package forge.screens.match.views; 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.CachedCardImage;
import forge.card.CardDetailUtil; import forge.card.CardDetailUtil;
import forge.card.CardDetailUtil.DetailColors; import forge.card.CardDetailUtil.DetailColors;
import forge.card.CardStateName;
import forge.game.GameView; import forge.game.GameView;
import forge.game.card.CardView.CardStateView; import forge.game.card.CardView.CardStateView;
import forge.game.spellability.StackItemView; import forge.game.spellability.StackItemView;
@@ -46,13 +27,23 @@ import forge.gui.framework.DragCell;
import forge.gui.framework.DragTab; import forge.gui.framework.DragTab;
import forge.gui.framework.EDocID; import forge.gui.framework.EDocID;
import forge.gui.framework.IVDoc; import forge.gui.framework.IVDoc;
import forge.screens.match.controllers.CStack;
import forge.screens.match.controllers.CDock.ArcState; import forge.screens.match.controllers.CDock.ArcState;
import forge.screens.match.controllers.CStack;
import forge.toolbox.FMouseAdapter; import forge.toolbox.FMouseAdapter;
import forge.toolbox.FScrollPanel; import forge.toolbox.FScrollPanel;
import forge.toolbox.FSkin; import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinnedTextArea; import forge.toolbox.FSkin.SkinnedTextArea;
import forge.util.collect.FCollectionView; 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. * Assembles Swing components of stack report.
@@ -257,7 +248,7 @@ public class VStack implements IVDoc<CStack> {
// TODO: A hacky workaround is currently used to make the game not leak the color information for Morph cards. // 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 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 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)); setBackground(new Color(color.r, color.g, color.b));
setForeground(FSkin.getHighContrastColor(getBackground())); setForeground(FSkin.getHighContrastColor(getBackground()));

View File

@@ -5,6 +5,7 @@ import forge.ai.ComputerUtilAbility;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CounterType; import forge.game.card.CounterType;
@@ -173,7 +174,7 @@ public class GameSimulatorTest extends SimulationTestCase {
Game game = initAndCreateGame(); Game game = initAndCreateGame();
Player p = game.getPlayers().get(1); Player p = game.getPlayers().get(1);
Card ripper = createCard("Ruthless Ripper", p); Card ripper = createCard("Ruthless Ripper", p);
ripper.setState(CardStateName.FaceDown, true); ripper.turnFaceDownNoUpdate();
p.getZone(ZoneType.Battlefield).add(ripper); p.getZone(ZoneType.Battlefield).add(ripper);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
@@ -1696,4 +1697,238 @@ public class GameSimulatorTest extends SimulationTestCase {
// One cards drawn // One cards drawn
assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 1); 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());
}
} }

View File

@@ -85,7 +85,7 @@ public class CardImageRenderer {
//determine colors for borders //determine colors for borders
final List<DetailColors> borderColors; final List<DetailColors> borderColors;
final boolean isFaceDown = card.getCurrentState().getState() == CardStateName.FaceDown; final boolean isFaceDown = card.isFaceDown();
if (isFaceDown) { if (isFaceDown) {
borderColors = ImmutableList.of(DetailColors.FACE_DOWN); borderColors = ImmutableList.of(DetailColors.FACE_DOWN);
} }
@@ -371,7 +371,7 @@ public class CardImageRenderer {
//determine colors for borders //determine colors for borders
final List<DetailColors> borderColors; final List<DetailColors> borderColors;
final boolean isFaceDown = card.getCurrentState().getState() == CardStateName.FaceDown; final boolean isFaceDown = card.isFaceDown();
if (isFaceDown) { if (isFaceDown) {
borderColors = ImmutableList.of(DetailColors.FACE_DOWN); borderColors = ImmutableList.of(DetailColors.FACE_DOWN);
} }

View File

@@ -213,7 +213,7 @@ public class CardRenderer {
return cardArt; return cardArt;
} }
public static FImageComplex getAftermathSecondCardArt(String imageKey) { public static FImageComplex getAftermathSecondCardArt(final String imageKey) {
FImageComplex cardArt = cardArtCache.get("Aftermath_second_"+imageKey); FImageComplex cardArt = cardArtCache.get("Aftermath_second_"+imageKey);
if (cardArt == null) { if (cardArt == null) {
Texture image = new CachedCardImage(imageKey) { 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. // 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 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 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 color = FSkinColor.fromRGB(borderColor.r, borderColor.g, borderColor.b);
color = FSkinColor.tintColor(Color.WHITE, color, CardRenderer.PT_BOX_TINT); color = FSkinColor.tintColor(Color.WHITE, color, CardRenderer.PT_BOX_TINT);

View File

@@ -19,10 +19,8 @@ import forge.card.CardRenderer;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.CardDetailUtil.DetailColors; import forge.card.CardDetailUtil.DetailColors;
import forge.card.CardRenderer.CardStackPosition; import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardStateName;
import forge.game.GameView; import forge.game.GameView;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.game.spellability.StackItemView; import forge.game.spellability.StackItemView;
import forge.game.zone.ZoneType; 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. // 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 = card.isFaceDown();
final boolean isFaceDown = curState.getState() == CardStateName.FaceDown;
final DetailColors color = isFaceDown ? CardDetailUtil.DetailColors.FACE_DOWN : CardDetailUtil.getBorderColor(card.getCurrentState(), true); // otherwise doesn't work correctly for face down Morphs 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); backColor = FSkinColor.fromRGB(color.r, color.g, color.b);
foreColor = FSkinColor.getHighContrastColor(backColor); foreColor = FSkinColor.getHighContrastColor(backColor);

View File

@@ -1,9 +1,7 @@
Name:Cytoshape Name:Cytoshape
ManaCost:1 G U ManaCost:1 G U
Types:Instant 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. 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.
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
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/cytoshape.jpg 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. Oracle:Choose a nonlegendary creature on the battlefield. Target creature becomes a copy of that creature until end of turn.

View File

@@ -3,9 +3,8 @@ ManaCost:2 U B
Types:Creature Shapeshifter Types:Creature Shapeshifter
PT:0/0 PT:0/0
# Make Svars for granting abilities and triggers on clones distinct to avoid SVars getting overwritten when cloning a clone # Make Svars for granting abilities and triggers on clones distinct to avoid SVars getting overwritten when cloning a clone
K:ETBReplacement:Copy:ChooseCreature:Optional K:ETBReplacement:Copy:DBCopy: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 | 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:DBCopy:DB$ Clone | Defined$ Remembered | AddAbilities$ EvilTwin
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: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 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." 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."

View File

@@ -1,6 +1,6 @@
Name:Heat Shimmer Name:Heat Shimmer
ManaCost:2 R ManaCost:2 R
Types:Sorcery 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 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." 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."

View File

@@ -3,7 +3,7 @@ ManaCost:4
Types:Legendary Artifact Equipment Types:Legendary Artifact Equipment
K:Equip:5 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. 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 DeckHas:Ability$Token
SVar:Picture:http://www.wizards.com/global/images/magic/general/helm_of_the_host.jpg 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} 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}

View File

@@ -3,6 +3,6 @@ ManaCost:2 R R R
Types:Legendary Creature Goblin Shaman Types:Legendary Creature Goblin Shaman
PT:2/2 PT:2/2
K:Haste 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 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. 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.

View File

@@ -3,6 +3,6 @@ ManaCost:4 R R
Types:Sorcery 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. 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: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 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. 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.

View File

@@ -2,7 +2,7 @@ Name:Minion Reflector
ManaCost:5 ManaCost:5
Types:Artifact 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." 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:BuffedBy:Creature
SVar:Picture:http://www.wizards.com/global/images/magic/general/minion_reflector.jpg 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." 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."

View File

@@ -10,6 +10,6 @@ SVar:RemoveEffect:DB$ ChangeZone | Origin$ Command | Destination$ Exile
SVar:X:Count$Valid Artifact.YouCtrl 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. 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. 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. 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.

View File

@@ -2,8 +2,8 @@ Name:Soul Separator
ManaCost:3 ManaCost:3
Types:Artifact 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. 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:DBCopy:DB$ CopyPermanent | Defined$ Remembered | SetPower$ 1 | SetToughness$ 1 | AddTypes$ Spirit | AddKeywords$ 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: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:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:RememberedLKI$CardPower SVar:X:RememberedLKI$CardPower
SVar:Y:RememberedLKI$CardToughness SVar:Y:RememberedLKI$CardToughness

View File

@@ -4,7 +4,7 @@ Types:Enchantment Aura
K:Enchant creature K:Enchant creature
A:SP$ Attach | Cost$ 2 R R | ValidTgts$ Creature | AILogic$ Pump 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." 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:NonStackingAttachEffect:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/splinter_twin.jpg 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." 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."

View File

@@ -2,7 +2,7 @@ Name:Twinflame
ManaCost:1 R ManaCost:1 R
Types:Sorcery Types:Sorcery
K:Strive:2 R 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:MaxTargets:Count$Valid Creature.YouCtrl
SVar:Picture:http://www.wizards.com/global/images/magic/general/twinflame.jpg 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. 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.

View File

@@ -3,12 +3,10 @@ ManaCost:3 U U
Types:Creature Shapeshifter Types:Creature Shapeshifter
PT:0/0 PT:0/0
K:Morph:1 U K:Morph:1 U
K:ETBReplacement:Copy:ChooseCreature:Optional K:ETBReplacement:Copy:DBCopy:Optional
# Make SVars for granting abilities and triggers on clones distinct to avoid SVars getting overwritten when cloning a clone 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: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
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: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 SVar:VesShapeTurn:DB$ SetState | Defined$ Self | Mode$ TurnFace
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." 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 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.) 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.)

View File

@@ -3,7 +3,11 @@ ManaCost:1 U U
Types:Creature Shapeshifter Types:Creature Shapeshifter
PT:0/1 PT:0/1
A:AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card. 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:NeedsOrderedGraveyard:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/volraths_shapeshifter.jpg 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. 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.

View File

@@ -19,7 +19,6 @@ import com.google.common.collect.Sets;
import forge.FThreads; import forge.FThreads;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.card.CardStateName;
import forge.game.GameView; import forge.game.GameView;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView; import forge.game.card.CardView.CardStateView;
@@ -178,8 +177,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
switch (altState.getState()) { switch (altState.getState()) {
case Original: case Original:
final CardStateView currentState = cv.getCurrentState(); if (cv.isFaceDown()) {
if (currentState.getState() == CardStateName.FaceDown) {
return getCurrentPlayer() == null || cv.canFaceDownBeShownToAny(getLocalPlayers()); return getCurrentPlayer() == null || cv.canFaceDownBeShownToAny(getLocalPlayers());
} }
return true; //original can always be shown if not a face down that can't be shown return true; //original can always be shown if not a face down that can't be shown