Merge branch 'svarsOriginalCardState' into 'master'

CardTraitBase: store the CardState where it was generated from

Closes #1738

See merge request core-developers/forge!4019
This commit is contained in:
Michael Kamensky
2021-02-26 15:02:19 +00:00
23 changed files with 166 additions and 156 deletions

View File

@@ -1396,6 +1396,7 @@ public class AiController {
final List<SpellAbility> abilities = Lists.newArrayList(); final List<SpellAbility> abilities = Lists.newArrayList();
LandAbility la = new LandAbility(land, player, null); LandAbility la = new LandAbility(land, player, null);
la.setCardState(land.getCurrentState());
if (la.canPlay()) { if (la.canPlay()) {
abilities.add(la); abilities.add(la);
} }
@@ -1403,6 +1404,7 @@ public class AiController {
// add mayPlay option // add mayPlay option
for (CardPlayOption o : land.mayPlay(player)) { for (CardPlayOption o : land.mayPlay(player)) {
la = new LandAbility(land, player, o.getAbility()); la = new LandAbility(land, player, o.getAbility());
la.setCardState(land.getCurrentState());
if (la.canPlay()) { if (la.canPlay()) {
abilities.add(la); abilities.add(la);
} }

View File

@@ -5,6 +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.card.CardStateName;
import forge.card.CardType; import forge.card.CardType;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
@@ -1837,7 +1839,7 @@ public class ComputerUtilCard {
public static AiPlayDecision checkNeedsToPlayReqs(final Card card, final SpellAbility sa) { public static AiPlayDecision checkNeedsToPlayReqs(final Card card, final SpellAbility sa) {
Game game = card.getGame(); Game game = card.getGame();
boolean isRightSplit = sa != null && sa.getCardState() != null; boolean isRightSplit = sa != null && sa.getCardState().getStateName() == CardStateName.RightSplit;
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay"; String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar"; String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";

View File

@@ -1,11 +1,13 @@
package forge.game; package forge.game;
import forge.card.CardStateName;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaAtom; import forge.card.mana.ManaAtom;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardState;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.card.IHasCardView; import forge.game.card.IHasCardView;
@@ -29,8 +31,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
/** The host card. */ /** The host card. */
protected Card hostCard; protected Card hostCard;
protected CardState cardState = null;
private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one)
/** The map params. */ /** The map params. */
protected Map<String, String> originalMapParams = Maps.newHashMap(), protected Map<String, String> originalMapParams = Maps.newHashMap(),
@@ -468,9 +469,8 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
} }
protected IHasSVars getSVarFallback() { protected IHasSVars getSVarFallback() {
if (getOriginalHost() != null) { if (getCardState() != null)
return getOriginalHost(); return getCardState();
}
return getHostCard(); return getHostCard();
} }
@@ -520,11 +520,30 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
sVars.remove(var); sVars.remove(var);
} }
public Card getOriginalHost() { public CardState getCardState() {
return grantorCard; return cardState;
} }
public void setOriginalHost(final Card c) { public void setCardState(CardState state) {
grantorCard = c; this.cardState = state;
}
public CardStateName getCardStateName() {
if (this.getCardState() == null) {
return null;
}
return getCardState().getView().getState();
}
public Card getOriginalHost() {
if (getCardState() != null)
return getCardState().getCard();
return null;
}
public boolean isCopiedTrait() {
if (this.getCardState() == null) {
return false;
}
return !getHostCard().equals(getCardState().getCard());
} }
public Map<String, String> getChangedTextColors() { public Map<String, String> getChangedTextColors() {
@@ -580,6 +599,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
copy.originalMapParams = Maps.newHashMap(originalMapParams); copy.originalMapParams = Maps.newHashMap(originalMapParams);
copy.mapParams = Maps.newHashMap(originalMapParams); copy.mapParams = Maps.newHashMap(originalMapParams);
copy.setSVars(sVars); copy.setSVars(sVars);
copy.setCardState(cardState);
// dont use setHostCard to not trigger the not copied parts yet // dont use setHostCard to not trigger the not copied parts yet
copy.hostCard = host; copy.hostCard = host;
} }

View File

@@ -4,6 +4,7 @@ import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -27,15 +28,13 @@ public abstract class TriggerReplacementBase extends CardTraitBase implements II
} }
@Override @Override
public void setOriginalHost(Card c) { public void setCardState(CardState state) {
super.setOriginalHost(c); super.setCardState(state);
if (overridingAbility != null) { if (overridingAbility != null) {
overridingAbility.setOriginalHost(c); overridingAbility.setCardState(state);
} }
} }
public Set<ZoneType> getActiveZone() { public Set<ZoneType> getActiveZone() {
return validHostZones; return validHostZones;
} }

View File

