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) {
if (c.isToken() && !c.isImmutable()) {
Card result = new TokenInfo(c).makeOneToken(newOwner);
CardFactory.copyCopiableCharacteristics(c, result);
CardFactory.copyCopiableCharacteristics(c, result, null, null);
return result;
}
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.card.CardRulesPredicates;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -267,17 +268,27 @@ public class CopyPermanentEffect extends TokenEffectBase {
copy.setOwner(newOwner);
copy.setSetCode(original.getSetCode());
if (sa.hasParam("Embalm")) {
copy.setEmbalmed(true);
copy.setTokenSpawningAbility(sa);
// 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 the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
// 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();
}
if (sa.hasParam("Eternalize")) {
copy.setEternalized(true);
}
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
// force update the now set State
if (original.isTransformable()) {
copy.setState(original.isTransformed() ? CardStateName.Transformed : CardStateName.Original, true, true);
} else {
copy.setState(copy.getCurrentStateName(), true, true);
}
copy.setToken(true);
return copy;

View File

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

View File

@@ -44,8 +44,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script);
}
// set owner
result.setOwner(owner);
result.setTokenSpawningAbility(sa);
tokenTable.put(owner, result, finalAmount);
}
}
@@ -59,8 +58,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
if (result == null) {
throw new RuntimeException("don't find Token for TokenScript: " + script);
}
// set owner
result.setOwner(owner);
result.setTokenSpawningAbility(sa);
tokenTable.put(owner, result, finalAmount);
return tokenTable;
@@ -81,6 +79,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
for (Player p : Sets.newHashSet(tokenTable.rowKeySet())) {
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(p);
repParams.put(AbilityKey.Token, tokenTable);
repParams.put(AbilityKey.Cause, sa);
repParams.put(AbilityKey.EffectOnly, true); // currently only effects can create tokens?
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 SpellAbility effectSourceAbility;
private SpellAbility tokenSpawningAbility;
private GameEntity entityAttachedTo;
@@ -223,8 +224,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private long prototypeTimestamp = -1;
private int timesMutated = 0;
private boolean tributed = false;
private boolean embalmed = false;
private boolean eternalized = false;
private boolean discarded = false;
private boolean flipped = false;
@@ -1562,6 +1561,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
newValue = 0;
}
break;
default:
break;
}
final int delta = oldValue - newValue;
@@ -5885,18 +5886,22 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
tributed = b;
}
public final boolean isEmbalmed() {
return embalmed;
public final SpellAbility getTokenSpawningAbility() {
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() {
return eternalized;
}
public final void setEternalized(final boolean b) {
eternalized = b;
SpellAbility sa = getTokenSpawningAbility();
return sa != null && sa.hasParam("Eternalize");
}
public final int getExertedThisTurn() {

View File

@@ -29,6 +29,7 @@ import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
@@ -85,8 +86,7 @@ public class CardFactory {
out.setToken(true);
// need to copy this values for the tokens
out.setEmbalmed(in.isEmbalmed());
out.setEternalized(in.isEternalized());
out.setTokenSpawningAbility(in.getTokenSpawningAbility());
}
out.setZone(in.getZone());
@@ -125,7 +125,7 @@ public class CardFactory {
final Card original = targetSA.getHostCard();
final Game game = source.getGame();
final Card c = new Card(game.nextCardId(), original.getPaperCard(), game);
copyCopiableCharacteristics(original, c);
copyCopiableCharacteristics(original, c, sourceSA, targetSA);
if (sourceSA.hasParam("NonLegendary")) {
c.removeType(CardType.Supertype.Legendary);
@@ -525,12 +525,12 @@ public class CardFactory {
* @param from the {@link Card} to copy from.
* @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();
if (toIsFaceDown) {
// If to is face down, copy to its front side
to.setState(CardStateName.Original, false);
copyCopiableCharacteristics(from, to);
copyCopiableCharacteristics(from, to, sourceSA, targetSA);
to.setState(CardStateName.FaceDown, false);
return;
}
@@ -545,6 +545,18 @@ public class CardFactory {
copyState(from, CardStateName.Original, to, to.getCurrentStateName());
}
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) {
copyState(from, from.getCurrentStateName(), to, CardStateName.Original);
} else {
@@ -585,6 +597,9 @@ public class CardFactory {
c.setState(in.getCurrentStateName(), false);
c.setRules(in.getRules());
if (in.isTransformed()) {
c.incrementTransformedTimestamp();
}
return c;
}
@@ -738,6 +753,15 @@ public class CardFactory {
final CardState ret2 = new CardState(out, CardStateName.Adventure);
ret2.copyFrom(in.getState(CardStateName.Adventure), false, sa);
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 {
// in all other cases just copy the current state to original
final CardState ret = new CardState(out, CardStateName.Original);
@@ -871,12 +895,14 @@ public class CardFactory {
state.setColor(MagicColor.WHITE);
state.setManaCost(ManaCost.NO_COST);
if (sa.isIntrinsic()) {
String name = TextUtil.fastReplace(
TextUtil.fastReplace(host.getName(), ",", ""),
" ", "_").toLowerCase();
String set = host.getSetCode().toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("embalm_" + name + "_" + set));
}
}
if (sa.hasParam("Eternalize") && out.isEternalized()) {
state.addType("Zombie");
@@ -885,12 +911,14 @@ public class CardFactory {
state.setBasePower(4);
state.setBaseToughness(4);
if (sa.isIntrinsic()) {
String name = TextUtil.fastReplace(
TextUtil.fastReplace(host.getName(), ",", ""),
" ", "_").toLowerCase();
String set = host.getSetCode().toLowerCase();
state.setImageKey(ImageKeys.getTokenKey("eternalize_" + name + "_" + set));
}
}
if (sa.hasParam("GainTextOf") && originalState != null) {
state.setSetCode(originalState.getSetCode());

View File

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