CardFactory: Token Copy of transforming permanent

This commit is contained in:
Hans Mackowiak
2023-05-07 22:53:24 +02:00
parent 3bc8fb6f9a
commit bb3709ebad
7 changed files with 84 additions and 45 deletions

View File

@@ -294,7 +294,7 @@ public class GameCopier {
private Card createCardCopy(Game newGame, Player newOwner, Card c) { private Card createCardCopy(Game newGame, Player newOwner, Card c) {
if (c.isToken() && !c.isImmutable()) { if (c.isToken() && !c.isImmutable()) {
Card result = new TokenInfo(c).makeOneToken(newOwner); Card result = new TokenInfo(c).makeOneToken(newOwner);
CardFactory.copyCopiableCharacteristics(c, result); CardFactory.copyCopiableCharacteristics(c, result, null, null);
return result; return result;
} }
if (USE_FROM_PAPER_CARD && !c.isImmutable() && c.getPaperCard() != null) { if (USE_FROM_PAPER_CARD && !c.isImmutable() && c.getPaperCard() != null) {

View File

@@ -14,6 +14,7 @@ import com.google.common.collect.Lists;
import forge.StaticData; import forge.StaticData;
import forge.card.CardRulesPredicates; import forge.card.CardRulesPredicates;
import forge.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
@@ -267,17 +268,27 @@ public class CopyPermanentEffect extends TokenEffectBase {
copy.setOwner(newOwner); copy.setOwner(newOwner);
copy.setSetCode(original.getSetCode()); copy.setSetCode(original.getSetCode());
if (sa.hasParam("Embalm")) { copy.setTokenSpawningAbility(sa);
copy.setEmbalmed(true); // 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
} // the resulting token is a transforming token that has both a front face and a back face.
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
if (sa.hasParam("Eternalize")) { // If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
copy.setEternalized(true); // This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
if (original.isTransformable()) {
copy.setBackSide(original.isBackSide());
copy.setRules(original.getRules());
if (original.isTransformed()) {
copy.incrementTransformedTimestamp();
}
} }
copy.setStates(CardFactory.getCloneStates(original, copy, sa)); copy.setStates(CardFactory.getCloneStates(original, copy, sa));
// force update the now set State // force update the now set State
copy.setState(copy.getCurrentStateName(), true, true); if (original.isTransformable()) {
copy.setState(original.isTransformed() ? CardStateName.Transformed : CardStateName.Original, true, true);
} else {
copy.setState(copy.getCurrentStateName(), true, true);
}
copy.setToken(true); copy.setToken(true);
return copy; return copy;

View File

@@ -84,6 +84,7 @@ public class ReplaceTokenEffect extends SpellAbilityEffect {
if (token == null) { if (token == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script); throw new RuntimeException("don't find Token for TokenScript: " + script);
} }
token.setTokenSpawningAbility((SpellAbility)repSA.getReplacingObject(AbilityKey.Cause));
token.setController(e.getKey(), timestamp); token.setController(e.getKey(), timestamp);
table.put(p, token, e.getValue()); table.put(p, token, e.getValue());
} }
@@ -136,6 +137,7 @@ public class ReplaceTokenEffect extends SpellAbilityEffect {
throw new RuntimeException("don't find Token for TokenScript: " + script); throw new RuntimeException("don't find Token for TokenScript: " + script);
} }
token.setTokenSpawningAbility((SpellAbility)repSA.getReplacingObject(AbilityKey.Cause));
token.setController(pe.getKey(), timestamp); token.setController(pe.getKey(), timestamp);
// if token is created from ForEach keep that // if token is created from ForEach keep that
token.addRemembered(pe.getValue().getRight()); token.addRemembered(pe.getValue().getRight());

View File

@@ -44,8 +44,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
if (result == null) { if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script); throw new RuntimeException("don't find Token for TokenScript: " + script);
} }
// set owner result.setTokenSpawningAbility(sa);
result.setOwner(owner);
tokenTable.put(owner, result, finalAmount); tokenTable.put(owner, result, finalAmount);
} }
} }
@@ -59,8 +58,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
if (result == null) { if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script); throw new RuntimeException("don't find Token for TokenScript: " + script);
} }
// set owner result.setTokenSpawningAbility(sa);
result.setOwner(owner);
tokenTable.put(owner, result, finalAmount); tokenTable.put(owner, result, finalAmount);
return tokenTable; return tokenTable;
@@ -81,6 +79,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
for (Player p : Sets.newHashSet(tokenTable.rowKeySet())) { for (Player p : Sets.newHashSet(tokenTable.rowKeySet())) {
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p);
repParams.put(AbilityKey.Token, tokenTable); repParams.put(AbilityKey.Token, tokenTable);
repParams.put(AbilityKey.Cause, sa);
repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens? repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens?
switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) { switch (game.getReplacementHandler().run(ReplacementType.CreateToken, repParams)) {

View File

@@ -120,6 +120,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private Card mergedTo; private Card mergedTo;
private SpellAbility effectSourceAbility; private SpellAbility effectSourceAbility;
private SpellAbility tokenSpawningAbility;
private GameEntity entityAttachedTo; private GameEntity entityAttachedTo;
@@ -223,8 +224,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private long prototypeTimestamp = -1; private long prototypeTimestamp = -1;
private int timesMutated = 0; private int timesMutated = 0;
private boolean tributed = false; private boolean tributed = false;
private boolean embalmed = false;
private boolean eternalized = false;
private boolean discarded = false; private boolean discarded = false;
private boolean flipped = false; private boolean flipped = false;
@@ -1562,6 +1561,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
newValue = 0; newValue = 0;
} }
break; break;
default:
break;
} }
final int delta = oldValue - newValue; final int delta = oldValue - newValue;
@@ -5885,18 +5886,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
tributed = b; tributed = b;
} }
public final boolean isEmbalmed() { public final SpellAbility getTokenSpawningAbility() {
return embalmed; return tokenSpawningAbility;
} }
public final void setEmbalmed(final boolean b) {
embalmed = b; public void setTokenSpawningAbility(SpellAbility sa) {
tokenSpawningAbility = sa;
}
public final boolean isEmbalmed() {
SpellAbility sa = getTokenSpawningAbility();
return sa != null && sa.hasParam("Embalm");
} }
public final boolean isEternalized() { public final boolean isEternalized() {
return eternalized; SpellAbility sa = getTokenSpawningAbility();
} return sa != null && sa.hasParam("Eternalize");
public final void setEternalized(final boolean b) {
eternalized = b;
} }
public final int getExertedThisTurn() { public final int getExertedThisTurn() {

View File

@@ -29,6 +29,7 @@ import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
@@ -85,8 +86,7 @@ public class CardFactory {
out.setToken(true); out.setToken(true);
// need to copy this values for the tokens // need to copy this values for the tokens
out.setEmbalmed(in.isEmbalmed()); out.setTokenSpawningAbility(in.getTokenSpawningAbility());
out.setEternalized(in.isEternalized());
} }
out.setZone(in.getZone()); out.setZone(in.getZone());
@@ -125,7 +125,7 @@ public class CardFactory {
final Card original = targetSA.getHostCard(); final Card original = targetSA.getHostCard();
final Game game = source.getGame(); final Game game = source.getGame();
final Card c = new Card(game.nextCardId(), original.getPaperCard(), game); final Card c = new Card(game.nextCardId(), original.getPaperCard(), game);
copyCopiableCharacteristics(original, c); copyCopiableCharacteristics(original, c, sourceSA, targetSA);
if (sourceSA.hasParam("NonLegendary")) { if (sourceSA.hasParam("NonLegendary")) {
c.removeType(CardType.Supertype.Legendary); c.removeType(CardType.Supertype.Legendary);
@@ -525,12 +525,12 @@ public class CardFactory {
* @param from the {@link Card} to copy from. * @param from the {@link Card} to copy from.
* @param to the {@link Card} to copy to. * @param to the {@link Card} to copy to.
*/ */
public static void copyCopiableCharacteristics(final Card from, final Card to) { public static void copyCopiableCharacteristics(final Card from, final Card to, SpellAbility sourceSA, SpellAbility targetSA) {
final boolean toIsFaceDown = to.isFaceDown(); final boolean toIsFaceDown = to.isFaceDown();
if (toIsFaceDown) { if (toIsFaceDown) {
// If to is face down, copy to its front side // If to is face down, copy to its front side
to.setState(CardStateName.Original, false); to.setState(CardStateName.Original, false);
copyCopiableCharacteristics(from, to); copyCopiableCharacteristics(from, to, sourceSA, targetSA);
to.setState(CardStateName.FaceDown, false); to.setState(CardStateName.FaceDown, false);
return; return;
} }
@@ -545,6 +545,18 @@ public class CardFactory {
copyState(from, CardStateName.Original, to, to.getCurrentStateName()); copyState(from, CardStateName.Original, to, to.getCurrentStateName());
} }
copyState(from, CardStateName.Flipped, to, CardStateName.Flipped); copyState(from, CardStateName.Flipped, to, CardStateName.Flipped);
} else if (from.isTransformable()
&& sourceSA != null && ApiType.CopySpellAbility.equals(sourceSA.getApi())
&& targetSA != null && targetSA.isSpell() && targetSA.getHostCard().isPermanent()) {
copyState(from, CardStateName.Original, to, CardStateName.Original);
copyState(from, CardStateName.Transformed, to, CardStateName.Transformed);
// 707.10g If an effect creates a copy of a transforming permanent spell, the copy is also a transforming permanent spell that has both a front face and a back face.
// The characteristics of its front and back face are determined by the copiable values of the same face of the spell it is a copy of, as modified by any other copy effects.
// If the spell it is a copy of has its back face up, the copy is created with its back face up. The token thats put onto the battlefield as that spell resolves is a transforming token.
to.setBackSide(from.isBackSide());
if (from.isTransformed()) {
to.incrementTransformedTimestamp();
}
} else if (fromIsTransformedCard) { } else if (fromIsTransformedCard) {
copyState(from, from.getCurrentStateName(), to, CardStateName.Original); copyState(from, from.getCurrentStateName(), to, CardStateName.Original);
} else { } else {
@@ -585,6 +597,9 @@ public class CardFactory {
c.setState(in.getCurrentStateName(), false); c.setState(in.getCurrentStateName(), false);
c.setRules(in.getRules()); c.setRules(in.getRules());
if (in.isTransformed()) {
c.incrementTransformedTimestamp();
}
return c; return c;
} }
@@ -738,6 +753,15 @@ public class CardFactory {
final CardState ret2 = new CardState(out, CardStateName.Adventure); final CardState ret2 = new CardState(out, CardStateName.Adventure);
ret2.copyFrom(in.getState(CardStateName.Adventure), false, sa); ret2.copyFrom(in.getState(CardStateName.Adventure), false, sa);
result.put(CardStateName.Adventure, ret2); result.put(CardStateName.Adventure, ret2);
} else if (in.isTransformable() && sa instanceof SpellAbility && ApiType.CopyPermanent.equals(((SpellAbility)sa).getApi())) {
// CopyPermanent can copy token
final CardState ret1 = new CardState(out, CardStateName.Original);
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
result.put(CardStateName.Original, ret1);
final CardState ret2 = new CardState(out, CardStateName.Transformed);
ret2.copyFrom(in.getState(CardStateName.Transformed), false, sa);
result.put(CardStateName.Transformed, ret2);
} else { } else {
// in all other cases just copy the current state to original // in all other cases just copy the current state to original
final CardState ret = new CardState(out, CardStateName.Original); final CardState ret = new CardState(out, CardStateName.Original);
@@ -866,16 +890,18 @@ public class CardFactory {
} }
// Special Rules for Embalm and Eternalize // Special Rules for Embalm and Eternalize
if (sa.hasParam("Embalm") && out.isEmbalmed()) { if (sa.hasParam("Embalm") && out.isEmbalmed()) {
state.addType("Zombie"); state.addType("Zombie");
state.setColor(MagicColor.WHITE); state.setColor(MagicColor.WHITE);
state.setManaCost(ManaCost.NO_COST); state.setManaCost(ManaCost.NO_COST);
String name = TextUtil.fastReplace( if (sa.isIntrinsic()) {
TextUtil.fastReplace(host.getName(), ",", ""), String name = TextUtil.fastReplace(
" ", "_").toLowerCase(); TextUtil.fastReplace(host.getName(), ",", ""),
String set = host.getSetCode().toLowerCase(); " ", "_").toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("embalm_" + name + "_" + set)); String set = host.getSetCode().toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("embalm_" + name + "_" + set));
}
} }
if (sa.hasParam("Eternalize") && out.isEternalized()) { if (sa.hasParam("Eternalize") && out.isEternalized()) {
@@ -885,11 +911,13 @@ public class CardFactory {
state.setBasePower(4); state.setBasePower(4);
state.setBaseToughness(4); state.setBaseToughness(4);
String name = TextUtil.fastReplace( if (sa.isIntrinsic()) {
TextUtil.fastReplace(host.getName(), ",", ""), String name = TextUtil.fastReplace(
" ", "_").toLowerCase(); TextUtil.fastReplace(host.getName(), ",", ""),
String set = host.getSetCode().toLowerCase(); " ", "_").toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("eternalize_" + name + "_" + set)); String set = host.getSetCode().toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("eternalize_" + name + "_" + set));
}
} }
if (sa.hasParam("GainTextOf") && originalState != null) { if (sa.hasParam("GainTextOf") && originalState != null) {

View File

@@ -28,12 +28,6 @@ public class ReplaceToken extends ReplacementEffect {
*/ */
@Override @Override
public boolean canReplace(Map<AbilityKey, Object> runParams) { public boolean canReplace(Map<AbilityKey, Object> runParams) {
/*
if (((int) runParams.get(AbilityKey.TokenNum)) <= 0) {
return false;
}
//*/
if (hasParam("EffectOnly")) { if (hasParam("EffectOnly")) {
final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.EffectOnly); final Boolean effectOnly = (Boolean) runParams.get(AbilityKey.EffectOnly);
if (!effectOnly) { if (!effectOnly) {
@@ -64,7 +58,7 @@ public class ReplaceToken extends ReplacementEffect {
@Override @Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) { public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.TokenNum, filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token))); sa.setReplacingObject(AbilityKey.TokenNum, filterAmount((TokenCreateTable) runParams.get(AbilityKey.Token)));
sa.setReplacingObject(AbilityKey.Token, runParams.get(AbilityKey.Token)); sa.setReplacingObjectsFrom(runParams, AbilityKey.Token, AbilityKey.Cause);
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected)); sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
} }