@@ -226,6 +226,7 @@ public final class AbilityFactory {
msg.append(". Looking for API: ").append(api); msg.append(". Looking for API: ").append(api);
throw new RuntimeException(msg.toString()); throw new RuntimeException(msg.toString());
} }
spellAbility.setCardState(state);
if (mapParams.containsKey("Forecast")) { if (mapParams.containsKey("Forecast")) {
spellAbility.putParam("ActivationZone", "Hand"); spellAbility.putParam("ActivationZone", "Hand");

View File

@@ -1614,7 +1614,7 @@ public class AbilityUtils {
// If the chosen creature has X in its mana cost, that X is considered to be 0. // If the chosen creature has X in its mana cost, that X is considered to be 0.
// The value of X in Altered Egos last ability will be whatever value was chosen for X while casting Altered Ego. // The value of X in Altered Egos last ability will be whatever value was chosen for X while casting Altered Ego.
if (sa.getOriginalHost() != null || !sa.getHostCard().equals(c)) { if (sa.isCopiedTrait() || !sa.getHostCard().equals(c)) {
return CardFactoryUtil.doXMath(0, expr, c); return CardFactoryUtil.doXMath(0, expr, c);
} }
@@ -2063,7 +2063,6 @@ public class AbilityUtils {
subAbility.setActivatingPlayer(sa.getActivatingPlayer()); subAbility.setActivatingPlayer(sa.getActivatingPlayer());
subAbility.setHostCard(sa.getHostCard()); subAbility.setHostCard(sa.getHostCard());
subAbility.setOriginalHost(c);
//add the spliced ability to the end of the chain //add the spliced ability to the end of the chain
sa.appendSubAbility(subAbility); sa.appendSubAbility(subAbility);

View File

@@ -431,7 +431,7 @@ public abstract class SpellAbilityEffect {
+ " exile it instead of putting it anywhere else."; + " exile it instead of putting it anywhere else.";
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone; String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true, null); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
re.setLayer(ReplacementLayer.Other); re.setLayer(ReplacementLayer.Other);
re.setOverridingAbility(AbilityFactory.getAbility(effect, eff)); re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));

View File

@@ -152,7 +152,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
final List<SpellAbility> addedAbilities = Lists.newArrayList(); final List<SpellAbility> addedAbilities = Lists.newArrayList();
for (final String s : abilities) { for (final String s : abilities) {
SpellAbility sSA = AbilityFactory.getAbility(c, s, sa); SpellAbility sSA = AbilityFactory.getAbility(c, s, sa);
sSA.setOriginalHost(source);
addedAbilities.add(sSA); addedAbilities.add(sSA);
} }
@@ -160,7 +159,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
final List<Trigger> addedTriggers = Lists.newArrayList(); final List<Trigger> addedTriggers = Lists.newArrayList();
for (final String s : triggers) { for (final String s : triggers) {
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa); final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa);
parsedTrigger.setOriginalHost(source);
addedTriggers.add(parsedTrigger); addedTriggers.add(parsedTrigger);
} }
@@ -174,7 +172,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
// itself a static ability) // itself a static ability)
final List<StaticAbility> addedStaticAbilities = Lists.newArrayList(); final List<StaticAbility> addedStaticAbilities = Lists.newArrayList();
for (final String s : stAbs) { for (final String s : stAbs) {
addedStaticAbilities.add(new StaticAbility(AbilityUtils.getSVar(sa, s), c)); addedStaticAbilities.add(new StaticAbility(AbilityUtils.getSVar(sa, s), c, sa.getCardState()));
} }
final GameCommand unanimate = new GameCommand() { final GameCommand unanimate = new GameCommand() {

View File

@@ -251,7 +251,7 @@ public class PlayEffect extends SpellAbilityEffect {
tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(tgtCard, sas); tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(tgtCard, sas);
} else { } else {
// For Illusionary Mask effect // For Illusionary Mask effect
tgtSA = CardFactoryUtil.abilityMorphDown(tgtCard); tgtSA = CardFactoryUtil.abilityMorphDown(tgtCard.getCurrentState());
} }
// in case player canceled from choice dialog // in case player canceled from choice dialog
if (tgtSA == null) { if (tgtSA == null) {

View File

@@ -2227,7 +2227,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
String sAbility = formatSpellAbility(sa); String sAbility = formatSpellAbility(sa);
// add Adventure to AbilityText // add Adventure to AbilityText
if (sa.isAdventure() && state.getView().getState().equals(CardStateName.Original)) { if (sa.isAdventure() && state.getStateName().equals(CardStateName.Original)) {
CardState advState = getState(CardStateName.Adventure); CardState advState = getState(CardStateName.Adventure);
StringBuilder sbSA = new StringBuilder(); StringBuilder sbSA = new StringBuilder();
sbSA.append(Localizer.getInstance().getMessage("lblAdventure")); sbSA.append(Localizer.getInstance().getMessage("lblAdventure"));
@@ -4388,7 +4388,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public final StaticAbility addStaticAbility(final String s) { public final StaticAbility addStaticAbility(final String s) {
if (!s.trim().isEmpty()) { if (!s.trim().isEmpty()) {
final StaticAbility stAb = new StaticAbility(s, this); final StaticAbility stAb = new StaticAbility(s, this, currentState);
stAb.setIntrinsic(true); stAb.setIntrinsic(true);
currentState.addStaticAbility(stAb); currentState.addStaticAbility(stAb);
return stAb; return stAb;
@@ -6326,11 +6326,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public void setSplitStateToPlayAbility(final SpellAbility sa) { public void setSplitStateToPlayAbility(final SpellAbility sa) {
if (isFaceDown()) {
return;
}
if (sa.isBestow()) { if (sa.isBestow()) {
animateBestow(); animateBestow();
} }
CardStateName stateName = sa.getCardState(); CardStateName stateName = sa.getCardStateName();
if (hasState(stateName)) { if (stateName != null && hasState(stateName) && this.getCurrentStateName() != stateName) {
setState(stateName, true); setState(stateName, true);
// need to set backSide value according to the SplitType // need to set backSide value according to the SplitType
if (hasBackSide()) { if (hasBackSide()) {
@@ -6356,6 +6359,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
public List<SpellAbility> getAllPossibleAbilities(final Player player, final boolean removeUnplayable) { public List<SpellAbility> getAllPossibleAbilities(final Player player, final boolean removeUnplayable) {
CardState oState = getState(CardStateName.Original);
// this can only be called by the Human // this can only be called by the Human
final List<SpellAbility> abilities = Lists.newArrayList(); final List<SpellAbility> abilities = Lists.newArrayList();
for (SpellAbility sa : getSpellAbilities()) { for (SpellAbility sa : getSpellAbilities()) {
@@ -6382,7 +6386,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
if (isInPlay() && isFaceDown() && isManifested()) { if (isInPlay() && isFaceDown() && isManifested()) {
CardState oState = getState(CardStateName.Original);
ManaCost cost = oState.getManaCost(); ManaCost cost = oState.getManaCost();
if (oState.getType().isCreature()) { if (oState.getType().isCreature()) {
abilities.add(CardFactoryUtil.abilityManifestFaceUp(this, cost)); abilities.add(CardFactoryUtil.abilityManifestFaceUp(this, cost));
@@ -6409,6 +6412,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
if (getState(CardStateName.Original).getType().isLand()) { if (getState(CardStateName.Original).getType().isLand()) {
LandAbility la = new LandAbility(this, player, null); LandAbility la = new LandAbility(this, player, null);
la.setCardState(oState);
if (la.canPlay()) { if (la.canPlay()) {
abilities.add(la); abilities.add(la);
} }
@@ -6433,6 +6437,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// extra for MayPlay // extra for MayPlay
for (CardPlayOption o : source.mayPlay(player)) { for (CardPlayOption o : source.mayPlay(player)) {
la = new LandAbility(this, player, o.getAbility()); la = new LandAbility(this, player, o.getAbility());
la.setCardState(oState);
if (la.canPlay()) { if (la.canPlay()) {
abilities.add(la); abilities.add(la);
} }
@@ -6449,9 +6454,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} }
if (isModal() && hasState(CardStateName.Modal)) { if (isModal() && hasState(CardStateName.Modal)) {
if (getState(CardStateName.Modal).getType().isLand()) { CardState modal = getState(CardStateName.Modal);
if (modal.getType().isLand()) {
LandAbility la = new LandAbility(this, player, null); LandAbility la = new LandAbility(this, player, null);
la.setCardState(CardStateName.Modal); la.setCardState(modal);
Card source = CardUtil.getLKICopy(this); Card source = CardUtil.getLKICopy(this);
boolean lkicheck = true; boolean lkicheck = true;
@@ -6483,7 +6489,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// extra for MayPlay // extra for MayPlay
for (CardPlayOption o : source.mayPlay(player)) { for (CardPlayOption o : source.mayPlay(player)) {
la = new LandAbility(this, player, o.getAbility()); la = new LandAbility(this, player, o.getAbility());
la.setCardState(CardStateName.Modal); la.setCardState(modal);
if (la.canPlay(source)) { if (la.canPlay(source)) {
abilities.add(la); abilities.add(la);
} }

View File

@@ -29,7 +29,6 @@ import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
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.staticability.StaticAbility;
@@ -258,7 +257,7 @@ public class CardFactory {
// ************** Link to different CardFactories ******************* // ************** Link to different CardFactories *******************
if (state == CardStateName.LeftSplit || state == CardStateName.RightSplit) { if (state == CardStateName.LeftSplit || state == CardStateName.RightSplit) {
for (final SpellAbility sa : card.getSpellAbilities()) { for (final SpellAbility sa : card.getSpellAbilities()) {
sa.setCardState(state); sa.setCardState(card.getState(state));
} }
CardFactoryUtil.setupKeywordedAbilities(card); CardFactoryUtil.setupKeywordedAbilities(card);
final CardState original = card.getState(CardStateName.Original); final CardState original = card.getState(CardStateName.Original);
@@ -366,9 +365,9 @@ public class CardFactory {
for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true)); for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true, c.getCurrentState()));
for (String s : face.getStaticAbilities()) c.addStaticAbility(s); for (String s : face.getStaticAbilities()) c.addStaticAbility(s);
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true)); for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true, c.getCurrentState()));
// keywords not before variables // keywords not before variables
c.addIntrinsicKeywords(face.getKeywords(), false); c.addIntrinsicKeywords(face.getKeywords(), false);
@@ -401,9 +400,9 @@ public class CardFactory {
SpellAbility sa = new SpellPermanent(c); SpellAbility sa = new SpellPermanent(c);
// Currently only for Modal, might react different when state is always set // Currently only for Modal, might react different when state is always set
if (c.getCurrentStateName() == CardStateName.Modal) { //if (c.getCurrentStateName() == CardStateName.Modal) {
sa.setCardState(c.getCurrentStateName()); sa.setCardState(c.getCurrentState());
} //}
c.addSpellAbility(sa); c.addSpellAbility(sa);
} }
// TODO add LandAbility there when refactor MayPlay // TODO add LandAbility there when refactor MayPlay
@@ -678,7 +677,6 @@ public class CardFactory {
if (origSVars.containsKey(s)) { if (origSVars.containsKey(s)) {
final String actualTrigger = origSVars.get(s); final String actualTrigger = origSVars.get(s);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true); final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true);
parsedTrigger.setOriginalHost(host);
state.addTrigger(parsedTrigger); state.addTrigger(parsedTrigger);
} }
} }
@@ -702,7 +700,6 @@ public class CardFactory {
if (origSVars.containsKey(s)) { if (origSVars.containsKey(s)) {
final String actualAbility = origSVars.get(s); final String actualAbility = origSVars.get(s);
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, out); final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, out);
grantedAbility.setOriginalHost(host);
grantedAbility.setIntrinsic(true); grantedAbility.setIntrinsic(true);
state.addSpellAbility(grantedAbility); state.addSpellAbility(grantedAbility);
} }
@@ -715,8 +712,7 @@ public class CardFactory {
for (final String s : str.split(",")) { for (final String s : str.split(",")) {
if (origSVars.containsKey(s)) { if (origSVars.containsKey(s)) {
final String actualStatic = origSVars.get(s); final String actualStatic = origSVars.get(s);
final StaticAbility grantedStatic = new StaticAbility(actualStatic, out); final StaticAbility grantedStatic = new StaticAbility(actualStatic, out, sa.getCardState());
grantedStatic.setOriginalHost(host);
grantedStatic.setIntrinsic(true); grantedStatic.setIntrinsic(true);
state.addStaticAbility(grantedStatic); state.addStaticAbility(grantedStatic);
} }
@@ -765,26 +761,6 @@ public class CardFactory {
// set the host card for copied replacement effects // set the host card for copied replacement effects
// needed for copied xPaid ETB effects (for the copy, xPaid = 0) // 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);
}
}
for (final Trigger trigger : state.getTriggers()) {
final SpellAbility newSa = trigger.getOverridingAbility();
if (newSa != null && newSa.getOriginalHost() == null) {
newSa.setOriginalHost(in);
}
}
if (sa.hasParam("GainTextOf") && originalState != null) { if (sa.hasParam("GainTextOf") && originalState != null) {
state.setSetCode(originalState.getSetCode()); state.setSetCode(originalState.getSetCode());

View File

@@ -94,8 +94,8 @@ public class CardFactoryUtil {
* a {@link forge.game.card.Card} object. * a {@link forge.game.card.Card} object.
* @return a {@link forge.game.spellability.SpellAbility} object. * @return a {@link forge.game.spellability.SpellAbility} object.
*/ */
public static SpellAbility abilityMorphDown(final Card sourceCard) { public static SpellAbility abilityMorphDown(final CardState cardState) {
final Spell morphDown = new Spell(sourceCard, new Cost(ManaCost.THREE, false)) { final Spell morphDown = new Spell(cardState.getCard(), new Cost(ManaCost.THREE, false)) {
private static final long serialVersionUID = -1438810964807867610L; private static final long serialVersionUID = -1438810964807867610L;
@Override @Override
@@ -119,6 +119,8 @@ public class CardFactoryUtil {
} }
}; };
morphDown.setCardState(cardState);
morphDown.setDescription("(You may cast this card face down as a 2/2 creature for {3}.)"); morphDown.setDescription("(You may cast this card face down as a 2/2 creature for {3}.)");
morphDown.setStackDescription("Morph - Creature 2/2"); morphDown.setStackDescription("Morph - Creature 2/2");
morphDown.setCastFaceDown(true); morphDown.setCastFaceDown(true);
@@ -136,7 +138,7 @@ public class CardFactoryUtil {
* a {@link forge.game.card.Card} object. * a {@link forge.game.card.Card} object.
* @return a {@link forge.game.spellability.AbilityActivated} object. * @return a {@link forge.game.spellability.AbilityActivated} object.
*/ */
public static SpellAbility abilityMorphUp(final Card sourceCard, final String costStr, final boolean mega) { public static SpellAbility abilityMorphUp(final CardState cardState, final String costStr, final boolean mega) {
Cost cost = new Cost(costStr, true); Cost cost = new Cost(costStr, true);
String costDesc = cost.toString(); String costDesc = cost.toString();
StringBuilder sbCost = new StringBuilder(mega ? "Megamorph" : "Morph"); StringBuilder sbCost = new StringBuilder(mega ? "Megamorph" : "Morph");
@@ -156,15 +158,15 @@ public class CardFactoryUtil {
sb.append(" | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its morph cost.)"); sb.append(" | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its morph cost.)");
final SpellAbility morphUp = AbilityFactory.getAbility(sb.toString(), sourceCard); final SpellAbility morphUp = AbilityFactory.getAbility(sb.toString(), cardState);
// if Cost has X in cost, need to check source for an SVar for this // if Cost has X in cost, need to check source for an SVar for this
if (cost.hasXInAnyCostPart() && sourceCard.hasSVar("X")) { if (cost.hasXInAnyCostPart() && cardState.hasSVar("X")) {
morphUp.setSVar("X", sourceCard.getSVar("X")); morphUp.setSVar("X", cardState.getSVar("X"));
} }
final StringBuilder sbStack = new StringBuilder(); final StringBuilder sbStack = new StringBuilder();
sbStack.append(sourceCard.getName()).append(" - turn this card face up."); sbStack.append(cardState.getName()).append(" - turn this card face up.");
morphUp.setStackDescription(sbStack.toString()); morphUp.setStackDescription(sbStack.toString());
return morphUp; return morphUp;
@@ -2098,6 +2100,7 @@ public class CardFactoryUtil {
final SpellAbility intrinsicAbility = AbilityFactory.getAbility(rawAbility, card); final SpellAbility intrinsicAbility = AbilityFactory.getAbility(rawAbility, card);
card.addSpellAbility(intrinsicAbility); card.addSpellAbility(intrinsicAbility);
intrinsicAbility.setIntrinsic(true); intrinsicAbility.setIntrinsic(true);
intrinsicAbility.setCardState(card.getCurrentState());
} catch (Exception e) { } catch (Exception e) {
String msg = "CardFactoryUtil:addAbilityFactoryAbilities: crash in raw Ability"; String msg = "CardFactoryUtil:addAbilityFactoryAbilities: crash in raw Ability";
Sentry.getContext().recordBreadcrumb( Sentry.getContext().recordBreadcrumb(
@@ -2139,16 +2142,17 @@ public class CardFactoryUtil {
} }
} }
private static ReplacementEffect createETBReplacement(final Card card, ReplacementLayer layer, private static ReplacementEffect createETBReplacement(final CardState card, ReplacementLayer layer,
final String effect, final boolean optional, final boolean secondary, final String effect, final boolean optional, final boolean secondary,
final boolean intrinsic, final String valid, final String zone) { final boolean intrinsic, final String valid, final String zone) {
SpellAbility repAb = AbilityFactory.getAbility(effect, card); SpellAbility repAb = AbilityFactory.getAbility(effect, card);
return createETBReplacement(card, layer, repAb, optional, secondary, intrinsic, valid, zone); return createETBReplacement(card, layer, repAb, optional, secondary, intrinsic, valid, zone);
} }
private static ReplacementEffect createETBReplacement(final Card card, ReplacementLayer layer, private static ReplacementEffect createETBReplacement(final CardState card, ReplacementLayer layer,
final SpellAbility repAb, final boolean optional, final boolean secondary, final SpellAbility repAb, final boolean optional, final boolean secondary,
final boolean intrinsic, final String valid, final String zone) { final boolean intrinsic, final String valid, final String zone) {
Card host = card.getCard();
String desc = repAb.getDescription(); String desc = repAb.getDescription();
setupETBReplacementAbility(repAb); setupETBReplacementAbility(repAb);
if (!intrinsic) { if (!intrinsic) {
@@ -2169,14 +2173,14 @@ public class CardFactoryUtil {
repEffsb.append(" | ActiveZones$ ").append(zone); repEffsb.append(" | ActiveZones$ ").append(zone);
} }
ReplacementEffect re = ReplacementHandler.parseReplacement(repEffsb.toString(), card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repEffsb.toString(), host, intrinsic, card);
re.setLayer(layer); re.setLayer(layer);
re.setOverridingAbility(repAb); re.setOverridingAbility(repAb);
return re; return re;
} }
public static ReplacementEffect makeEtbCounter(final String kw, final Card card, final boolean intrinsic) public static ReplacementEffect makeEtbCounter(final String kw, final CardState card, final boolean intrinsic)
{ {
String parse = kw; String parse = kw;
@@ -2212,7 +2216,7 @@ public class CardFactoryUtil {
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield " String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
+ "| Secondary$ True | Description$ " + desc + (!extraparams.equals("") ? " | " + extraparams : ""); + "| Secondary$ True | Description$ " + desc + (!extraparams.equals("") ? " | " + extraparams : "");
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card.getCard(), intrinsic, card);
re.setOverridingAbility(sa); re.setOverridingAbility(sa);
@@ -3342,10 +3346,11 @@ public class CardFactoryUtil {
} }
} }
public static void addReplacementEffect(final KeywordInterface inst, final Card card, final boolean intrinsic) { public static void addReplacementEffect(final KeywordInterface inst, final CardState card, final boolean intrinsic) {
Card host = card.getCard();
String keyword = inst.getOriginal(); String keyword = inst.getOriginal();
if (keyword.equals("Aftermath") && card.getCurrentStateName().equals(CardStateName.RightSplit)) { if (keyword.equals("Aftermath") && card.getStateName().equals(CardStateName.RightSplit)) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile "); sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");
sb.append("| ValidStackSa$ Spell.Aftermath | Description$ Aftermath"); sb.append("| ValidStackSa$ Spell.Aftermath | Description$ Aftermath");
@@ -3362,7 +3367,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(intrinsic); saExile.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile); re.setOverridingAbility(saExile);
@@ -3408,7 +3413,7 @@ public class CardFactoryUtil {
saReveal.setIntrinsic(intrinsic); saReveal.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, host, intrinsic, card);
re.setOverridingAbility(saReveal); re.setOverridingAbility(saReveal);
@@ -3460,7 +3465,7 @@ public class CardFactoryUtil {
saReturn.setIntrinsic(intrinsic); saReturn.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saReturn); re.setOverridingAbility(saReturn);
@@ -3483,7 +3488,7 @@ public class CardFactoryUtil {
saMill.setIntrinsic(intrinsic); saMill.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, host, intrinsic, card);
re.setOverridingAbility(saMill); re.setOverridingAbility(saMill);
re.setSVar("DredgeCheckLib", "Count$ValidLibrary Card.YouOwn"); re.setSVar("DredgeCheckLib", "Count$ValidLibrary Card.YouOwn");
@@ -3513,7 +3518,7 @@ public class CardFactoryUtil {
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | Secondary$ True | Description$ Devour " + magnitude + " ("+ inst.getReminderText() + ")"; String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | Secondary$ True | Description$ Devour " + magnitude + " ("+ inst.getReminderText() + ")";
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
setupETBReplacementAbility(cleanupSA); setupETBReplacementAbility(cleanupSA);
re.setOverridingAbility(sacrificeSA); re.setOverridingAbility(sacrificeSA);
@@ -3563,7 +3568,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(false); saExile.setIntrinsic(false);
} }
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile); re.setOverridingAbility(saExile);
@@ -3598,7 +3603,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(false); saExile.setIntrinsic(false);
} }
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile); re.setOverridingAbility(saExile);
@@ -3607,7 +3612,7 @@ public class CardFactoryUtil {
// Set Madness Replacement effects // Set Madness Replacement effects
String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | Secondary$ True " String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | Secondary$ True "
+ " | Description$ Madness: If you discard this card, discard it into exile."; + " | Description$ Madness: If you discard this card, discard it into exile.";
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
String sVarMadness = "DB$ Discard | Defined$ ReplacedPlayer | Mode$ Defined | DefinedCards$ ReplacedCard | Madness$ True"; String sVarMadness = "DB$ Discard | Defined$ ReplacedPlayer | Mode$ Defined | DefinedCards$ ReplacedCard | Madness$ True";
re.setOverridingAbility(AbilityFactory.getAbility(sVarMadness, card)); re.setOverridingAbility(AbilityFactory.getAbility(sVarMadness, card));
@@ -3657,7 +3662,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(false); saExile.setIntrinsic(false);
} }
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile); re.setOverridingAbility(saExile);
@@ -3704,7 +3709,7 @@ public class CardFactoryUtil {
} else if (keyword.equals("Sunburst")) { } else if (keyword.equals("Sunburst")) {
// Rule 702.43a If this object is entering the battlefield as a creature, // Rule 702.43a If this object is entering the battlefield as a creature,
// ignoring any type-changing effects that would affect it // ignoring any type-changing effects that would affect it
CounterType t = CounterType.get(card.isCreature() ? CounterEnumType.P1P1 : CounterEnumType.CHARGE); CounterType t = CounterType.get(host.isCreature() ? CounterEnumType.P1P1 : CounterEnumType.CHARGE);
StringBuilder sb = new StringBuilder("etbCounter:"); StringBuilder sb = new StringBuilder("etbCounter:");
sb.append(t).append(":Sunburst:no Condition:"); sb.append(t).append(":Sunburst:no Condition:");
@@ -3732,7 +3737,7 @@ public class CardFactoryUtil {
sa.setIntrinsic(false); sa.setIntrinsic(false);
} }
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(sa); re.setOverridingAbility(sa);
@@ -3773,7 +3778,7 @@ public class CardFactoryUtil {
String repeffstr = "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.Self" String repeffstr = "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.Self"
+ " | Secondary$ True | Regeneration$ True | Description$ " + keyword; + " | Secondary$ True | Regeneration$ True | Description$ " + keyword;
String effect = "DB$ Regeneration | Defined$ ReplacedCard"; String effect = "DB$ Regeneration | Defined$ ReplacedCard";
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
SpellAbility sa = AbilityFactory.getAbility(effect, card); SpellAbility sa = AbilityFactory.getAbility(effect, card);
re.setOverridingAbility(sa); re.setOverridingAbility(sa);
@@ -3810,13 +3815,13 @@ public class CardFactoryUtil {
if (from) { if (from) {
String fromRep = rep + " | ValidSource$ Card.Self"; String fromRep = rep + " | ValidSource$ Card.Self";
ReplacementEffect re = ReplacementHandler.parseReplacement(fromRep, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(fromRep, host, intrinsic, card);
inst.addReplacement(re); inst.addReplacement(re);
} }
if (to) { if (to) {
String toRep = rep + " | ValidTarget$ Card.Self"; String toRep = rep + " | ValidTarget$ Card.Self";
ReplacementEffect re = ReplacementHandler.parseReplacement(toRep, card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(toRep, host, intrinsic, card);
inst.addReplacement(re); inst.addReplacement(re);
} }
@@ -3828,7 +3833,7 @@ public class CardFactoryUtil {
StringBuilder sb = new StringBuilder("Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self "); StringBuilder sb = new StringBuilder("Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self ");
// to show it on Nexus // to show it on Nexus
if (card.isPermanent()) { if (host.isPermanent()) {
sb.append("| Secondary$ True"); sb.append("| Secondary$ True");
} }
sb.append("| Description$ ").append(keyword); sb.append("| Description$ ").append(keyword);
@@ -3841,7 +3846,7 @@ public class CardFactoryUtil {
sa.setIntrinsic(false); sa.setIntrinsic(false);
} }
ReplacementEffect re = ReplacementHandler.parseReplacement(sb.toString(), card, intrinsic); ReplacementEffect re = ReplacementHandler.parseReplacement(sb.toString(), host, intrinsic, card);
re.setOverridingAbility(sa); re.setOverridingAbility(sa);
@@ -3878,16 +3883,17 @@ public class CardFactoryUtil {
} }
} }
public static void addSpellAbility(final KeywordInterface inst, final Card card, final boolean intrinsic) { public static void addSpellAbility(final KeywordInterface inst, final CardState card, final boolean intrinsic) {
String keyword = inst.getOriginal(); String keyword = inst.getOriginal();
if (keyword.startsWith("Alternative Cost") && !card.isLand()) { Card host = card.getCard();
if (keyword.startsWith("Alternative Cost") && !host.isLand()) {
final String[] kw = keyword.split(":"); final String[] kw = keyword.split(":");
String costStr = kw[1]; String costStr = kw[1];
for (SpellAbility sa: card.getBasicSpells()) { for (SpellAbility sa: host.getBasicSpells()) {
final SpellAbility newSA = sa.copy(); final SpellAbility newSA = sa.copy();
newSA.setBasicSpell(false); newSA.setBasicSpell(false);
if (costStr.equals("ConvertedManaCost")) { if (costStr.equals("ConvertedManaCost")) {
costStr = Integer.toString(card.getCMC()); costStr = Integer.toString(host.getCMC());
} }
final Cost cost = new Cost(costStr, false).add(sa.getPayCosts().copyWithNoMana()); final Cost cost = new Cost(costStr, false).add(sa.getPayCosts().copyWithNoMana());
newSA.putParam("Secondary", "True"); newSA.putParam("Secondary", "True");
@@ -3925,7 +3931,7 @@ public class CardFactoryUtil {
final SpellAbility sa = AbilityFactory.getAbility(effect, card); final SpellAbility sa = AbilityFactory.getAbility(effect, card);
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.equals("Aftermath") && card.getCurrentStateName().equals(CardStateName.RightSplit)) { } else if (keyword.equals("Aftermath") && card.getStateName().equals(CardStateName.RightSplit)) {
// Aftermath does modify existing SA, and does not add new one // Aftermath does modify existing SA, and does not add new one
// only target RightSplit of it // only target RightSplit of it
@@ -4135,7 +4141,7 @@ public class CardFactoryUtil {
inst.addSpellAbility(newSA); inst.addSpellAbility(newSA);
} else if (keyword.startsWith("Foretell")) { } else if (keyword.startsWith("Foretell")) {
final SpellAbility foretell = new AbilityStatic(card, new Cost(ManaCost.TWO, false), null) { final SpellAbility foretell = new AbilityStatic(card.getCard(), new Cost(ManaCost.TWO, false), null) {
@Override @Override
public boolean canPlay() { public boolean canPlay() {
if (!getRestrictions().canPlay(getHostCard(), this)) { if (!getRestrictions().canPlay(getHostCard(), this)) {
@@ -4183,6 +4189,8 @@ public class CardFactoryUtil {
foretell.setDescription(sbDesc.toString()); foretell.setDescription(sbDesc.toString());
foretell.putParam("Secondary", "True"); foretell.putParam("Secondary", "True");
foretell.setCardState(card);
foretell.getRestrictions().setZone(ZoneType.Hand); foretell.getRestrictions().setZone(ZoneType.Hand);
foretell.setIntrinsic(intrinsic); foretell.setIntrinsic(intrinsic);
inst.addSpellAbility(foretell); inst.addSpellAbility(foretell);
@@ -4207,12 +4215,12 @@ public class CardFactoryUtil {
// instantiate attach ability // instantiate attach ability
final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("Fuse") && card.getCurrentStateName().equals(CardStateName.Original)) { } else if (keyword.startsWith("Fuse") && card.getStateName().equals(CardStateName.Original)) {
final SpellAbility sa = AbilityFactory.buildFusedAbility(card); final SpellAbility sa = AbilityFactory.buildFusedAbility(card.getCard());
card.addSpellAbility(sa); card.addSpellAbility(sa);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("Haunt")) { } else if (keyword.startsWith("Haunt")) {
if (!card.isCreature() && intrinsic) { if (!host.isCreature() && intrinsic) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final String hauntSVarName = k[1]; final String hauntSVarName = k[1];
@@ -4377,7 +4385,7 @@ public class CardFactoryUtil {
final Cost prowlCost = new Cost(k[1], false); final Cost prowlCost = new Cost(k[1], false);
final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(prowlCost); final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(prowlCost);
if (card.isInstant() || card.isSorcery()) { if (host.isInstant() || host.isSorcery()) {
newSA.putParam("Secondary", "True"); newSA.putParam("Secondary", "True");
} }
newSA.putParam("PrecostDesc", "Prowl"); newSA.putParam("PrecostDesc", "Prowl");
@@ -4509,7 +4517,7 @@ public class CardFactoryUtil {
// be careful with Suspend ability, it will not hit the stack // be careful with Suspend ability, it will not hit the stack
Cost cost = new Cost(k[2], true); Cost cost = new Cost(k[2], true);
final SpellAbility suspend = new AbilityStatic(card, cost, null) { final SpellAbility suspend = new AbilityStatic(host, cost, null) {
@Override @Override
public boolean canPlay() { public boolean canPlay() {
if (!(this.getRestrictions().canPlay(this.getHostCard(), this))) { if (!(this.getRestrictions().canPlay(this.getHostCard(), this))) {
@@ -4525,7 +4533,7 @@ public class CardFactoryUtil {
@Override @Override
public void resolve() { public void resolve() {
final Game game = card.getGame(); final Game game = this.getHostCard().getGame();
final Card c = game.getAction().exile(this.getHostCard(), this); final Card c = game.getAction().exile(this.getHostCard(), this);
int counters = AbilityUtils.calculateAmount(c, k[1], this); int counters = AbilityUtils.calculateAmount(c, k[1], this);
@@ -4546,6 +4554,7 @@ public class CardFactoryUtil {
String svar = "X"; // emulate "References X" here String svar = "X"; // emulate "References X" here
suspend.setSVar(svar, card.getSVar(svar)); suspend.setSVar(svar, card.getSVar(svar));
suspend.setCardState(card);
final StringBuilder sbStack = new StringBuilder(); final StringBuilder sbStack = new StringBuilder();
sbStack.append(card.getName()).append(" suspending for "); sbStack.append(card.getName()).append(" suspending for ");
@@ -4673,7 +4682,7 @@ public class CardFactoryUtil {
} }
} }
public static void addStaticAbility(final KeywordInterface inst, final Card card, final boolean intrinsic) { public static void addStaticAbility(final KeywordInterface inst, final CardState state, final boolean intrinsic) {
String keyword = inst.getOriginal(); String keyword = inst.getOriginal();
String effect = null; String effect = null;
Map<String, String> svars = Maps.newHashMap(); Map<String, String> svars = Maps.newHashMap();
@@ -4716,7 +4725,7 @@ public class CardFactoryUtil {
sb.append("Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ PlayEncoded"); sb.append("Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ PlayEncoded");
sb.append(" | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ "); sb.append(" | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ ");
sb.append("Whenever CARDNAME deals combat damage to a player, its controller may cast a copy of "); sb.append("Whenever CARDNAME deals combat damage to a player, its controller may cast a copy of ");
sb.append(card.getName()).append(" without paying its mana cost."); sb.append(state.getName()).append(" without paying its mana cost.");
String trig = sb.toString(); String trig = sb.toString();
@@ -4786,7 +4795,7 @@ public class CardFactoryUtil {
} }
if (effect != null) { if (effect != null) {
StaticAbility st = new StaticAbility(effect, card); StaticAbility st = new StaticAbility(effect, state.getCard(), state);
st.setIntrinsic(intrinsic); st.setIntrinsic(intrinsic);
for (Map.Entry<String, String> e : svars.entrySet()) { for (Map.Entry<String, String> e : svars.entrySet()) {
st.setSVar(e.getKey(), e.getValue()); st.setSVar(e.getKey(), e.getValue());
@@ -4964,7 +4973,7 @@ public class CardFactoryUtil {
if (sa == null) { if (sa == null) {
return; return;
} }
sa.setCardState(CardStateName.Adventure); sa.setCardState(card.getCurrentState());
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile "); sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");

View File

@@ -104,6 +104,10 @@ public class CardState extends GameObject implements IHasSVars {
view.updateName(this); view.updateName(this);
} }
public CardStateName getStateName() {
return this.getView().getState();
}
@Override @Override
public String toString() { public String toString() {
return name + " (" + view.getState() + ")"; return name + " (" + view.getState() + ")";
@@ -303,6 +307,11 @@ public class CardState extends GameObject implements IHasSVars {
return Iterables.filter(getSpellAbilities(), SpellAbilityPredicates.isIntrinsic()); return Iterables.filter(getSpellAbilities(), SpellAbilityPredicates.isIntrinsic());
} }
public final SpellAbility getFirstSpellAbility() {
return Iterables.getFirst(getNonManaAbilities(), null);
}
public final boolean hasSpellAbility(final SpellAbility sa) { public final boolean hasSpellAbility(final SpellAbility sa) {
return getSpellAbilities().contains(sa); return getSpellAbilities().contains(sa);
} }
@@ -431,7 +440,7 @@ public class CardState extends GameObject implements IHasSVars {
if (getTypeWithChanges().isPlaneswalker()) { if (getTypeWithChanges().isPlaneswalker()) {
if (loyaltyRep == null) { if (loyaltyRep == null) {
loyaltyRep = CardFactoryUtil.makeEtbCounter("etbCounter:LOYALTY:" + this.baseLoyalty, card, true); loyaltyRep = CardFactoryUtil.makeEtbCounter("etbCounter:LOYALTY:" + this.baseLoyalty, this, true);
} }
result.add(loyaltyRep); result.add(loyaltyRep);
} }

View File

@@ -109,9 +109,9 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
Sentry.getContext().addExtra("Keyword", this.original); Sentry.getContext().addExtra("Keyword", this.original);
CardFactoryUtil.addTriggerAbility(this, host, intrinsic); CardFactoryUtil.addTriggerAbility(this, host, intrinsic);
CardFactoryUtil.addReplacementEffect(this, host, intrinsic); CardFactoryUtil.addReplacementEffect(this, host.getCurrentState(), intrinsic);
CardFactoryUtil.addSpellAbility(this, host, intrinsic); CardFactoryUtil.addSpellAbility(this, host.getCurrentState(), intrinsic);
CardFactoryUtil.addStaticAbility(this, host, intrinsic); CardFactoryUtil.addStaticAbility(this, host.getCurrentState(), intrinsic);
} catch (Exception e) { } catch (Exception e) {
String msg = "KeywordInstance:createTraits: failed Traits for Keyword"; String msg = "KeywordInstance:createTraits: failed Traits for Keyword";
Sentry.getContext().recordBreadcrumb( Sentry.getContext().recordBreadcrumb(

View File

@@ -29,7 +29,7 @@ public class PlayerFactoryUtil {
} }
if (effect != null) { if (effect != null) {
final Card card = player.getKeywordCard(); final Card card = player.getKeywordCard();
StaticAbility st = new StaticAbility(effect, card); StaticAbility st = new StaticAbility(effect, card, card.getCurrentState());
st.setIntrinsic(false); st.setIntrinsic(false);
inst.addStaticAbility(st); inst.addStaticAbility(st);
} }

View File

@@ -17,6 +17,7 @@
*/ */
package forge.game.replacement; package forge.game.replacement;
import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.IHasSVars; import forge.game.IHasSVars;
@@ -25,6 +26,7 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardState;
import forge.game.card.CardTraitChanges; import forge.game.card.CardTraitChanges;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
@@ -405,6 +407,11 @@ public class ReplacementHandler {
ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), sVarHolder)); ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), sVarHolder));
} }
if (sVarHolder instanceof CardState) {
ret.setCardState((CardState)sVarHolder);
} else if (sVarHolder instanceof CardTraitBase) {
ret.setCardState(((CardTraitBase)sVarHolder).getCardState());
}
return ret; return ret;
} }
} }

View File

@@ -49,11 +49,11 @@ public class LandAbility extends Ability {
Card land = this.getHostCard(); Card land = this.getHostCard();
final Player p = this.getActivatingPlayer(); final Player p = this.getActivatingPlayer();
if (this.getCardState() != null) { if (this.getCardState() != null && land.getCurrentStateName() != this.getCardStateName()) {
if (!land.isLKI()) { if (!land.isLKI()) {
land = CardUtil.getLKICopy(land); land = CardUtil.getLKICopy(land);
} }
CardStateName stateName = getCardState(); CardStateName stateName = getCardStateName();
if (!land.hasState(stateName)) { if (!land.hasState(stateName)) {
land.addAlternateState(stateName, false); land.addAlternateState(stateName, false);
land.getState(stateName).copyFrom(getHostCard().getState(stateName), true); land.getState(stateName).copyFrom(getHostCard().getState(stateName), true);
@@ -87,7 +87,7 @@ public class LandAbility extends Ability {
StringBuilder sb = new StringBuilder("Play land"); StringBuilder sb = new StringBuilder("Play land");
if (getHostCard().isModal()) { if (getHostCard().isModal()) {
sb.append(" (").append(getHostCard().getName(ObjectUtils.defaultIfNull(getCardState(), CardStateName.Original))).append(")"); sb.append(" (").append(getHostCard().getName(ObjectUtils.firstNonNull(getCardStateName(), CardStateName.Original))).append(")");
} }
StaticAbility sta = getMayPlay(); StaticAbility sta = getMayPlay();

View File

@@ -175,11 +175,11 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
} }
source.turnFaceDownNoUpdate(); source.turnFaceDownNoUpdate();
lkicheck = true; lkicheck = true;
} else if (getCardState() != null) { } else if (getCardState() != null && source.getCurrentStateName() != getCardStateName()) {
if (!source.isLKI()) { if (!source.isLKI()) {
source = CardUtil.getLKICopy(source); source = CardUtil.getLKICopy(source);
} }
CardStateName stateName = getCardState(); CardStateName stateName = getCardState().getStateName();
if (!source.hasState(stateName)) { if (!source.hasState(stateName)) {
source.addAlternateState(stateName, false); source.addAlternateState(stateName, false);
source.getState(stateName).copyFrom(getHostCard().getState(stateName), true); source.getState(stateName).copyFrom(getHostCard().getState(stateName), true);

View File

@@ -115,8 +115,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private boolean blessing = false; private boolean blessing = false;
private Integer chapter = null; private Integer chapter = null;
private CardStateName stateName = null;
/** The pay costs. */ /** The pay costs. */
private Cost payCosts; private Cost payCosts;
private SpellAbilityRestriction restrictions = new SpellAbilityRestriction(); private SpellAbilityRestriction restrictions = new SpellAbilityRestriction();
@@ -466,21 +464,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return this.hasParam("Boast"); return this.hasParam("Boast");
} }
public void setOriginalHost(final Card c) {
super.setOriginalHost(c);
if (subAbility != null) {
subAbility.setOriginalHost(c);
}
for (AbilitySub sa : additionalAbilities.values()) {
sa.setOriginalHost(c);
}
for (List<AbilitySub> list : additionalAbilityLists.values()) {
for (AbilitySub sa : list) {
sa.setOriginalHost(c);
}
}
}
// If this is not null, then ability was made in a factory // If this is not null, then ability was made in a factory
public ApiType getApi() { public ApiType getApi() {
return api; return api;
@@ -960,15 +943,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
mayPlay = sta; mayPlay = sta;
} }
public CardStateName getCardState() {
return stateName;
}
public void setCardState(CardStateName stateName0) {
this.stateName = stateName0;
}
public boolean isAdventure() { public boolean isAdventure() {
return this.stateName == CardStateName.Adventure; return this.getCardStateName() == CardStateName.Adventure;
} }
public SpellAbility copy() { public SpellAbility copy() {

View File

@@ -31,6 +31,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardState;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
@@ -236,8 +237,8 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
* @param host * @param host
* the host * the host
*/ */
public StaticAbility(final String params, final Card host) { public StaticAbility(final String params, final Card host, CardState state) {
this(parseParams(params, host), host); this(parseParams(params, host), host, state);
} }
/** /**
@@ -248,13 +249,14 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
* @param host * @param host
* the host * the host
*/ */
private StaticAbility(final Map<String, String> params, final Card host) { private StaticAbility(final Map<String, String> params, final Card host, CardState state) {
this.id = nextId(); this.id = nextId();
this.originalMapParams.putAll(params); this.originalMapParams.putAll(params);
this.mapParams.putAll(params); this.mapParams.putAll(params);
this.layers = this.generateLayer(); this.layers = this.generateLayer();
this.hostCard = host; this.hostCard = host;
buildCommonAttributes(host); buildCommonAttributes(host);
this.setCardState(state);
} }
public final CardCollectionView applyContinuousAbilityBefore(final StaticAbilityLayer layer, final CardCollectionView preList) { public final CardCollectionView applyContinuousAbilityBefore(final StaticAbilityLayer layer, final CardCollectionView preList) {

View File

@@ -736,7 +736,6 @@ public final class StaticAbilityContinuous {
if (abilty.startsWith("AB") || abilty.startsWith("ST")) { // grant the ability if (abilty.startsWith("AB") || abilty.startsWith("ST")) { // grant the ability
final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard, stAb); final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard, stAb);
sa.setIntrinsic(false); sa.setIntrinsic(false);
sa.setOriginalHost(hostCard);
addedAbilities.add(sa); addedAbilities.add(sa);
} }
} }
@@ -766,9 +765,6 @@ public final class StaticAbilityContinuous {
newSA.setRestrictions(sa.getRestrictions()); newSA.setRestrictions(sa.getRestrictions());
newSA.getRestrictions().setLimitToCheck(params.get("GainsAbilitiesLimitPerTurn")); newSA.getRestrictions().setLimitToCheck(params.get("GainsAbilitiesLimitPerTurn"));
} }
if (newSA.getOriginalHost() == null) {
newSA.setOriginalHost(c);
}
newSA.setOriginalAbility(sa); // need to be set to get the Once Per turn Clause correct newSA.setOriginalAbility(sa); // need to be set to get the Once Per turn Clause correct
newSA.setGrantorStatic(stAb); newSA.setGrantorStatic(stAb);
newSA.setIntrinsic(false); newSA.setIntrinsic(false);
@@ -794,7 +790,6 @@ public final class StaticAbilityContinuous {
// turn them into SpellAbility object before adding to card // turn them into SpellAbility object before adding to card
// with that the TargetedCard does not need the Svars added to them anymore // with that the TargetedCard does not need the Svars added to them anymore
// but only do it if the trigger doesn't already have a overriding ability // but only do it if the trigger doesn't already have a overriding ability
actualTrigger.setOriginalHost(hostCard);
addedTrigger.add(actualTrigger); addedTrigger.add(actualTrigger);
} }
} }
@@ -807,9 +802,8 @@ public final class StaticAbilityContinuous {
s = TextUtil.fastReplace(s, "ConvertedManaCost", costcmc); s = TextUtil.fastReplace(s, "ConvertedManaCost", costcmc);
} }
StaticAbility stat = new StaticAbility(s, affectedCard); StaticAbility stat = new StaticAbility(s, affectedCard, stAb.getCardState());
stat.setIntrinsic(false); stat.setIntrinsic(false);
stat.setOriginalHost(hostCard);
addedStaticAbility.add(stat); addedStaticAbility.add(stat);
} }
} }

View File

@@ -17,6 +17,7 @@
*/ */
package forge.game.trigger; package forge.game.trigger;
import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.IHasSVars; import forge.game.IHasSVars;
@@ -29,6 +30,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
@@ -151,6 +153,12 @@ public class TriggerHandler {
ret = type.createTrigger(mapParams, host, intrinsic); ret = type.createTrigger(mapParams, host, intrinsic);
if (sVarHolder != null) { if (sVarHolder != null) {
ret.ensureAbility(sVarHolder); ret.ensureAbility(sVarHolder);
if (sVarHolder instanceof CardState) {
ret.setCardState((CardState)sVarHolder);
} else if (sVarHolder instanceof CardTraitBase) {
ret.setCardState(((CardTraitBase)sVarHolder).getCardState());
}
} }
} catch (Exception e) { } catch (Exception e) {
String msg = "TriggerHandler:parseTrigger failed to parse"; String msg = "TriggerHandler:parseTrigger failed to parse";

View File

@@ -6,6 +6,7 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.ApiType; 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.CardState;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.*; import forge.game.spellability.*;
@@ -515,10 +516,11 @@ public class WrappedAbility extends Ability {
sa.setXManaCostPaid(n); sa.setXManaCostPaid(n);
} }
public Card getOriginalHost() {
return sa.getOriginalHost(); public CardState getCardState() {
return sa.getCardState();
} }
public void setOriginalHost(final Card c) { public void setCardState(CardState state) {
sa.setOriginalHost(c); sa.setCardState(state);
} }
} }