From a298dcf3bccb5d8647dbd1b7b5ba4f4381f96b03 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Sat, 29 Sep 2018 11:22:17 +0200 Subject: [PATCH] TokenScript: make CardTextChange working again SpellAbility: changeTextIntrinsic to sub abilities --- .../src/main/java/forge/card/CardType.java | 15 +++ .../main/java/forge/game/CardTraitBase.java | 67 +++++++++- .../java/forge/game/ability/AbilityUtils.java | 10 +- .../src/main/java/forge/game/card/Card.java | 6 + .../forge/game/card/CardChangedWords.java | 9 +- .../main/java/forge/game/card/CardState.java | 20 ++- .../main/java/forge/game/card/CardUtil.java | 2 + .../java/forge/game/card/token/TokenInfo.java | 120 +++++++++++++++++- .../forge/game/keyword/KeywordCollection.java | 4 + .../forge/game/spellability/SpellAbility.java | 25 ++++ .../main/java/forge/game/trigger/Trigger.java | 50 +++++++- 11 files changed, 308 insertions(+), 20 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java index e3713cf1f1b..ebc12e29266 100644 --- a/forge-core/src/main/java/forge/card/CardType.java +++ b/forge-core/src/main/java/forge/card/CardType.java @@ -188,6 +188,21 @@ public final class CardType implements Comparable, CardTypeView { return supertypes.remove(st); } + public boolean remove(final String str) { + boolean changed = false; + if (CardType.isASupertype(str) && supertypes.remove(stringToSupertype.get(str))) { + changed = true; + } else if (CardType.isACardType(str) && coreTypes.remove(stringToCoreType.get(str))) { + changed = true; + } else if (subtypes.remove(str)) { + changed = true; + } + if (changed) { + calculatedType = null; + } + return changed; + } + public boolean setCreatureTypes(Collection ctypes) { // if it isn't a creature then this has no effect if (!isCreature() && !isTribal()) { diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index b59c994a80c..59c47275a5d 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -46,6 +46,11 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { protected Map sVars = Maps.newHashMap(); + protected Map intrinsicChangedTextColors = Maps.newHashMap(); + protected Map intrinsicChangedTextTypes = Maps.newHashMap(); + protected Map changedTextColors = Maps.newHashMap(); + protected Map changedTextTypes = Maps.newHashMap(); + /** Keys of descriptive (text) parameters. */ private static final ImmutableList descriptiveKeys = ImmutableList.builder() .add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build(); @@ -53,6 +58,11 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { private static final ImmutableList mutableKeys = ImmutableList.builder() .add("AddAbility").build(); + /** + * Keys that should not changed + */ + private static final ImmutableList noChangeKeys = ImmutableList.builder() + .add("TokenScript", "LegacyImage", "TokenImage").build(); /** * Sets the temporary. * @@ -449,9 +459,15 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { } 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 (descriptiveKeys.contains(key)) { + if (noChangeKeys.contains(key)) { + continue; + } else if (descriptiveKeys.contains(key)) { // change descriptions differently newValue = AbilityUtils.applyDescriptionTextChangeEffects(value, this); } else if (mutableKeys.contains(key)) { @@ -515,4 +531,53 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { public Set getSVars() { return sVars.keySet(); } + + public Map getChangedTextColors() { + return _combineChangedMap(intrinsicChangedTextColors, changedTextColors); + } + public Map getChangedTextTypes() { + return _combineChangedMap(intrinsicChangedTextTypes, changedTextTypes); + } + + private Map _combineChangedMap(Map input, Map output) { + // no need to do something, just return hash + if (input.isEmpty()) { + return output; + } + if (output.isEmpty()) { + return input; + } + // magic combine them + Map result = Maps.newHashMap(output); + for (Map.Entry e : input.entrySet()) { + String value = e.getValue(); + result.put(e.getKey(), output.containsKey(value) ? output.get(value) : value); + } + return result; + } + + public void changeTextIntrinsic(Map colorMap, Map typeMap) { + intrinsicChangedTextColors = colorMap; + intrinsicChangedTextTypes = typeMap; + 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.applyTextChangeEffects(value, true, colorMap, typeMap); + }else if (this.getHostCard().hasSVar(value)) { + // don't change literal SVar names! + continue; + } else { + newValue = AbilityUtils.applyTextChangeEffects(value, false, colorMap, typeMap); + } + + if (newValue != null) { + this.mapParams.put(key, newValue); + } + } + // this does overwrite the original MapParams + this.originalMapParams = Maps.newHashMap(this.mapParams); + } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 3401a5d6a80..ab4032c090a 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1724,12 +1724,18 @@ public class AbilityUtils { } private static final String applyTextChangeEffects(final String def, final Card card, final boolean isDescriptive) { + return applyTextChangeEffects(def, isDescriptive, + card.getChangedTextColorWords(), card.getChangedTextTypeWords()); + } + + public static final String applyTextChangeEffects(final String def, final boolean isDescriptive, + Map colorMap, Map typeMap) { if (StringUtils.isEmpty(def)) { return def; } String replaced = def; - for (final Entry e : card.getChangedTextColorWords().entrySet()) { + for (final Entry e : colorMap.entrySet()) { final String key = e.getKey(); String value; if (key.equals("Any")) { @@ -1750,7 +1756,7 @@ public class AbilityUtils { replaced = replaced.replaceAll("(?)" + key, value); } } - for (final Entry e : card.getChangedTextTypeWords().entrySet()) { + for (final Entry e : typeMap.entrySet()) { final String key = e.getKey(); final String pkey = CardType.getPluralType(key); final String pvalue = getReplacedText(pkey, CardType.getPluralType(e.getValue()), isDescriptive); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 6fc7ffde41c..f56315a4a29 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3640,6 +3640,12 @@ public class Card extends GameEntity implements Comparable { } } + public final void removeIntrinsicKeyword(final KeywordInterface s) { + if (currentState.removeIntrinsicKeyword(s)) { + currentState.getView().updateKeywords(this, currentState); + } + } + public Collection getExtrinsicKeyword() { return extrinsicKeyword.getValues(); } diff --git a/forge-game/src/main/java/forge/game/card/CardChangedWords.java b/forge-game/src/main/java/forge/game/card/CardChangedWords.java index d62f15dde32..ae8719dee69 100644 --- a/forge-game/src/main/java/forge/game/card/CardChangedWords.java +++ b/forge-game/src/main/java/forge/game/card/CardChangedWords.java @@ -7,8 +7,6 @@ import java.util.SortedMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; -import forge.card.CardType; - public final class CardChangedWords { private final SortedMap map = Maps.newTreeMap(); @@ -68,14 +66,9 @@ public final class CardChangedWords { // the actual change (b->c) resultCache.put(ccw.getOriginalWord(), ccw.getNewWord()); - - // possible plural form - final String singular = CardType.getPluralType(ccw.getOriginalWord()); - if (!singular.equals(ccw.getOriginalWord())) { - resultCache.put(singular, ccw.getNewWord()); - } } + // TODO should that be removed? for (final String key : ImmutableList.copyOf(resultCache.keySet())) { if (!key.equals("Any")) { resultCache.put(key.toLowerCase(), resultCache.get(key).toLowerCase()); diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 483077cae84..5372ddfa18e 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -231,6 +231,9 @@ public class CardState extends GameObject { public final boolean removeIntrinsicKeyword(final String s) { return intrinsicKeywords.remove(s); } + public final boolean removeIntrinsicKeyword(final KeywordInterface s) { + return intrinsicKeywords.remove(s); + } public final FCollectionView getSpellAbilities() { FCollection newCol = new FCollection(manaAbilities); @@ -539,7 +542,7 @@ public class CardState extends GameObject { intrinsicKeywords.insert(inst); } } - + public void updateChangedText() { final List allAbs = ImmutableList.builder() .addAll(manaAbilities) @@ -554,4 +557,19 @@ public class CardState extends GameObject { } } } + + public void changeTextIntrinsic(Map colorMap, Map typeMap) { + final List allAbs = ImmutableList.builder() + .addAll(manaAbilities) + .addAll(nonManaAbilities) + .addAll(triggers) + .addAll(replacementEffects) + .addAll(staticAbilities) + .build(); + for (final CardTraitBase ctb : allAbs) { + if (ctb.isIntrinsic()) { + ctb.changeTextIntrinsic(colorMap, typeMap); + } + } + } } diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 0f959f598d5..8db0b965455 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -281,6 +281,8 @@ public final class CardUtil { newCopy.setChangedCardKeywords(in.getChangedCardKeywords()); newCopy.setChangedCardTypes(in.getChangedCardTypesMap()); + newCopy.copyChangedTextFrom(in); + newCopy.setMeldedWith(in.getMeldedWith()); newCopy.setTimestamp(in.getTimestamp()); diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java index d54b7b4256f..86f319baf80 100644 --- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java +++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java @@ -12,6 +12,7 @@ import forge.game.Game; import forge.game.card.Card; import forge.game.card.CardFactory; import forge.game.card.CardFactoryUtil; +import forge.game.card.CardUtil; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -20,6 +21,8 @@ import forge.item.PaperToken; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + public class TokenInfo { final String name; final String imageName; @@ -229,10 +232,121 @@ public class TokenInfo { String edition = host.getSetCode(); PaperToken token = StaticData.instance().getAllTokens().getToken(script, edition); - // TODO add Card Text Change from SpellAbility - if (token != null) { - return Card.fromPaperCard(token, null, game); + final Card result = Card.fromPaperCard(token, null, game); + + // update Token with CardTextChanges + Map colorMap = sa.getChangedTextColors(); + Map typeMap = sa.getChangedTextTypes(); + if (!colorMap.isEmpty()) { + if (!result.isColorless()) { + // change Token Colors + byte color = CardUtil.getColors(result).getColor(); + + for (final Map.Entry e : colorMap.entrySet()) { + byte v = MagicColor.fromName(e.getValue()); + // Any used by Swirl the Mists + if ("Any".equals(e.getKey())) { + for (final byte c : MagicColor.WUBRG) { + // try to replace color flips + if ((color & c) != 0) { + color &= ~c; + color |= v; + } + } + } else { + byte c = MagicColor.fromName(e.getKey()); + // try to replace color flips + if ((color & c) != 0) { + color &= ~c; + color |= v; + } + } + } + + result.setColor(color); + } + } + if (!typeMap.isEmpty()) { + String oldName = result.getName(); + + CardType type = new CardType(result.getType()); + String joinedName = StringUtils.join(type.getSubtypes(), " "); + final boolean nameGenerated = oldName.equals(joinedName); + boolean typeChanged = false; + + if (!Iterables.isEmpty(type.getSubtypes())) { + for (final Map.Entry e : typeMap.entrySet()) { + if (type.hasSubtype(e.getKey())) { + type.remove(e.getKey()); + type.add(e.getValue()); + typeChanged = true; + } + } + } + + if (typeChanged) { + result.setType(type); + + // update generated Name + if (nameGenerated) { + result.setName(StringUtils.join(type.getSubtypes(), " ")); + } + } + } + + // replace Intrinsic Keyword + List toRemove = Lists.newArrayList(); + List toAdd = Lists.newArrayList(); + for (final KeywordInterface k : result.getCurrentState().getIntrinsicKeywords()) { + final String o = k.getOriginal(); + // only Modifiable should go there + if (!CardUtil.isKeywordModifiable(o)) { + continue; + } + String r = new String(o); + // replace types + for (final Map.Entry e : typeMap.entrySet()) { + final String key = e.getKey(); + final String pkey = CardType.getPluralType(key); + final String value = e.getValue(); + final String pvalue = CardType.getPluralType(e.getValue()); + r = r.replaceAll(pkey, pvalue); + r = r.replaceAll(key, value); + } + // replace color words + for (final Map.Entry e : colorMap.entrySet()) { + final String vName = e.getValue(); + final String vCaps = StringUtils.capitalize(vName); + final String vLow = vName.toLowerCase(); + if ("Any".equals(e.getKey())) { + for (final byte c : MagicColor.WUBRG) { + final String cName = MagicColor.toLongString(c); + final String cNameCaps = StringUtils.capitalize(cName); + final String cNameLow = cName.toLowerCase(); + r = r.replaceAll(cNameCaps, vCaps); + r = r.replaceAll(cNameLow, vLow); + } + } else { + final String cName = e.getKey(); + final String cNameCaps = StringUtils.capitalize(cName); + final String cNameLow = cName.toLowerCase(); + r = r.replaceAll(cNameCaps, vCaps); + r = r.replaceAll(cNameLow, vLow); + } + } + if (!r.equals(o)) { + toRemove.add(k); + toAdd.add(r); + } + } + for (final KeywordInterface k : toRemove) { + result.getCurrentState().removeIntrinsicKeyword(k); + } + result.addIntrinsicKeywords(toAdd); + + result.getCurrentState().changeTextIntrinsic(colorMap, typeMap); + return result; } return null; diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java index f00b65cf76d..98623cf6d72 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java @@ -96,6 +96,10 @@ public class KeywordCollection implements Iterable, Serializable { return result; } + public boolean remove(KeywordInterface keyword) { + return map.remove(keyword.getKeyword(), keyword); + } + public boolean removeAll(Iterable keywords) { boolean result = false; for (String k : keywords) { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index afa679d269d..9eddd745b4a 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1709,6 +1709,31 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } + /* (non-Javadoc) + * @see forge.game.CardTraitBase#changeTextIntrinsic(java.util.Map, java.util.Map) + */ + @Override + public void changeTextIntrinsic(Map colorMap, Map typeMap) { + super.changeTextIntrinsic(colorMap, typeMap); + + if (subAbility != null) { + // if the parent of the subability is not this, + // then there might be a loop + if (subAbility.getParent() == this) { + subAbility.changeTextIntrinsic(colorMap, typeMap); + } + } + for (AbilitySub sa : additionalAbilities.values()) { + sa.changeTextIntrinsic(colorMap, typeMap); + } + + for (List list : additionalAbilityLists.values()) { + for (AbilitySub sa : list) { + sa.changeTextIntrinsic(colorMap, typeMap); + } + } + } + @Override public void setIntrinsic(boolean i) { super.setIntrinsic(i); diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index 8b39080b26f..a3dd3f3fa93 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -169,11 +169,7 @@ public abstract class Trigger extends TriggerReplacementBase { if (!desc.contains("ABILITY")) { return desc; } - SpellAbility sa = getOverridingAbility(); - if (sa == null && this.mapParams.containsKey("Execute")) { - sa = AbilityFactory.getAbility(state, this.mapParams.get("Execute")); - setOverridingAbility(sa); - } + SpellAbility sa = ensureAbility(); return replaceAbilityText(desc, sa); @@ -583,4 +579,48 @@ public abstract class Trigger extends TriggerReplacementBase { throw new RuntimeException("Trigger : clone() error, " + ex); } } + + + /* (non-Javadoc) + * @see forge.game.CardTraitBase#changeText() + */ + @Override + public void changeText() { + if (!isIntrinsic()) { + return; + } + super.changeText(); + + ensureAbility(); + + if (getOverridingAbility() != null) { + getOverridingAbility().changeText(); + } + } + + /* (non-Javadoc) + * @see forge.game.CardTraitBase#changeTextIntrinsic(java.util.Map, java.util.Map) + */ + @Override + public void changeTextIntrinsic(Map colorMap, Map typeMap) { + if (!isIntrinsic()) { + return; + } + super.changeTextIntrinsic(colorMap, typeMap); + + ensureAbility(); + + if (getOverridingAbility() != null) { + getOverridingAbility().changeTextIntrinsic(colorMap, typeMap); + } + } + + private SpellAbility ensureAbility() { + SpellAbility sa = getOverridingAbility(); + if (sa == null && hasParam("Execute")) { + sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute")); + setOverridingAbility(sa); + } + return sa; + } }