Fix ChangeText not being fully applied for trigger effects + traits gained by static (#4619)

* Fix ChangeText not being applied for trigger effects

* Clean up

* Better approach

* Better approach 2

* Fix traits gained by static abilities missing original host text changes

* Refresh cache if text of static host gets changed

* Clean up

---------

Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.60>
Co-authored-by: TRT <>
This commit is contained in:
tool4ever
2024-02-05 05:10:33 +01:00
committed by GitHub
parent 3ef13bb24c
commit c10ce3ee32
10 changed files with 78 additions and 68 deletions

View File

@@ -531,31 +531,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
return true;
}
public void changeText() {
// copy changed text words into card trait there
this.changedTextColors = getHostCard().getChangedTextColorWords();
this.changedTextTypes = getHostCard().getChangedTextTypeWords();
for (final String key : this.mapParams.keySet()) {
final String value = this.originalMapParams.get(key), newValue;
if (noChangeKeys.contains(key)) {
continue;
} else if (descriptiveKeys.contains(key)) {
// change descriptions differently
newValue = AbilityUtils.applyDescriptionTextChangeEffects(value, this);
} else if (this.getHostCard().hasSVar(value)) {
// don't change literal SVar names!
newValue = null;
} else {
newValue = AbilityUtils.applyAbilityTextChangeEffects(value, this);
}
if (newValue != null) {
this.mapParams.put(key, newValue);
}
}
}
@Override
public CardView getCardView() {
return CardView.get(hostCard);
@@ -694,9 +669,37 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
this.originalMapParams = Maps.newHashMap(this.mapParams);
}
public void changeText() {
// copy changed text words into card trait there
this.changedTextColors = getHostCard().getChangedTextColorWords();
this.changedTextTypes = getHostCard().getChangedTextTypeWords();
for (final String key : this.mapParams.keySet()) {
final String value = this.originalMapParams.get(key), newValue;
if (noChangeKeys.contains(key)) {
continue;
} else if (descriptiveKeys.contains(key)) {
// change descriptions differently
newValue = AbilityUtils.applyDescriptionTextChangeEffects(value, this);
} else if (this.getHostCard().hasSVar(value)) {
// don't change literal SVar names!
newValue = null;
} else {
newValue = AbilityUtils.applyAbilityTextChangeEffects(value, this);
}
if (newValue != null) {
this.mapParams.put(key, newValue);
}
}
}
protected void copyHelper(CardTraitBase copy, Card host) {
copyHelper(copy, host, false);
}
protected void copyHelper(CardTraitBase copy, Card host, boolean keepTextChanges) {
copy.originalMapParams = Maps.newHashMap(originalMapParams);
copy.mapParams = Maps.newHashMap(originalMapParams);
copy.mapParams = Maps.newHashMap(keepTextChanges ? mapParams : originalMapParams);
copy.setSVars(sVars);
copy.setCardState(cardState);
// dont use setHostCard to not trigger the not copied parts yet

View File

@@ -25,7 +25,6 @@ import forge.game.card.token.TokenInfo;
import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Lang;
@@ -52,13 +51,10 @@ public class AmassEffect extends TokenEffectBase {
@Override
public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard();
final Game game = card.getGame();
final Card source = sa.getHostCard();
final Game game = source.getGame();
final Player activator = sa.getActivatingPlayer();
final PlayerController pc = activator.getController();
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
final boolean remember = sa.hasParam("RememberAmass");
final int amount = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("Num", "1"), sa);
final String type = sa.getParam("Type");
// create army token if needed
@@ -84,29 +80,25 @@ public class AmassEffect extends TokenEffectBase {
}
}
Map<String, Object> params = Maps.newHashMap();
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
params.put("Amount", 1);
CardCollectionView tgtCards = CardLists.getType(activator.getCardsIn(ZoneType.Battlefield), "Army");
tgtCards = pc.chooseCardsForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), 1, 1, false, params);
if (tgtCards.isEmpty()) {
return;
}
Map<String, Object> params = Maps.newHashMap();
params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
params.put("Amount", 1);
Card tgt = activator.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), false, params);
if (sa.hasParam("RememberAmass")) {
source.addRemembered(tgt);
}
GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card tgtCard : tgtCards) {
tgtCard.addCounter(CounterEnumType.P1P1, amount, activator, table);
if (remember) {
card.addRemembered(tgtCard);
}
}
tgt.addCounter(CounterEnumType.P1P1, amount, activator, table);
table.replaceCounterEffect(game, sa, true);
// change type after counters
long ts = game.getNextTimestamp();
for (final Card tgtCard : tgtCards) {
tgtCard.addChangedCardTypes(CardType.parse(type, true), null, false, EnumSet.noneOf(RemoveType.class), ts, 0, true, false);
}
tgt.addChangedCardTypes(CardType.parse(type, true), null, false, EnumSet.noneOf(RemoveType.class), game.getNextTimestamp(), 0, true, false);
}
}

