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

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

View File

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

View File

@@ -226,6 +226,7 @@ public final class AbilityFactory {
msg.append(". Looking for API: ").append(api);
throw new RuntimeException(msg.toString());
}
spellAbility.setCardState(state);
if (mapParams.containsKey("Forecast")) {
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.
// 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);
}
@@ -2063,7 +2063,6 @@ public class AbilityUtils {
subAbility.setActivatingPlayer(sa.getActivatingPlayer());
subAbility.setHostCard(sa.getHostCard());
subAbility.setOriginalHost(c);
//add the spliced ability to the end of the chain
sa.appendSubAbility(subAbility);

View File

@@ -431,7 +431,7 @@ public abstract class SpellAbilityEffect {
+ " exile it instead of putting it anywhere else.";
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.setOverridingAbility(AbilityFactory.getAbility(effect, eff));

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,6 @@ import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
@@ -258,7 +257,7 @@ public class CardFactory {
// ************** Link to different CardFactories *******************
if (state == CardStateName.LeftSplit || state == CardStateName.RightSplit) {
for (final SpellAbility sa : card.getSpellAbilities()) {
sa.setCardState(state);
sa.setCardState(card.getState(state));
}
CardFactoryUtil.setupKeywordedAbilities(card);
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 (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 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
c.addIntrinsicKeywords(face.getKeywords(), false);
@@ -401,9 +400,9 @@ public class CardFactory {
SpellAbility sa = new SpellPermanent(c);
// Currently only for Modal, might react different when state is always set
if (c.getCurrentStateName() == CardStateName.Modal) {
sa.setCardState(c.getCurrentStateName());
}
//if (c.getCurrentStateName() == CardStateName.Modal) {
sa.setCardState(c.getCurrentState());
//}
c.addSpellAbility(sa);
}
// TODO add LandAbility there when refactor MayPlay
@@ -678,7 +677,6 @@ public class CardFactory {
if (origSVars.containsKey(s)) {
final String actualTrigger = origSVars.get(s);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true);
parsedTrigger.setOriginalHost(host);
state.addTrigger(parsedTrigger);
}
}
@@ -702,7 +700,6 @@ public class CardFactory {
if (origSVars.containsKey(s)) {
final String actualAbility = origSVars.get(s);
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, out);
grantedAbility.setOriginalHost(host);
grantedAbility.setIntrinsic(true);
state.addSpellAbility(grantedAbility);
}
@@ -715,8 +712,7 @@ public class CardFactory {
for (final String s : str.split(",")) {
if (origSVars.containsKey(s)) {
final String actualStatic = origSVars.get(s);
final StaticAbility grantedStatic = new StaticAbility(actualStatic, out);
grantedStatic.setOriginalHost(host);
final StaticAbility grantedStatic = new StaticAbility(actualStatic, out, sa.getCardState());
grantedStatic.setIntrinsic(true);
state.addStaticAbility(grantedStatic);
}
@@ -765,26 +761,6 @@ public class CardFactory {
// set the host card for copied replacement effects
// needed for copied xPaid ETB effects (for the copy, xPaid = 0)
for (final ReplacementEffect rep : state.getReplacementEffects()) {
final SpellAbility newSa = rep.getOverridingAbility();
if (newSa != null && newSa.getOriginalHost() == null) {
newSa.setOriginalHost(in);
}
}
// set the host card for copied spellabilities, if they are not set yet
for (final SpellAbility newSa : state.getSpellAbilities()) {
if (newSa.getOriginalHost() == null) {
newSa.setOriginalHost(in);
}
}
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) {
state.setSetCode(originalState.getSetCode());

View File

@@ -94,8 +94,8 @@ public class CardFactoryUtil {
* a {@link forge.game.card.Card} object.
* @return a {@link forge.game.spellability.SpellAbility} object.
*/
public static SpellAbility abilityMorphDown(final Card sourceCard) {
final Spell morphDown = new Spell(sourceCard, new Cost(ManaCost.THREE, false)) {
public static SpellAbility abilityMorphDown(final CardState cardState) {
final Spell morphDown = new Spell(cardState.getCard(), new Cost(ManaCost.THREE, false)) {
private static final long serialVersionUID = -1438810964807867610L;
@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.setStackDescription("Morph - Creature 2/2");
morphDown.setCastFaceDown(true);
@@ -136,7 +138,7 @@ public class CardFactoryUtil {
* a {@link forge.game.card.Card} 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);
String costDesc = cost.toString();
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.)");
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.hasXInAnyCostPart() && sourceCard.hasSVar("X")) {
morphUp.setSVar("X", sourceCard.getSVar("X"));
if (cost.hasXInAnyCostPart() && cardState.hasSVar("X")) {
morphUp.setSVar("X", cardState.getSVar("X"));
}
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());
return morphUp;
@@ -2098,6 +2100,7 @@ public class CardFactoryUtil {
final SpellAbility intrinsicAbility = AbilityFactory.getAbility(rawAbility, card);
card.addSpellAbility(intrinsicAbility);
intrinsicAbility.setIntrinsic(true);
intrinsicAbility.setCardState(card.getCurrentState());
} catch (Exception e) {
String msg = "CardFactoryUtil:addAbilityFactoryAbilities: crash in raw Ability";
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 boolean intrinsic, final String valid, final String zone) {
SpellAbility repAb = AbilityFactory.getAbility(effect, card);
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 boolean intrinsic, final String valid, final String zone) {
Card host = card.getCard();
String desc = repAb.getDescription();
setupETBReplacementAbility(repAb);
if (!intrinsic) {
@@ -2169,14 +2173,14 @@ public class CardFactoryUtil {
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.setOverridingAbility(repAb);
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;
@@ -2212,7 +2216,7 @@ public class CardFactoryUtil {
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
+ "| 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);
@@ -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();
if (keyword.equals("Aftermath") && card.getCurrentStateName().equals(CardStateName.RightSplit)) {
if (keyword.equals("Aftermath") && card.getStateName().equals(CardStateName.RightSplit)) {
StringBuilder sb = new StringBuilder();
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");
sb.append("| ValidStackSa$ Spell.Aftermath | Description$ Aftermath");
@@ -3362,7 +3367,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile);
@@ -3408,7 +3413,7 @@ public class CardFactoryUtil {
saReveal.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, host, intrinsic, card);
re.setOverridingAbility(saReveal);
@@ -3460,7 +3465,7 @@ public class CardFactoryUtil {
saReturn.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saReturn);
@@ -3483,7 +3488,7 @@ public class CardFactoryUtil {
saMill.setIntrinsic(intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, host, intrinsic, card);
re.setOverridingAbility(saMill);
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() + ")";
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
setupETBReplacementAbility(cleanupSA);
re.setOverridingAbility(sacrificeSA);
@@ -3563,7 +3568,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(false);
}
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile);
@@ -3598,7 +3603,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(false);
}
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile);
@@ -3607,7 +3612,7 @@ public class CardFactoryUtil {
// Set Madness Replacement effects
String repeffstr = "Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | Secondary$ True "
+ " | 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";
re.setOverridingAbility(AbilityFactory.getAbility(sVarMadness, card));
@@ -3657,7 +3662,7 @@ public class CardFactoryUtil {
saExile.setIntrinsic(false);
}
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(saExile);
@@ -3704,7 +3709,7 @@ public class CardFactoryUtil {
} else if (keyword.equals("Sunburst")) {
// Rule 702.43a If this object is entering the battlefield as a creature,
// 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:");
sb.append(t).append(":Sunburst:no Condition:");
@@ -3732,7 +3737,7 @@ public class CardFactoryUtil {
sa.setIntrinsic(false);
}
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
re.setOverridingAbility(sa);
@@ -3773,7 +3778,7 @@ public class CardFactoryUtil {
String repeffstr = "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.Self"
+ " | Secondary$ True | Regeneration$ True | Description$ " + keyword;
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);
re.setOverridingAbility(sa);
@@ -3810,13 +3815,13 @@ public class CardFactoryUtil {
if (from) {
String fromRep = rep + " | ValidSource$ Card.Self";
ReplacementEffect re = ReplacementHandler.parseReplacement(fromRep, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(fromRep, host, intrinsic, card);
inst.addReplacement(re);
}
if (to) {
String toRep = rep + " | ValidTarget$ Card.Self";
ReplacementEffect re = ReplacementHandler.parseReplacement(toRep, card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(toRep, host, intrinsic, card);
inst.addReplacement(re);
}
@@ -3828,7 +3833,7 @@ public class CardFactoryUtil {
StringBuilder sb = new StringBuilder("Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self ");
// to show it on Nexus
if (card.isPermanent()) {
if (host.isPermanent()) {
sb.append("| Secondary$ True");
}
sb.append("| Description$ ").append(keyword);
@@ -3841,7 +3846,7 @@ public class CardFactoryUtil {
sa.setIntrinsic(false);
}
ReplacementEffect re = ReplacementHandler.parseReplacement(sb.toString(), card, intrinsic);
ReplacementEffect re = ReplacementHandler.parseReplacement(sb.toString(), host, intrinsic, card);
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();
if (keyword.startsWith("Alternative Cost") && !card.isLand()) {
Card host = card.getCard();
if (keyword.startsWith("Alternative Cost") && !host.isLand()) {
final String[] kw = keyword.split(":");
String costStr = kw[1];
for (SpellAbility sa: card.getBasicSpells()) {
for (SpellAbility sa: host.getBasicSpells()) {
final SpellAbility newSA = sa.copy();
newSA.setBasicSpell(false);
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());
newSA.putParam("Secondary", "True");
@@ -3925,7 +3931,7 @@ public class CardFactoryUtil {
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
sa.setIntrinsic(intrinsic);
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
// only target RightSplit of it
@@ -4135,7 +4141,7 @@ public class CardFactoryUtil {
inst.addSpellAbility(newSA);
} 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
public boolean canPlay() {
if (!getRestrictions().canPlay(getHostCard(), this)) {
@@ -4183,6 +4189,8 @@ public class CardFactoryUtil {
foretell.setDescription(sbDesc.toString());
foretell.putParam("Secondary", "True");
foretell.setCardState(card);
foretell.getRestrictions().setZone(ZoneType.Hand);
foretell.setIntrinsic(intrinsic);
inst.addSpellAbility(foretell);
@@ -4207,12 +4215,12 @@ public class CardFactoryUtil {
// instantiate attach ability
final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card);
inst.addSpellAbility(sa);
} else if (keyword.startsWith("Fuse") && card.getCurrentStateName().equals(CardStateName.Original)) {
final SpellAbility sa = AbilityFactory.buildFusedAbility(card);
} else if (keyword.startsWith("Fuse") && card.getStateName().equals(CardStateName.Original)) {
final SpellAbility sa = AbilityFactory.buildFusedAbility(card.getCard());
card.addSpellAbility(sa);
inst.addSpellAbility(sa);
} else if (keyword.startsWith("Haunt")) {
if (!card.isCreature() && intrinsic) {
if (!host.isCreature() && intrinsic) {
final String[] k = keyword.split(":");
final String hauntSVarName = k[1];
@@ -4377,7 +4385,7 @@ public class CardFactoryUtil {
final Cost prowlCost = new Cost(k[1], false);
final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(prowlCost);
if (card.isInstant() || card.isSorcery()) {
if (host.isInstant() || host.isSorcery()) {
newSA.putParam("Secondary", "True");
}
newSA.putParam("PrecostDesc", "Prowl");
@@ -4509,7 +4517,7 @@ public class CardFactoryUtil {
// be careful with Suspend ability, it will not hit the stack
Cost cost = new Cost(k[2], true);
final SpellAbility suspend = new AbilityStatic(card, cost, null) {
final SpellAbility suspend = new AbilityStatic(host, cost, null) {
@Override
public boolean canPlay() {
if (!(this.getRestrictions().canPlay(this.getHostCard(), this))) {
@@ -4525,7 +4533,7 @@ public class CardFactoryUtil {
@Override
public void resolve() {
final Game game = card.getGame();
final Game game = this.getHostCard().getGame();
final Card c = game.getAction().exile(this.getHostCard(), this);
int counters = AbilityUtils.calculateAmount(c, k[1], this);
@@ -4546,6 +4554,7 @@ public class CardFactoryUtil {
String svar = "X"; // emulate "References X" here
suspend.setSVar(svar, card.getSVar(svar));
suspend.setCardState(card);
final StringBuilder sbStack = new StringBuilder();
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 effect = null;
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(" | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ ");
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();
@@ -4786,7 +4795,7 @@ public class CardFactoryUtil {
}
if (effect != null) {
StaticAbility st = new StaticAbility(effect, card);
StaticAbility st = new StaticAbility(effect, state.getCard(), state);
st.setIntrinsic(intrinsic);
for (Map.Entry<String, String> e : svars.entrySet()) {
st.setSVar(e.getKey(), e.getValue());
@@ -4964,7 +4973,7 @@ public class CardFactoryUtil {
if (sa == null) {
return;
}
sa.setCardState(CardStateName.Adventure);
sa.setCardState(card.getCurrentState());
StringBuilder sb = new StringBuilder();
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);
}
public CardStateName getStateName() {
return this.getView().getState();
}
@Override
public String toString() {
return name + " (" + view.getState() + ")";
@@ -303,6 +307,11 @@ public class CardState extends GameObject implements IHasSVars {
return Iterables.filter(getSpellAbilities(), SpellAbilityPredicates.isIntrinsic());
}
public final SpellAbility getFirstSpellAbility() {
return Iterables.getFirst(getNonManaAbilities(), null);
}
public final boolean hasSpellAbility(final SpellAbility sa) {
return getSpellAbilities().contains(sa);
}
@@ -431,7 +440,7 @@ public class CardState extends GameObject implements IHasSVars {
if (getTypeWithChanges().isPlaneswalker()) {
if (loyaltyRep == null) {
loyaltyRep = CardFactoryUtil.makeEtbCounter("etbCounter:LOYALTY:" + this.baseLoyalty, card, true);
loyaltyRep = CardFactoryUtil.makeEtbCounter("etbCounter:LOYALTY:" + this.baseLoyalty, this, true);
}
result.add(loyaltyRep);
}

View File

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

View File

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

View File

@@ -17,6 +17,7 @@
*/
package forge.game.replacement;
import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.IHasSVars;
@@ -25,6 +26,7 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardState;
import forge.game.card.CardTraitChanges;
import forge.game.card.CardUtil;
import forge.game.keyword.KeywordInterface;
@@ -405,6 +407,11 @@ public class ReplacementHandler {
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;
}
}

View File

@@ -49,11 +49,11 @@ public class LandAbility extends Ability {
Card land = this.getHostCard();
final Player p = this.getActivatingPlayer();
if (this.getCardState() != null) {
if (this.getCardState() != null && land.getCurrentStateName() != this.getCardStateName()) {
if (!land.isLKI()) {
land = CardUtil.getLKICopy(land);
}
CardStateName stateName = getCardState();
CardStateName stateName = getCardStateName();
if (!land.hasState(stateName)) {
land.addAlternateState(stateName, false);
land.getState(stateName).copyFrom(getHostCard().getState(stateName), true);
@@ -87,7 +87,7 @@ public class LandAbility extends Ability {
StringBuilder sb = new StringBuilder("Play land");
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();

View File

@@ -175,11 +175,11 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
}
source.turnFaceDownNoUpdate();
lkicheck = true;
} else if (getCardState() != null) {
} else if (getCardState() != null && source.getCurrentStateName() != getCardStateName()) {
if (!source.isLKI()) {
source = CardUtil.getLKICopy(source);
}
CardStateName stateName = getCardState();
CardStateName stateName = getCardState().getStateName();
if (!source.hasState(stateName)) {
source.addAlternateState(stateName, false);
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 Integer chapter = null;
private CardStateName stateName = null;
/** The pay costs. */
private Cost payCosts;
private SpellAbilityRestriction restrictions = new SpellAbilityRestriction();
@@ -466,21 +464,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
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
public ApiType getApi() {
return api;
@@ -960,15 +943,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
mayPlay = sta;
}
public CardStateName getCardState() {
return stateName;
}
public void setCardState(CardStateName stateName0) {
this.stateName = stateName0;
}
public boolean isAdventure() {
return this.stateName == CardStateName.Adventure;
return this.getCardStateName() == CardStateName.Adventure;
}
public SpellAbility copy() {

View File

@@ -31,6 +31,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardState;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
@@ -236,8 +237,8 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
* @param host
* the host
*/
public StaticAbility(final String params, final Card host) {
this(parseParams(params, host), host);
public StaticAbility(final String params, final Card host, CardState state) {
this(parseParams(params, host), host, state);
}
/**
@@ -248,13 +249,14 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
* @param 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.originalMapParams.putAll(params);
this.mapParams.putAll(params);
this.layers = this.generateLayer();
this.hostCard = host;
buildCommonAttributes(host);
this.setCardState(state);
}
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
final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard, stAb);
sa.setIntrinsic(false);
sa.setOriginalHost(hostCard);
addedAbilities.add(sa);
}
}
@@ -766,9 +765,6 @@ public final class StaticAbilityContinuous {
newSA.setRestrictions(sa.getRestrictions());
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.setGrantorStatic(stAb);
newSA.setIntrinsic(false);
@@ -794,7 +790,6 @@ public final class StaticAbilityContinuous {
// turn them into SpellAbility object before adding to card
// 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
actualTrigger.setOriginalHost(hostCard);
addedTrigger.add(actualTrigger);
}
}
@@ -807,9 +802,8 @@ public final class StaticAbilityContinuous {
s = TextUtil.fastReplace(s, "ConvertedManaCost", costcmc);
}
StaticAbility stat = new StaticAbility(s, affectedCard);
StaticAbility stat = new StaticAbility(s, affectedCard, stAb.getCardState());
stat.setIntrinsic(false);
stat.setOriginalHost(hostCard);
addedStaticAbility.add(stat);
}
}

View File

@@ -17,6 +17,7 @@
*/
package forge.game.trigger;
import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.IHasSVars;
@@ -29,6 +30,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardZoneTable;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
@@ -151,6 +153,12 @@ public class TriggerHandler {
ret = type.createTrigger(mapParams, host, intrinsic);
if (sVarHolder != null) {
ret.ensureAbility(sVarHolder);
if (sVarHolder instanceof CardState) {
ret.setCardState((CardState)sVarHolder);
} else if (sVarHolder instanceof CardTraitBase) {
ret.setCardState(((CardTraitBase)sVarHolder).getCardState());
}
}
} catch (Exception e) {
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.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardState;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.*;
@@ -515,10 +516,11 @@ public class WrappedAbility extends Ability {
sa.setXManaCostPaid(n);
}
public Card getOriginalHost() {
return sa.getOriginalHost();
public CardState getCardState() {
return sa.getCardState();
}
public void setOriginalHost(final Card c) {
sa.setOriginalHost(c);
public void setCardState(CardState state) {
sa.setCardState(state);
}
}