...
 
Commits (32)
......@@ -1142,7 +1142,7 @@ public abstract class GameState {
} else if (info.startsWith("SummonSick")) {
c.setSickness(true);
} else if (info.startsWith("FaceDown")) {
c.setState(CardStateName.FaceDown, true);
c.turnFaceDown(true);
if (info.endsWith("Manifested")) {
c.setManifested(true);
}
......
......@@ -367,8 +367,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean apply(Card card) {
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
return card.getName().equals("Volrath's Shapeshifter")
|| card.getStates().contains(CardStateName.OriginalText) && card.getState(CardStateName.OriginalText).getName().equals("Volrath's Shapeshifter");
return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter");
}
}).isEmpty()) {
int bestValue = 0;
......
......@@ -12,7 +12,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
......@@ -21,7 +20,6 @@ public class CloneAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Game game = source.getGame();
......@@ -39,27 +37,13 @@ public class CloneAi extends SpellAbilityAi {
// TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?"
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")) {
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
return false;
}
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
return false;
}
// don't activate during main2 unless this effect is permanent
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
return false;
}
PhaseHandler phase = game.getPhaseHandler();
if (null == tgt) {
if (!sa.usesTargeting()) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
......@@ -131,7 +115,7 @@ public class CloneAi extends SpellAbilityAi {
* <p>
* cloneTgtAI.
* </p>
*
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
......@@ -155,7 +139,7 @@ public class CloneAi extends SpellAbilityAi {
// a good target
return false;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
......@@ -178,7 +162,7 @@ public class CloneAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
*
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
* forge.game.player.Player)
......@@ -186,9 +170,13 @@ public class CloneAi extends SpellAbilityAi {
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final Card cloneTarget = getCloneTarget(sa);
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
final boolean isVesuva = "Vesuva".equals(host.getName());
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
......@@ -198,7 +186,8 @@ public class CloneAi extends SpellAbilityAi {
if (!newOptions.isEmpty()) {
options = newOptions;
}
Card choice = ComputerUtilCard.getBestAI(options);
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
if (isVesuva && "Vesuva".equals(choice.getName())) {
choice = null;
}
......@@ -206,4 +195,44 @@ public class CloneAi extends SpellAbilityAi {
return choice;
}
protected Card getCloneTarget(final SpellAbility sa) {
final Card host = sa.getHostCard();
Card tgtCard = host;
if (sa.hasParam("CloneTarget")) {
final List<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;
}
}
......@@ -4,7 +4,6 @@ import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
......@@ -104,7 +103,7 @@ public class ManifestAi extends SpellAbilityAi {
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
// (e.g. Grafdigger's Cage)
Card topCopy = CardUtil.getLKICopy(library.getFirst());
topCopy.setState(CardStateName.FaceDown, false);
topCopy.turnFaceDownNoUpdate();
topCopy.setManifested(true);
final Map<String, Object> repParams = Maps.newHashMap();
......
......@@ -19,10 +19,7 @@ import forge.game.GameObjectMap;
import forge.game.GameRules;
import forge.game.Match;
import forge.game.StaticEffect;
import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CounterType;
import forge.game.card.*;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
import forge.game.keyword.KeywordInterface;
......@@ -267,6 +264,7 @@ public class GameCopier {
System.err.println(sa.toString());
}
}
return newCard;
}
......@@ -295,6 +293,7 @@ public class GameCopier {
newCard.setChangedCardTypes(c.getChangedCardTypesMap());
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
newCard.addHiddenExtrinsicKeyword(kw);
......@@ -305,7 +304,7 @@ public class GameCopier {
if (c.isFaceDown()) {
boolean isCreature = newCard.isCreature();
boolean hasManaCost = !newCard.getManaCost().isNoCost();
newCard.setState(CardStateName.FaceDown, true);
newCard.turnFaceDown(true);
if (c.isManifested()) {
newCard.setManifested(true);
// TODO: Should be able to copy other abilities...
......@@ -335,6 +334,11 @@ public class GameCopier {
}
}
newCard.setFlipped(c.isFlipped());
for (Map.Entry<Long, CardCloneStates> e : c.getCloneStates().entrySet()) {
newCard.addCloneState(e.getValue().copy(newCard, true), e.getKey());
}
Map<CounterType, Integer> counters = c.getCounters();
if (!counters.isEmpty()) {
newCard.setCounters(Maps.newEnumMap(counters));
......
......@@ -194,6 +194,8 @@ public class GameSimulator {
System.out.println();
}
final SpellAbility playingSa = sa;
simGame.copyLastState();
ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() {
@Override
public void run() {
......
......@@ -5,13 +5,12 @@ public enum CardStateName {
Original,
FaceDown,
Flipped,
Cloner,
Transformed,
Meld,
Cloned,
LeftSplit,
RightSplit,
OriginalText; // backup state for cards like Volrath's Shapeshifter
;
/**
* TODO: Write javadoc for this method.
......
......@@ -901,10 +901,8 @@ public class Game {
for (Card c : p.getAllCards()) {
if (c.hasSVar("NeedsOrderedGraveyard")) {
return true;
} else if (c.getStates().contains(CardStateName.OriginalText)) {
if (c.getState(CardStateName.OriginalText).hasSVar("NeedsOrderedGraveyard")) {
return true;
}
} else if (c.getOriginalState(CardStateName.Original).hasSVar("NeedsOrderedGraveyard")) {
return true;
}
}
for (Card c : p.getOpponents().getCardsIn(ZoneType.Battlefield)) {
......
......@@ -193,26 +193,9 @@ public class GameAction {
}
if (!c.isToken()) {
if (c.isCloned()) {
c.switchStates(CardStateName.Original, CardStateName.Cloner, false);
c.setState(CardStateName.Original, false);
c.clearStates(CardStateName.Cloner, false);
if (c.isFlipCard()) {
c.clearStates(CardStateName.Flipped, false);
}
if (c.getStates().contains(CardStateName.OriginalText)) {
c.clearStates(CardStateName.OriginalText, false);
c.removeSVar("GainingTextFrom");
c.removeSVar("GainingTextFromTimestamp");
}
c.updateStateForView();
} else if (c.getStates().contains(CardStateName.OriginalText)) {
// Volrath's Shapeshifter
CardFactory.copyState(c, CardStateName.OriginalText, c, CardStateName.Original, false);
c.setState(CardStateName.Original, false);
c.clearStates(CardStateName.OriginalText, false);
c.removeSVar("GainingTextFrom");
c.removeSVar("GainingTextFromTimestamp");
if (c.isCloned() || c.hasTextChangeState()) {
c.removeCloneStates();
c.removeTextChangeStates();
c.updateStateForView();
}
......@@ -287,18 +270,10 @@ public class GameAction {
// not to battlefield anymore!
toBattlefield = false;
if (copied.isCloned()) {
copied.switchStates(CardStateName.Original, CardStateName.Cloner, false);
copied.setState(CardStateName.Original, false);
copied.clearStates(CardStateName.Cloner, false);
if (copied.isFlipCard()) {
copied.clearStates(CardStateName.Flipped, false);
}
if (copied.getStates().contains(CardStateName.OriginalText)) {
copied.clearStates(CardStateName.OriginalText, false);
copied.removeSVar("GainingTextFrom");
copied.removeSVar("GainingTextFromTimestamp");
}
if (c.isCloned() || c.hasTextChangeState()) {
c.removeCloneStates();
c.removeTextChangeStates();
c.updateStateForView();
}
if (copied.getCurrentStateName() != CardStateName.Original) {
......@@ -858,6 +833,7 @@ public class GameAction {
final Map<StaticAbility, CardCollectionView> affectedPerAbility = Maps.newHashMap();
for (final StaticAbilityLayer layer : StaticAbilityLayer.CONTINUOUS_LAYERS) {
List<StaticAbility> toAdd = Lists.newArrayList();
for (final StaticAbility stAb : staticAbilities) {
final CardCollectionView previouslyAffected = affectedPerAbility.get(stAb);
final CardCollectionView affectedHere;
......@@ -869,8 +845,18 @@ public class GameAction {
} else {
affectedHere = previouslyAffected;
stAb.applyContinuousAbility(layer, previouslyAffected);
}
}
if (affectedHere != null) {
for (final Card c : affectedHere) {
for (final StaticAbility st2 : c.getStaticAbilities()) {
if (!staticAbilities.contains(st2)) {
toAdd.add(st2);
}
}
}
}
}
staticAbilities.addAll(toAdd);
}
for (final CardCollectionView affected : affectedPerAbility.values()) {
......
......@@ -998,6 +998,10 @@ public class StaticEffect {
affectedCard.removeWithFlash(getTimestamp());
}
if (params.containsKey("GainTextOf")) {
affectedCard.removeTextChangeState(getTimestamp());
}
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
}
return affectedCards;
......
......@@ -2,7 +2,6 @@ package forge.game.ability.effects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils;
......@@ -178,7 +177,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
movedCard.setExiledWith(host);
}
if (sa.hasParam("ExileFaceDown")) {
movedCard.setState(CardStateName.FaceDown, true);
movedCard.turnFaceDown(true);
}
if (sa.hasParam("Tapped")) {
movedCard.setTapped(true);
......
......@@ -5,10 +5,8 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
......@@ -29,12 +27,12 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.TextUtil;
import forge.util.collect.*;
import forge.util.Lang;
import forge.util.MessageUtil;
import forge.util.TextUtil;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
......@@ -542,7 +540,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard.updateStateForView();
}
if (sa.hasParam("FaceDown")) {
movedCard.setState(CardStateName.FaceDown, true);
movedCard.turnFaceDown(true);
}
if (sa.hasParam("Attacking")) {
// What should they attack?
......@@ -592,7 +590,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
if (sa.hasParam("ExileFaceDown")) {
movedCard.setState(CardStateName.FaceDown, true);
movedCard.turnFaceDown(true);
}
if (sa.hasParam("TrackDiscarded")) {
......@@ -1050,24 +1048,22 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown") && ZoneType.Battlefield.equals(destination)) {
c.setState(CardStateName.FaceDown, true);
c.turnFaceDown(true);
// set New Pt doesn't work because this values need to be copyable for clone effects
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")) {
if (sa.hasParam("FaceDownPower")) {
c.setBasePower(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownPower"), sa));
}
if (sa.hasParam("FaceDownToughness")) {
c.setBaseToughness(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownToughness"), sa));
}
if (sa.hasParam("FaceDownPower")) {
c.setBasePower(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownPower"), sa));
}
if (sa.hasParam("FaceDownToughness")) {
c.setBaseToughness(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownToughness"), sa));
}
if (sa.hasParam("FaceDownAddType")) {
CardType t = new CardType(c.getCurrentState().getType());
t.addAll(Arrays.asList(sa.getParam("FaceDownAddType").split(",")));
c.getCurrentState().setType(t);
for (String type : sa.getParam("FaceDownAddType").split(",")) {
c.addType(type);
}
}
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
......@@ -1091,7 +1087,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// need to do that again?
if (sa.hasParam("FaceDown") && !ZoneType.Battlefield.equals(destination)) {
movedCard.setState(CardStateName.FaceDown, true);
movedCard.turnFaceDown(true);
}
movedCard.setTimestamp(ts);
}
......@@ -1105,7 +1101,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard.setExiledWith(host);
}
if (sa.hasParam("ExileFaceDown")) {
movedCard.setState(CardStateName.FaceDown, true);
movedCard.turnFaceDown(true);
}
}
else {
......
......@@ -2,16 +2,11 @@ package forge.game.ability.effects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ImageKeys;
import forge.StaticData;
import forge.card.CardRulesPredicates;
import forge.card.CardType;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
......@@ -19,16 +14,12 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
......@@ -259,153 +250,29 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
private Card getProtoType(final SpellAbility sa, final Card original) {
final Card host = sa.getHostCard();
final List<String> keywords = Lists.newArrayList();
final List<String> types = Lists.newArrayList();
final List<String> svars = Lists.newArrayList();
final List<String> triggers = Lists.newArrayList();
boolean asNonLegendary = false;
if (sa.hasParam("Keywords")) {
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
}
if (sa.hasParam("AddTypes")) {
types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & ")));
}
if (sa.hasParam("NonLegendary")) {
asNonLegendary = true;
}
if (sa.hasParam("AddSVars")) {
svars.addAll(Arrays.asList(sa.getParam("AddSVars").split(" & ")));
}
if (sa.hasParam("Triggers")) {
triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(" & ")));
}
final Card copy = CardFactory.copyCopiableCharacteristics(original, sa.getActivatingPlayer());
copy.setToken(true);
copy.setCopiedPermanent(original);
// add keywords from sa
for (final String kw : keywords) {
copy.addIntrinsicKeyword(kw);
}
if (asNonLegendary) {
copy.removeType(CardType.Supertype.Legendary);
}
if (sa.hasParam("SetCreatureTypes")) {
copy.setCreatureTypes(ImmutableList.copyOf(sa.getParam("SetCreatureTypes").split(" ")));
}
if (sa.hasParam("SetColor")) {
copy.setColor(MagicColor.fromName(sa.getParam("SetColor")));
}
for (final String type : types) {
copy.addType(type);
}
for (final String svar : svars) {
String actualsVar = host.getSVar(svar);
String name = svar;
if (actualsVar.startsWith("SVar:")) {
actualsVar = actualsVar.split("SVar:")[1];
name = actualsVar.split(":")[0];
actualsVar = actualsVar.split(":")[1];
}
copy.setSVar(name, actualsVar);
}
for (final String s : triggers) {
final String actualTrigger = host.getSVar(s);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, copy, true);
copy.addTrigger(parsedTrigger);
}
// set power of clone
if (sa.hasParam("SetPower")) {
String rhs = sa.getParam("SetPower");
int power = Integer.MAX_VALUE;
try {
power = Integer.parseInt(rhs);
} catch (final NumberFormatException e) {
power = CardFactoryUtil.xCount(copy, copy.getSVar(rhs));
}
copy.setBasePower(power);
}
// set toughness of clone
if (sa.hasParam("SetToughness")) {
String rhs = sa.getParam("SetToughness");
int toughness = Integer.MAX_VALUE;
try {
toughness = Integer.parseInt(rhs);
} catch (final NumberFormatException e) {
toughness = CardFactoryUtil.xCount(copy, copy.getSVar(rhs));
}
copy.setBaseToughness(toughness);
}
if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), copy);
}
final Player newOwner = sa.getActivatingPlayer();
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
final Card copy = new Card(id, original.getPaperCard(), host.getGame());
copy.setOwner(newOwner);
copy.setSetCode(original.getSetCode());
if (sa.hasParam("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.setStates(CardFactory.getCloneStates(original, copy, sa));
// force update the now set State
copy.setState(copy.getCurrentStateName(), true, true);
copy.setToken(true);
if (sa.hasParam("AtEOTTrig")) {
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), copy);
}
copy.updateStateForView();
return copy;
}
}
package forge.game.ability.effects;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.AbilityUtils;
......@@ -318,7 +317,7 @@ public class DigEffect extends SpellAbilityEffect {
}
if (sa.hasParam("ExileFaceDown")) {
c.setState(CardStateName.FaceDown, true);
c.turnFaceDown(true);
}
if (sa.hasParam("Imprint")) {
host.addImprintedCard(c);
......
......@@ -7,7 +7,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.StaticData;
import forge.card.CardRulesPredicates;
import forge.card.CardStateName;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.game.Game;
......@@ -49,8 +48,10 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
for (byte i = 0; i < MagicColor.WUBRG.length; i++) {
if (color.hasAnyColor(MagicColor.WUBRG[i])) {
landNames.add(MagicColor.Constant.BASIC_LANDS.get(i));
landNames.add(MagicColor.Constant.SNOW_LANDS.get(i));
}
}
final Predicate<PaperCard> cp = Predicates.compose(new Predicate<String>() {
@Override
public boolean apply(final String name) {
......@@ -69,15 +70,9 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
random = CardFactory.getCard(ran, activator, game);
}
final String imageFileName = game.getRules().canCloneUseTargetsImage() ? source.getImageKey() : random.getImageKey();
source.addAlternateState(CardStateName.Cloner, false);
source.switchStates(CardStateName.Original, CardStateName.Cloner, false);
source.setState(CardStateName.Original, false);
source.addCloneState(CardFactory.getCloneStates(random, source, sa), game.getNextTimestamp());
source.updateStateForView();
final CardStateName stateToCopy = random.getCurrentStateName();
CardFactory.copyState(random, stateToCopy, source, source.getCurrentStateName());
source.setImageKey(imageFileName);
source.setController(activator, 0);
game.getAction().moveTo(activator.getZone(ZoneType.Battlefield), source, sa);
......
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;
}
}
......@@ -101,16 +101,18 @@ public class CardFactoryUtil {
@Override
public void resolve() {
Card c = hostCard.getGame().getAction().moveToPlay(hostCard, this);
c.setPreFaceDownState(CardStateName.Original);
hostCard.getGame().getAction().moveToPlay(hostCard, this);
//c.setPreFaceDownState(CardStateName.Original);
}
@Override
public boolean canPlay() {
CardStateName stateBackup = hostCard.getCurrentStateName();
hostCard.setState(CardStateName.FaceDown, false);
boolean face = hostCard.isFaceDown();
hostCard.turnFaceDownNoUpdate();
boolean success = super.canPlay();
hostCard.setState(stateBackup, false);
hostCard.setFaceDown(face);
return success;
}
};
......
......@@ -526,7 +526,12 @@ public class CardState extends GameObject {
}
}
public CardState copy(final Card host, CardStateName name, final boolean lki) {
CardState result = new CardState(host, name);
result.copyFrom(this, lki);
return result;
}
public CardRarity getRarity() {
return rarity;
}
......
......@@ -227,12 +227,17 @@ public final class CardUtil {
// used for the purpose of cards that care about the zone the card was known to be in last
newCopy.setLastKnownZone(in.getLastKnownZone());
newCopy.getCurrentState().copyFrom(in.getState(in.getCurrentStateName()), true);
newCopy.getCurrentState().copyFrom(in.getState(in.getFaceupCardStateName()), true);
if (in.isFaceDown()) {
newCopy.turnFaceDownNoUpdate();
}
/*
if (in.isCloned()) {
newCopy.addAlternateState(CardStateName.Cloner, false);
newCopy.getState(CardStateName.Cloner).copyFrom(in.getState(CardStateName.Cloner), true);
}
//*/
newCopy.setType(new CardType(in.getType()));
newCopy.setToken(in.isToken());
......@@ -327,7 +332,7 @@ public final class CardUtil {
final CardType type = new CardType();
type.add("Creature");
final CardState ret = new CardState(c.getView().createAlternateState(CardStateName.FaceDown), c);
final CardState ret = new CardState(c, CardStateName.FaceDown);
ret.setBasePower(2);
ret.setBaseToughness(2);
......
......@@ -113,7 +113,7 @@ public class CardView extends GameEntityView {
}
public boolean isFaceDown() {
return getCurrentState().getState() == CardStateName.FaceDown;
return get(TrackableProperty.Facedown);// getCurrentState().getState() == CardStateName.FaceDown;
}
public boolean isFlipCard() {
......@@ -121,16 +121,18 @@ public class CardView extends GameEntityView {
}
public boolean isFlipped() {
return getCurrentState().getState() == CardStateName.Flipped;
return get(TrackableProperty.Flipped); // getCurrentState().getState() == CardStateName.Flipped;
}
public boolean isSplitCard() {
return get(TrackableProperty.SplitCard);
}
/*
public boolean isTransformed() {
return getCurrentState().getState() == CardStateName.Transformed;
}
//*/
public boolean isAttacking() {
return get(TrackableProperty.Attacking);
......@@ -627,7 +629,9 @@ public class CardView extends GameEntityView {
set(TrackableProperty.SplitCard, isSplitCard);
set(TrackableProperty.FlipCard, c.isFlipCard());
CardStateView cloner = CardView.getState(c, CardStateName.Cloner);
final Card cloner = c.getCloner();
//CardStateView cloner = CardView.getState(c, CardStateName.Cloner);
set(TrackableProperty.Cloner, cloner == null ? null : cloner.getName() + " (" + cloner.getId() + ")");
CardState currentState = c.getCurrentState();
......@@ -699,7 +703,7 @@ public class CardView extends GameEntityView {
if (name.isEmpty()) {
CardStateView alternate = getAlternateState();
if (alternate != null) {
if (this.getCurrentState().getState() == CardStateName.FaceDown) {
if (isFaceDown()) {
return "Face-down card (H" + getHiddenId() + ")";
} else {
return getAlternateState().getName() + " (" + getId() + ")";
......
......@@ -42,7 +42,7 @@ public class CostAdjustment {
boolean isStateChangeToFaceDown = false;
if (sa.isSpell() && ((Spell) sa).isCastFaceDown()) {
// Turn face down to apply cost modifiers correctly
host.setState(CardStateName.FaceDown, false);
host.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true;
} // isSpell
......@@ -83,6 +83,7 @@ public class CostAdjustment {
// Reset card state (if changed)
if (isStateChangeToFaceDown) {
host.setState(CardStateName.Original, false);
host.setFaceDown(false);
}
return result;
}
......
......@@ -151,6 +151,11 @@ public class TargetChoices implements Cloneable {
return sb.toString();
}
@Override
public final String toString() {
return this.getTargetedString();
}
public final boolean isTargetingAnyCard() {
return !targetCards.isEmpty();
}
......
......@@ -19,7 +19,6 @@ package forge.game.staticability;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.card.MagicColor;
import forge.game.CardTraitBase;
import forge.game.Game;
......@@ -200,23 +199,6 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
String desc = getParam("Description");
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;
} else {
return "";
......
......@@ -20,7 +20,6 @@ package forge.game.staticability;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GlobalRuleChange;
......@@ -502,67 +501,10 @@ public final class StaticAbilityContinuous {
// Gain text from another card
if (layer == StaticAbilityLayer.TEXT) {
// Make no changes in case the target for the ability is still the same as before
boolean noChange = false;
if (gainTextSource != null && affectedCard.hasSVar("GainingTextFrom") && affectedCard.hasSVar("GainingTextFromTimestamp")
&& 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()));
}
if (gainTextSource != null) {
affectedCard.addTextChangeState(
CardFactory.getCloneStates(gainTextSource, affectedCard, stAb), se.getTimestamp()
);
}
}
......
......@@ -3,6 +3,8 @@ package forge.game.staticability;
import com.google.common.collect.ImmutableList;
public enum StaticAbilityLayer {
/** Layer 1 for control-changing effects. */
COPY,
/** Layer 2 for control-changing effects. */
CONTROL,
......@@ -35,5 +37,5 @@ public enum StaticAbilityLayer {
RULES;
public final static ImmutableList<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);
}
......@@ -232,7 +232,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
}
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;
}
......
......@@ -16,13 +16,20 @@ public enum TrackableProperty {
CurrentPlane(TrackableTypes.StringType),
PlanarPlayer(TrackableTypes.PlayerViewType),
//Card
//Card
Owner(TrackableTypes.PlayerViewType),
Controller(TrackableTypes.PlayerViewType),
Zone(TrackableTypes.EnumType(ZoneType.class)),
Flipped(TrackableTypes.BooleanType),
Facedown(TrackableTypes.BooleanType),
//TODO?
Cloner(TrackableTypes.StringType),
Cloned(TrackableTypes.BooleanType),
FlipCard(TrackableTypes.BooleanType),
SplitCard(TrackableTypes.BooleanType),
Attacking(TrackableTypes.BooleanType),
Blocking(TrackableTypes.BooleanType),
PhasedOut(TrackableTypes.BooleanType),
......@@ -47,7 +54,7 @@ public enum TrackableProperty {
EncodedCards(TrackableTypes.CardViewCollectionType),
GainControlTargets(TrackableTypes.CardViewCollectionType),
CloneOrigin(TrackableTypes.CardViewType),
Cloner(TrackableTypes.StringType),
ImprintedCards(TrackableTypes.CardViewCollectionType),
HauntedBy(TrackableTypes.CardViewCollectionType),
Haunting(TrackableTypes.CardViewType),
......