View File

@@ -4658,8 +4658,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final SpellAbility getSpellAbilityForStaticAbility(final String str, final StaticAbility stAb) {
SpellAbility result = storedSpellAbility.get(stAb, str);
if (result == null) {
if (!canUseCachedTrait(result, stAb)) {
result = AbilityFactory.getAbility(str, this, stAb);
// apply text changes from the statics host
result.changeTextIntrinsic(stAb.getChangedTextColors(), stAb.getChangedTextTypes());
result.setIntrinsic(false);
result.setGrantorStatic(stAb);
storedSpellAbility.put(stAb, str, result);
@@ -4669,8 +4671,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final Trigger getTriggerForStaticAbility(final String str, final StaticAbility stAb) {
Trigger result = storedTrigger.get(stAb, str);
if (result == null) {
if (!canUseCachedTrait(result, stAb)) {
result = TriggerHandler.parseTrigger(str, this, false, stAb);
// apply text changes from the statics host
result.changeTextIntrinsic(stAb.getChangedTextColors(), stAb.getChangedTextTypes());
storedTrigger.put(stAb, str, result);
}
return result;
@@ -4699,8 +4703,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final ReplacementEffect getReplacementEffectForStaticAbility(final String str, final StaticAbility stAb) {
ReplacementEffect result = storedReplacementEffect.get(stAb, str);
if (result == null) {
if (!canUseCachedTrait(result, stAb)) {
result = ReplacementHandler.parseReplacement(str, this, false, stAb);
// apply text changes from the statics host
result.changeTextIntrinsic(stAb.getChangedTextColors(), stAb.getChangedTextTypes());
storedReplacementEffect.put(stAb, str, result);
}
return result;
@@ -4708,8 +4714,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public final StaticAbility getStaticAbilityForStaticAbility(final String str, final StaticAbility stAb) {
StaticAbility result = storedStaticAbility.get(stAb, str);
if (result == null) {
if (!canUseCachedTrait(result, stAb)) {
result = StaticAbility.create(str, this, stAb.getCardState(), false);
// apply text changes from the statics host
result.changeTextIntrinsic(stAb.getChangedTextColors(), stAb.getChangedTextTypes());
storedStaticAbility.put(stAb, str, result);
}
return result;
@@ -4781,6 +4789,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return result;
}
private boolean canUseCachedTrait(CardTraitBase cached, CardTraitBase stAb) {
if (cached == null) {
return false;
}
return cached.getChangedTextColors().equals(stAb.getChangedTextColors()) && cached.getChangedTextTypes().equals(stAb.getChangedTextTypes());
}
public final void addChangedCardTraits(Collection<SpellAbility> spells, Collection<SpellAbility> removedAbilities,
Collection<Trigger> trigger, Collection<ReplacementEffect> replacements, Collection<StaticAbility> statics,
boolean removeAll, boolean removeNonMana, long timestamp, long staticId) {

View File

@@ -574,6 +574,9 @@ public class CardFactory {
}
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki) {
copySpellAbility(from, to, host, p, lki, false);
}
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki, final boolean keepTextChanges) {
if (from.usesTargeting()) {
to.setTargetRestrictions(from.getTargetRestrictions());
}
@@ -581,16 +584,16 @@ public class CardFactory {
to.setStackDescription(from.getOriginalStackDescription());
if (from.getSubAbility() != null) {
to.setSubAbility((AbilitySub) from.getSubAbility().copy(host, p, lki));
to.setSubAbility((AbilitySub) from.getSubAbility().copy(host, p, lki, keepTextChanges));
}
for (Map.Entry<String, SpellAbility> e : from.getAdditionalAbilities().entrySet()) {
to.setAdditionalAbility(e.getKey(), e.getValue().copy(host, p, lki));
to.setAdditionalAbility(e.getKey(), e.getValue().copy(host, p, lki, keepTextChanges));
}
for (Map.Entry<String, List<AbilitySub>> e : from.getAdditionalAbilityLists().entrySet()) {
to.setAdditionalAbilityList(e.getKey(), Lists.transform(e.getValue(), new Function<AbilitySub, AbilitySub>() {
@Override
public AbilitySub apply(AbilitySub input) {
return (AbilitySub) input.copy(host, p, lki);
return (AbilitySub) input.copy(host, p, lki, keepTextChanges);
}
}));
}

View File

@@ -1473,7 +1473,7 @@ public class CardFactoryUtil {
sbTrig.append("Living Weapon (").append(inst.getReminderText()).append(")");
final StringBuilder sbGerm = new StringBuilder();
sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenScript$ b_0_0_phyrexian_germ |TokenOwner$ You | RememberTokens$ True");
sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenScript$ b_0_0_phyrexian_germ | TokenOwner$ You | RememberTokens$ True");
final SpellAbility saGerm = AbilityFactory.getAbility(sbGerm.toString(), card);

View File

@@ -1158,6 +1158,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return copy(host, this.getActivatingPlayer(), lki);
}
public SpellAbility copy(Card host, Player activ, final boolean lki) {
return copy(host, activ, lki, false);
}
public SpellAbility copy(Card host, Player activ, final boolean lki, final boolean keepTextChanges) {
SpellAbility clone = null;
try {
clone = (SpellAbility) clone();
@@ -1166,7 +1169,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
// don't use setHostCard to not trigger the not copied parts yet
copyHelper(clone, host);
copyHelper(clone, host, lki || keepTextChanges);
// always set this to false, it is only set in CopyEffect
clone.mayChooseNewTargets = false;
@@ -1205,7 +1208,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
clone.additionalAbilities = Maps.newHashMap();
clone.additionalAbilityLists = Maps.newHashMap();
// run special copy Ability to make a deep copy
CardFactory.copySpellAbility(this, clone, host, activ, lki);
CardFactory.copySpellAbility(this, clone, host, activ, lki, keepTextChanges);
} catch (final CloneNotSupportedException e) {
System.err.println(e);
}

View File

@@ -830,7 +830,7 @@ public final class StaticAbilityContinuous {
if (!stAb.matchesValidParam("GainsValidAbilities", sa)) {
continue;
}
SpellAbility newSA = sa.copy(affectedCard, false);
SpellAbility newSA = sa.copy(affectedCard, sa.getActivatingPlayer(), false, true);
if (params.containsKey("GainsAbilitiesLimitPerTurn")) {
newSA.setRestrictions(sa.getRestrictions());
newSA.getRestrictions().setLimitToCheck(params.get("GainsAbilitiesLimitPerTurn"));

View File

@@ -583,7 +583,6 @@ public abstract class Trigger extends TriggerReplacementBase {
}
}
/* (non-Javadoc)
* @see forge.game.CardTraitBase#changeText()
*/
@@ -606,9 +605,6 @@ public abstract class Trigger extends TriggerReplacementBase {
*/
@Override
public void changeTextIntrinsic(Map<String, String> colorMap, Map<String, String> typeMap) {
if (!isIntrinsic()) {
return;
}
super.changeTextIntrinsic(colorMap, typeMap);
SpellAbility sa = ensureAbility();

View File

@@ -521,7 +521,7 @@ public class TriggerHandler {
controller = regtrig.getSpawningAbility().getActivatingPlayer();
}
// need to copy the SA because of TriggeringObjects
sa = sa.copy(host, controller, false);
sa = sa.copy(host, controller, false, true);
}
sa.setTrigger(regtrig);

View File

@@ -302,9 +302,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (si == null && sp.isActivatedAbility() && !sp.isCopied()) {
// if not already copied use a fresh instance
SpellAbility original = sp;
sp = sp.copy();
// need to reapply text changes
sp.changeText();
sp = sp.copy(sp.getHostCard(), activator, false, true);
sp.setOriginalAbility(original);
original.clearTargets();
original.setXManaCostPaid(null);