diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java index c8e377dccdb..f6801e7e723 100644 --- a/forge-core/src/main/java/forge/card/CardType.java +++ b/forge-core/src/main/java/forge/card/CardType.java @@ -247,6 +247,7 @@ public boolean remove(final Supertype st) { return supertypes.remove(st); } + @Deprecated public boolean remove(final String str) { boolean changed = false; @@ -681,6 +682,14 @@ public int compareTo(final CardType o) { return toString().compareTo(o.toString()); } + @Override + public boolean equals(final Object o) { + if (!(o instanceof CardType)) { + return false; + } + return toString().equals(o.toString()); + } + public boolean sharesCreaturetypeWith(final CardTypeView ctOther) { if (ctOther == null) { return false; diff --git a/forge-core/src/main/java/forge/card/ChangedColorWord.java b/forge-core/src/main/java/forge/card/ChangedColorWord.java new file mode 100644 index 00000000000..0b1ae2c698d --- /dev/null +++ b/forge-core/src/main/java/forge/card/ChangedColorWord.java @@ -0,0 +1,23 @@ +package forge.card; + +import java.util.Map; + +public record ChangedColorWord(MagicColor.Color oldColor, MagicColor.Color newColor) implements IChangedText { + + @Override + public void applyColorChanges(Map result) { + if (oldColor == null) { + for (MagicColor.Color old : ColorSet.WUBRG) { + result.put(old, newColor); + } + } else { + for (Map.Entry e : result.entrySet()) { + if (e.getValue().equals(oldColor)) { + e.setValue(newColor); + } + } + result.put(oldColor, newColor); + } + + } +} diff --git a/forge-core/src/main/java/forge/card/IChangedText.java b/forge-core/src/main/java/forge/card/IChangedText.java new file mode 100644 index 00000000000..a219957c5d8 --- /dev/null +++ b/forge-core/src/main/java/forge/card/IChangedText.java @@ -0,0 +1,8 @@ +package forge.card; + +import java.util.Map; + +public interface IChangedText { + default void applyColorChanges(Map result) { } + default void applyTypeChanges(Map result) { } +} diff --git a/forge-core/src/main/java/forge/card/ITextChanges.java b/forge-core/src/main/java/forge/card/ITextChanges.java new file mode 100644 index 00000000000..b468ec3589c --- /dev/null +++ b/forge-core/src/main/java/forge/card/ITextChanges.java @@ -0,0 +1,13 @@ +package forge.card; + +import java.util.Map; + +public interface ITextChanges { + Map colorChanges(); + Map typeChanges(); + + boolean isEmpty(); + + ITextChanges combine(ITextChanges output); + default ITextChanges getView() { return this; } +} diff --git a/forge-core/src/main/java/forge/card/ResetChangedText.java b/forge-core/src/main/java/forge/card/ResetChangedText.java new file mode 100644 index 00000000000..629bf9a3bf6 --- /dev/null +++ b/forge-core/src/main/java/forge/card/ResetChangedText.java @@ -0,0 +1,16 @@ +package forge.card; + +import java.util.Map; + +public record ResetChangedText() implements IChangedText { + + @Override + public void applyColorChanges(Map result) { + result.clear(); + } + @Override + public void applyTypeChanges(Map result) { + result.clear(); + } + +} diff --git a/forge-core/src/main/java/forge/card/TextChanges.java b/forge-core/src/main/java/forge/card/TextChanges.java new file mode 100644 index 00000000000..8d7c50e676e --- /dev/null +++ b/forge-core/src/main/java/forge/card/TextChanges.java @@ -0,0 +1,79 @@ +package forge.card; + +import com.google.common.collect.Maps; +import com.google.common.collect.Table; +import com.google.common.collect.TreeBasedTable; + +import java.util.Map; + +public class TextChanges implements ITextChanges { + + public static ITextChanges EMPTY = TextChangesView.EMPTY; + + private final Table map = TreeBasedTable.create(); + private final Map colorChanges = Maps.newHashMap(); + private final Map typeChanges = Maps.newHashMap(); + private boolean isDirty = false; + + public long add(final long timestamp, final long staticId, final IChangedText changes) { + map.put(timestamp, staticId, changes); + isDirty = true; + return timestamp; + } + + public boolean remove(final long timestamp, final long staticId) { + isDirty = true; + return map.remove(timestamp, staticId) != null; + } + + public void clear() { + map.clear(); + colorChanges.clear(); + typeChanges.clear(); + isDirty = false; + } + public void copyFrom(final TextChanges other) { + map.clear(); + map.putAll(other.map); + isDirty = true; + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public Map colorChanges() { + refreshCache(); + return this.colorChanges; + } + + @Override + public Map typeChanges() { + refreshCache(); + return this.typeChanges; + } + + private void refreshCache() { + if (isDirty) { + colorChanges.clear(); + typeChanges.clear(); + for (final IChangedText changes : this.map.values()) { + changes.applyColorChanges(colorChanges); + changes.applyTypeChanges(typeChanges); + } + isDirty = false; + } + } + + @Override + public TextChangesView getView() { + return new TextChangesView(colorChanges(), typeChanges()); + } + + @Override + public ITextChanges combine(ITextChanges output) { + return getView().combine(output); + } +} diff --git a/forge-core/src/main/java/forge/card/TextChangesView.java b/forge-core/src/main/java/forge/card/TextChangesView.java new file mode 100644 index 00000000000..cf92ffe9e4c --- /dev/null +++ b/forge-core/src/main/java/forge/card/TextChangesView.java @@ -0,0 +1,47 @@ +package forge.card; + +import com.google.common.collect.Maps; + +import java.util.Map; + +public record TextChangesView(Map colorChanges, Map typeChanges) implements ITextChanges { + + public static TextChangesView EMPTY = new TextChangesView(Map.of(), Map.of()); + + @Override + public boolean isEmpty() { + return colorChanges.isEmpty() && typeChanges.isEmpty(); + } + + @Override + public ITextChanges combine(ITextChanges output) { + if (output.isEmpty()) { + return this; + } + if (this.isEmpty()) { + return output; + }; + + return new TextChangesView( + _combineChangedMap(colorChanges(), output.colorChanges()), + _combineChangedMap(typeChanges(), output.typeChanges()) + ); + } + + 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(input); + for (Map.Entry e : result.entrySet()) { + e.setValue(output.getOrDefault(e.getValue(), e.getValue())); + } + result.putAll(output); + return result; + } +} diff --git a/forge-core/src/main/java/forge/card/WordChangedType.java b/forge-core/src/main/java/forge/card/WordChangedType.java index 9652f359de7..296ff72fd99 100644 --- a/forge-core/src/main/java/forge/card/WordChangedType.java +++ b/forge-core/src/main/java/forge/card/WordChangedType.java @@ -1,6 +1,8 @@ package forge.card; -public record WordChangedType(String oldWord, String newWord) implements ICardChangedType { +import java.util.Map; + +public record WordChangedType(String oldWord, String newWord) implements ICardChangedType, IChangedText { @Override public CardType applyChanges(CardType newType) { @@ -10,4 +12,14 @@ public CardType applyChanges(CardType newType) { } return newType; } + + @Override + public void applyTypeChanges(Map result) { + for (Map.Entry e : result.entrySet()) { + if (e.getValue().equals(oldWord)) { + e.setValue(newWord); + } + } + result.put(oldWord, newWord); + } } diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 2bf2caed7e9..68a37667c5a 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -12,7 +12,9 @@ import com.google.common.collect.Lists; import forge.card.CardStateName; +import forge.card.ITextChanges; import forge.card.MagicColor; +import forge.card.TextChanges; import forge.card.mana.ManaAtom; import forge.game.ability.AbilityUtils; import forge.game.card.Card; @@ -55,10 +57,8 @@ public abstract class CardTraitBase implements GameObject, IHasCardView, IHasSVa protected Map sVars = Maps.newTreeMap(); - protected Map intrinsicChangedTextColors = Maps.newHashMap(); - protected Map intrinsicChangedTextTypes = Maps.newHashMap(); - protected Map changedTextColors = Maps.newHashMap(); - protected Map changedTextTypes = Maps.newHashMap(); + protected ITextChanges intrinsicTextChanges = TextChanges.EMPTY; + protected ITextChanges textChanges = TextChanges.EMPTY; /** Keys of descriptive (text) parameters. */ private static final ImmutableList descriptiveKeys = ImmutableList.builder() @@ -658,45 +658,24 @@ public boolean isCopiedTrait() { return !getHostCard().equals(getCardState().getCard()); } - 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.getOrDefault(value, value)); - } - return result; + public ITextChanges getTextChanges() { + return intrinsicTextChanges.combine(textChanges); } - public void changeTextIntrinsic(Map colorMap, Map typeMap) { - intrinsicChangedTextColors = colorMap; - intrinsicChangedTextTypes = typeMap; + public void changeTextIntrinsic(ITextChanges textChanges) { + this.intrinsicTextChanges = textChanges.getView(); 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); + newValue = AbilityUtils.applyTextChangeEffects(value, true, textChanges); } else if (this.getHostCard().hasSVar(value)) { // don't change literal SVar names! continue; } else { - newValue = AbilityUtils.applyTextChangeEffects(value, false, colorMap, typeMap); + newValue = AbilityUtils.applyTextChangeEffects(value, false, textChanges); } if (newValue != null) { @@ -709,8 +688,7 @@ public void changeTextIntrinsic(Map colorMap, Map public void changeText() { // copy changed text words into card trait there - this.changedTextColors = getHostCard().getChangedTextColorWords(); - this.changedTextTypes = getHostCard().getChangedTextTypeWords(); + this.textChanges = getHostCard().getTextChanges().getView(); for (final String key : this.mapParams.keySet()) { final String value = this.originalMapParams.get(key), newValue; diff --git a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java index 4be656cd779..e2b66691f2d 100644 --- a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java +++ b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java @@ -1,9 +1,9 @@ package forge.game; import java.util.EnumSet; -import java.util.Map; import java.util.Set; +import forge.card.ITextChanges; import forge.game.card.Card; import forge.game.card.CardState; import forge.game.keyword.KeywordInterface; @@ -104,17 +104,14 @@ public void changeText() { } } - /* (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); + public void changeTextIntrinsic(ITextChanges textChanges) { + super.changeTextIntrinsic(textChanges); SpellAbility sa = ensureAbility(); if (sa != null) { - sa.changeTextIntrinsic(colorMap, typeMap); + sa.changeTextIntrinsic(textChanges); } } } 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 97a5399df12..8707abbd87e 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -7,6 +7,7 @@ import forge.card.CardType; import forge.card.CardTypeView; import forge.card.ColorSet; +import forge.card.ITextChanges; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.card.mana.ManaCost; @@ -2977,21 +2978,21 @@ public static final String applyAbilityTextChangeEffects(final String def, final if (ability == null || !ability.isIntrinsic() || ability.hasParam("LockInText")) { return def; } - return applyTextChangeEffects(def, ability.getHostCard(), false); + return applyTextChangeEffects(def, false, ability.getTextChanges()); } - public static final String applyKeywordTextChangeEffects(final String kw, final Card card) { + public static final String applyKeywordTextChangeEffects(final String kw, ITextChanges changes) { if (!CardUtil.isKeywordModifiable(kw)) { return kw; } - return applyTextChangeEffects(kw, card, false); + return applyTextChangeEffects(kw, false, changes); } public static final String applyDescriptionTextChangeEffects(final String def, final CardTraitBase ability) { if (ability == null || !ability.isIntrinsic() || ability.hasParam("LockInText")) { return def; } - return applyTextChangeEffects(def, ability.getHostCard(), true); + return applyTextChangeEffects(def, true, ability.getTextChanges()); } /** @@ -3006,35 +3007,19 @@ public static final String applyDescriptionTextChangeEffects(final String def, f return applyTextChangeEffects(def, card, true); } private static String applyTextChangeEffects(final String def, final Card card, final boolean isDescriptive) { - return applyTextChangeEffects(def, isDescriptive, card.getChangedTextColorWords(), card.getChangedTextTypeWords()); + return applyTextChangeEffects(def, isDescriptive, card.getTextChanges()); } - public static final String applyTextChangeEffects(final String def, final boolean isDescriptive, - Map colorMap, Map typeMap) { + public static final String applyTextChangeEffects(final String def, final boolean isDescriptive, ITextChanges changes) { if (StringUtils.isEmpty(def)) { return def; } - String replaced = def; - for (final Entry e : colorMap.entrySet()) { - final String key = e.getKey(); - if (key.equals("Any")) { - for (final byte c : MagicColor.WUBRG) { - final String colorLowerCase = MagicColor.toLongString(c).toLowerCase(), - colorCaptCase = StringUtils.capitalize(MagicColor.toLongString(c)); - // Color should not replace itself. - if (e.getValue().equalsIgnoreCase(colorLowerCase)) { - continue; - } - replaced = getReplacedText(replaced, colorLowerCase, e.getValue().toLowerCase(), isDescriptive); - replaced = getReplacedText(replaced, colorCaptCase, e.getValue(), isDescriptive); - } - } else { - replaced = getReplacedText(replaced, key.toLowerCase(), e.getValue().toLowerCase(), isDescriptive); - replaced = getReplacedText(replaced, key, e.getValue(), isDescriptive); - } + for (final Entry e : changes.colorChanges().entrySet()) { + replaced = getReplacedText(replaced, e.getKey().getName().toLowerCase(), e.getValue().getName().toLowerCase(), isDescriptive); + replaced = getReplacedText(replaced, StringUtils.capitalize(e.getKey().getName()), StringUtils.capitalize(e.getValue().getName()), isDescriptive); } - for (final Entry e : typeMap.entrySet()) { + for (final Entry e : changes.typeChanges().entrySet()) { final String key = e.getKey(); if (isDescriptive) { replaced = getReplacedText(replaced, CardType.getPluralType(key), CardType.getPluralType(e.getValue()), isDescriptive); diff --git a/forge-game/src/main/java/forge/game/ability/effects/TextBoxExchangeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/TextBoxExchangeEffect.java index 9c8a194e193..06ec0d9f324 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TextBoxExchangeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/TextBoxExchangeEffect.java @@ -100,25 +100,25 @@ private static void swapTextBox(final Card to, final TextBoxData from, final lon for (SpellAbility sa : from.spellabilities) { SpellAbility copy = sa.copy(to, false, true); // need to persist any previous word changes - copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes()); + copy.changeTextIntrinsic(copy.getTextChanges()); spellabilities.add(copy); } List triggers = Lists.newArrayList(); for (Trigger tr : from.triggers) { Trigger copy = tr.copy(to, false, true); - copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes()); + copy.changeTextIntrinsic(copy.getTextChanges()); triggers.add(copy); } List reps = Lists.newArrayList(); for (ReplacementEffect re : from.replacements) { ReplacementEffect copy = re.copy(to, false, true); - copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes()); + copy.changeTextIntrinsic(copy.getTextChanges()); reps.add(copy); } List statics = Lists.newArrayList(); for (StaticAbility st : from.statics) { StaticAbility copy = st.copy(to, false, true); - copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes()); + copy.changeTextIntrinsic(copy.getTextChanges()); statics.add(copy); } to.addChangedCardTraitsByText(spellabilities, triggers, reps, statics, ts, 0); 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 2fd2681c5e3..598c0cff005 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -180,8 +180,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr private final Set canBlockAny = Sets.newHashSet(); // changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps - private final CardChangedWords changedTextColors = new CardChangedWords(); - private final CardChangedWords changedTextTypes = new CardChangedWords(); + private final TextChanges textChanges = new TextChanges(); private final Set rememberedObjects = Sets.newLinkedHashSet(); private final List draftActions = Lists.newArrayList(); @@ -410,8 +409,6 @@ public Card(final int id0, final IPaperCard paperCard0, final Game game0, final view = new CardView(id0, tracker0); currentState = new CardState(view.getCurrentState(), this); states.put(CardStateName.Original, currentState); - view.updateChangedColorWords(this); - view.updateChangedTypes(this); view.updateSickness(this); view.updateClassLevel(this); view.updateDraftAction(this); @@ -4788,7 +4785,7 @@ public final SpellAbility getSpellAbilityForStaticAbility(final String str, fina if (!canUseCachedTrait(result, stAb)) { result = AbilityFactory.getAbility(str, this, stAb); // apply text changes from the statics host - result.changeTextIntrinsic(stAb.getChangedTextColors(), stAb.getChangedTextTypes()); + result.changeTextIntrinsic(stAb.getTextChanges()); result.setIntrinsic(false); result.setGrantorStatic(stAb); storedSpellAbility.put(stAb, str, result); @@ -4801,7 +4798,7 @@ public final Trigger getTriggerForStaticAbility(final String str, final StaticAb if (!canUseCachedTrait(result, stAb)) { result = TriggerHandler.parseTrigger(str, this, false, stAb); // apply text changes from the statics host - result.changeTextIntrinsic(stAb.getChangedTextColors(), stAb.getChangedTextTypes()); + result.changeTextIntrinsic(stAb.getTextChanges()); storedTrigger.put(stAb, str, result); } return result; @@ -4841,7 +4838,7 @@ public final ReplacementEffect getReplacementEffectForStaticAbility(final String if (!canUseCachedTrait(result, stAb)) { result = ReplacementHandler.parseReplacement(str, this, false, stAb); // apply text changes from the statics host - result.changeTextIntrinsic(stAb.getChangedTextColors(), stAb.getChangedTextTypes()); + result.changeTextIntrinsic(stAb.getTextChanges()); storedReplacementEffect.put(stAb, str, result); } return result; @@ -4852,7 +4849,7 @@ public final StaticAbility getStaticAbilityForStaticAbility(final String str, fi 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()); + result.changeTextIntrinsic(stAb.getTextChanges()); storedStaticAbility.put(stAb, str, result); } return result; @@ -4928,7 +4925,7 @@ private boolean canUseCachedTrait(CardTraitBase cached, CardTraitBase stAb) { if (cached == null) { return false; } - return cached.getChangedTextColors().equals(stAb.getChangedTextColors()) && cached.getChangedTextTypes().equals(stAb.getChangedTextTypes()); + return cached.getTextChanges().equals(stAb.getTextChanges()); } public final Table getChangedCardTraitsByText() { @@ -4947,8 +4944,7 @@ public final void addChangedCardTraitsByText(Collection spells, )); // setting card traits via text, does overwrite any other word change effects? - this.changedTextColors.addEmpty(timestamp, staticId); - this.changedTextTypes.addEmpty(timestamp, staticId); + textChanges.add(staticId, staticId, new ResetChangedText()); updateChangedText(); } @@ -4977,7 +4973,14 @@ public final boolean removeChangedCardTraits(long timestamp, long staticId) { return changedCardTraits.remove(timestamp, staticId) != null; } public final boolean removeChangedCardTraitsByText(long timestamp, long staticId) { - return changedCardTraitsByText.remove(timestamp, staticId) != null; + boolean changed = false; + if (changedCardTraitsByText.remove(timestamp, staticId) != null) { + changed = true; + } + if (textChanges.remove(timestamp, staticId)) { + changed = true; + } + return changed; } public Iterable getChangedCardTraitsList(CardState state) { @@ -5463,13 +5466,13 @@ public final void addChangedTextColorWord(final String originalWord, final Strin if (MagicColor.fromName(newWord) == 0) { throw new RuntimeException("Not a color: " + newWord); } - changedTextColors.add(timestamp, staticId, StringUtils.capitalize(originalWord), StringUtils.capitalize(newWord)); - + ChangedColorWord word = new ChangedColorWord(originalWord.equals("Any") ? null : MagicColor.Color.fromName(originalWord), MagicColor.Color.fromName(newWord)); + textChanges.add(timestamp, staticId, word); updateChangedText(); } public final void removeChangedTextColorWord(final Long timestamp, final long staticId) { - if (changedTextColors.remove(timestamp, staticId)) { + if (textChanges.remove(timestamp, staticId)) { updateChangedText(); } } @@ -5480,8 +5483,9 @@ public final void removeChangedTextColorWord(final Long timestamp, final long st * @param newWord the new type word. */ public final void addChangedTextTypeWord(final String originalWord, final String newWord, final Long timestamp, final long staticId) { - changedTextTypes.add(timestamp, staticId, originalWord, newWord); - changedCardTypesByText.put(timestamp, staticId, new WordChangedType(originalWord, newWord)); + WordChangedType word = new WordChangedType(originalWord, newWord); + changedCardTypesByText.put(timestamp, staticId, word); + textChanges.add(timestamp, staticId, word); updateChangedText(); } @@ -5489,7 +5493,7 @@ public final void removeChangedTextTypeWord(final Long timestamp, final long sta if (changedCardTypesByText.remove(timestamp, staticId) != null) { updateTypeCache(); } - if (changedTextTypes.remove(timestamp, staticId)) { + if (textChanges.remove(timestamp, staticId)) { updateChangedText(); } } @@ -5524,7 +5528,7 @@ public void updateChangedText() { trait.changeText(); } } else { - final String newtxt = AbilityUtils.applyKeywordTextChangeEffects(oldtxt, this); + final String newtxt = AbilityUtils.applyKeywordTextChangeEffects(oldtxt, this.getTextChanges()); if (!newtxt.equals(oldtxt)) { KeywordInterface newKw = Keyword.getInstance(newtxt); newKw.createTraits(this, true); @@ -5538,8 +5542,7 @@ public void updateChangedText() { text = AbilityUtils.applyDescriptionTextChangeEffects(originalText, this); - getView().updateChangedColorWords(this); - getView().updateChangedTypes(this); + getView().updateTextChanges(this); updateManaCostForView(); updateTypeCache(); @@ -5549,12 +5552,8 @@ public void updateChangedText() { view.updateNonAbilityText(this); } - public final ImmutableMap getChangedTextColorWords() { - return ImmutableMap.copyOf(changedTextColors); - } - - public final ImmutableMap getChangedTextTypeWords() { - return ImmutableMap.copyOf(changedTextTypes); + public ITextChanges getTextChanges() { + return this.textChanges; } /** @@ -5562,8 +5561,7 @@ public final ImmutableMap getChangedTextTypeWords() { * one. The original changes of this Card are removed. */ public final void copyChangedTextFrom(final Card other) { - changedTextColors.copyFrom(other.changedTextColors); - changedTextTypes.copyFrom(other.changedTextTypes); + textChanges.copyFrom(other.textChanges); } public final boolean isPermanent() { diff --git a/forge-game/src/main/java/forge/game/card/CardChangedWords.java b/forge-game/src/main/java/forge/game/card/CardChangedWords.java deleted file mode 100644 index 0f7d700302f..00000000000 --- a/forge-game/src/main/java/forge/game/card/CardChangedWords.java +++ /dev/null @@ -1,105 +0,0 @@ -package forge.game.card; - -import com.google.common.collect.ForwardingMap; -import com.google.common.collect.Maps; -import com.google.common.collect.Table; -import com.google.common.collect.TreeBasedTable; - -import java.util.Map; - -public final class CardChangedWords extends ForwardingMap { - - class WordHolder { - public String oldWord; - public String newWord; - - public boolean clear = false; - - WordHolder() { - this.clear = true; - } - WordHolder(String oldWord, String newWord) { - this.oldWord = oldWord; - this.newWord = newWord; - } - } - - private final Table map = TreeBasedTable.create(); - - private boolean isDirty = false; - private Map resultCache = Maps.newHashMap(); - - public CardChangedWords() { - } - - public Long addEmpty(final long timestamp, final long staticId) { - final Long stamp = timestamp; - map.put(stamp, staticId, new WordHolder()); // Table doesn't allow null value - isDirty = true; - return stamp; - } - - public Long add(final long timestamp, final long staticId, final String originalWord, final String newWord) { - final Long stamp = timestamp; - map.put(stamp, staticId, new WordHolder(originalWord, newWord)); - isDirty = true; - return stamp; - } - - public boolean remove(final Long timestamp, final long staticId) { - isDirty = true; - return map.remove(timestamp, staticId) != null; - } - - @Override - public void clear() { - super.clear(); - map.clear(); - isDirty = true; - } - - void copyFrom(final CardChangedWords other) { - map.clear(); - map.putAll(other.map); - isDirty = true; - } - - /** - * Converts this object to a {@link Map}. - * - * @return a map of strings to strings, where each changed word in this - * object is mapped to its corresponding replacement word. - */ - @Override - protected Map delegate() { - refreshCache(); - return resultCache; - } - - private void refreshCache() { - if (isDirty) { - resultCache.clear(); - for (final WordHolder ccw : this.map.values()) { - // is empty pair is for resetting the data, it is done for Volrath’s Shapeshifter - if (ccw.clear) { - resultCache.clear(); - continue; - } - - // changes because a->b and b->c (resulting in a->c) - final Map toBeChanged = Maps.newHashMap(); - for (final Entry e : resultCache.entrySet()) { - if (e.getValue().equals(ccw.oldWord)) { - toBeChanged.put(e.getKey(), ccw.newWord); - } - } - resultCache.putAll(toBeChanged); - - // the actual change (b->c) - resultCache.put(ccw.oldWord, ccw.newWord); - } - - isDirty = false; - } - } -} 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 94872cb5ab8..3d1a8983f39 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -1059,10 +1059,10 @@ public void updateChangedText() { } } - public void changeTextIntrinsic(Map colorMap, Map typeMap) { + public void changeTextIntrinsic(ITextChanges textChanges) { for (final CardTraitBase ctb : getTraits()) { if (ctb.isIntrinsic()) { - ctb.changeTextIntrinsic(colorMap, typeMap); + ctb.changeTextIntrinsic(textChanges); } } } diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 099079492c1..abbc57e52f3 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -88,8 +88,6 @@ public CardView(final int id0, final Tracker tracker, final String name0) { this(id0, tracker); getCurrentState().setName(name0); set(TrackableProperty.Name, name0); - set(TrackableProperty.ChangedColorWords, new HashMap()); - set(TrackableProperty.ChangedTypes, new HashMap()); set(TrackableProperty.Sickness, true); } public CardView(final int id0, final Tracker tracker, final String name0, final PlayerView ownerAndController, final String imageKey) { @@ -811,17 +809,11 @@ public CardView getPairedWith() { return get(TrackableProperty.PairedWith); } - public Map getChangedColorWords() { - return get(TrackableProperty.ChangedColorWords); + public ITextChanges getTextChanges() { + return get(TrackableProperty.TextChanges); } - void updateChangedColorWords(Card c) { - set(TrackableProperty.ChangedColorWords, c.getChangedTextColorWords()); - } - public Map getChangedTypes() { - return get(TrackableProperty.ChangedTypes); - } - void updateChangedTypes(Card c) { - set(TrackableProperty.ChangedTypes, c.getChangedTextTypeWords()); + void updateTextChanges(Card c) { + set(TrackableProperty.TextChanges, c.getTextChanges().getView()); } void updateNonAbilityText(Card c) { 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 da0698aff56..bf14fb5f4f4 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 @@ -1,14 +1,17 @@ package forge.game.card.token; import com.google.common.base.Joiner; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + import forge.ImageKeys; import forge.StaticData; import forge.card.CardType; import forge.card.ColorSet; import forge.card.GamePieceType; +import forge.card.ITextChanges; import forge.card.MagicColor; +import forge.card.WordChangedType; import forge.card.mana.ManaCost; import forge.game.Game; import forge.game.ability.AbilityUtils; @@ -25,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; @@ -173,53 +177,32 @@ public Card makeOneToken(final Player controller, int id) { static protected void protoTypeApplyTextChange(final Card result, final SpellAbility sa) { // update Token with CardTextChanges - Map colorMap = sa.getChangedTextColors(); - Map typeMap = sa.getChangedTextTypes(); + ITextChanges changes = sa.getTextChanges(); + Map colorMap = changes.colorChanges(); + Map typeMap = changes.typeChanges(); if (!colorMap.isEmpty()) { if (!result.isColorless()) { // change Token Colors - byte color = result.getColor().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; - } - } + Set colorResult = Sets.newHashSet(); + for (MagicColor.Color c : result.getColor()) { + colorResult.add(colorMap.getOrDefault(c, c)); } - result.setColor(ColorSet.fromMask(color)); + result.setColor(ColorSet.fromEnums(colorResult)); } } if (!typeMap.isEmpty()) { CardType type = new CardType(result.getType()); final boolean nameGenerated = result.getName().endsWith(" Token"); - boolean typeChanged = false; - if (!Iterables.isEmpty(type.getSubtypes())) { + if (!type.getSubtypes().isEmpty()) { for (final Map.Entry e : typeMap.entrySet()) { - if (type.hasSubtype(e.getKey())) { - type.remove(e.getKey()); - type.add(e.getValue()); - typeChanged = true; - } + WordChangedType word = new WordChangedType(e.getKey(), e.getValue()); + word.applyChanges(type); } } - if (typeChanged) { + if (result.getType().equals(type)) { result.setType(type); // update generated Name @@ -238,37 +221,7 @@ static protected void protoTypeApplyTextChange(final Card result, final SpellAbi if (!CardUtil.isKeywordModifiable(o)) { continue; } - String r = 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); - } - } + String r = AbilityUtils.applyKeywordTextChangeEffects(o, changes); if (!r.equals(o)) { toRemove.add(k); toAdd.add(r); @@ -279,7 +232,7 @@ static protected void protoTypeApplyTextChange(final Card result, final SpellAbi } result.addIntrinsicKeywords(toAdd); - result.getCurrentState().changeTextIntrinsic(colorMap, typeMap); + result.getCurrentState().changeTextIntrinsic(changes); } static public Card getProtoType(final String script, final SpellAbility sa, final Player owner) { 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 198589f9080..a57650d2cce 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -33,6 +33,7 @@ import forge.GameCommand; import forge.card.CardStateName; import forge.card.ColorSet; +import forge.card.ITextChanges; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.game.CardTraitBase; @@ -2311,27 +2312,24 @@ public void changeText() { } } - /* (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); + public void changeTextIntrinsic(ITextChanges textChanges) { + super.changeTextIntrinsic(textChanges); 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); + subAbility.changeTextIntrinsic(textChanges); } } for (SpellAbility sa : additionalAbilities.values()) { - sa.changeTextIntrinsic(colorMap, typeMap); + sa.changeTextIntrinsic(textChanges); } for (List list : additionalAbilityLists.values()) { for (AbilitySub sa : list) { - sa.changeTextIntrinsic(colorMap, typeMap); + sa.changeTextIntrinsic(textChanges); } } } diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index fa6630d0d04..c22c8e2e7ea 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -136,10 +136,9 @@ public enum TrackableProperty { Loyalty(TrackableTypes.StringType), Defense(TrackableTypes.StringType), AttractionLights(TrackableTypes.IntegerSetType), - ChangedColorWords(TrackableTypes.StringMapType), HasChangedColors(TrackableTypes.BooleanType), HasPrintedPT(TrackableTypes.BooleanType), - ChangedTypes(TrackableTypes.StringMapType), + TextChanges(TrackableTypes.TextChangesViewType), //check produce mana for BG OrigProduceMana(TrackableTypes.ColorSetType), diff --git a/forge-game/src/main/java/forge/trackable/TrackableTypes.java b/forge-game/src/main/java/forge/trackable/TrackableTypes.java index c4b9813d488..2fa460e82aa 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableTypes.java +++ b/forge-game/src/main/java/forge/trackable/TrackableTypes.java @@ -9,6 +9,7 @@ import forge.card.CardType; import forge.card.CardTypeView; import forge.card.ColorSet; +import forge.card.TextChangesView; import forge.card.mana.ManaCost; import forge.game.GameEntityView; import forge.game.card.CardView; @@ -314,4 +315,11 @@ public KeywordCollectionView getDefaultValue() { return KeywordCollectionView.EMPTY; } }; + + public static final TrackableType TextChangesViewType = new TrackableType() { + @Override + public TextChangesView getDefaultValue() { + return TextChangesView.EMPTY; + } + }; } diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index c7542b58395..11afc5420be 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -1,9 +1,9 @@ package forge.gui.card; -import com.google.common.collect.Sets; import forge.card.CardRarity; import forge.card.CardStateName; import forge.card.ColorSet; +import forge.card.ITextChanges; import forge.card.MagicColor; import forge.game.GameView; import forge.game.card.Card; @@ -330,26 +330,27 @@ public static String composeCardText(final CardStateView state, final GameView g } // text changes - final Map changedColorWords = card.getChangedColorWords(); - final Map changedTypes = card.getChangedTypes(); - if (changedColorWords != null && changedTypes != null) { - if (!(changedColorWords.isEmpty() && changedTypes.isEmpty())) { - area.append("\n"); - } - - for (final Entry e : Sets.union(changedColorWords.entrySet(), changedTypes.entrySet())) { + final ITextChanges textChanges = card.getTextChanges(); + if (!textChanges.isEmpty()) { + area.append("\n"); + if (textChanges.colorChanges().size() == 5) { area.append("Text changed: all instances of "); - if (e.getKey().equals("Any")) { - if (changedColorWords.containsKey(e.getValue())) { - area.append("color words"); - } else if (forge.card.CardType.getBasicTypes().contains(e.getValue())) { - area.append("basic land types"); - } else { - area.append("creature types"); - } - } else { - area.append(e.getKey()); + area.append("color words"); + area.append(" are replaced by "); + area.append(textChanges.colorChanges().get(MagicColor.Color.WHITE).getTranslatedName()); + area.append(".\n"); + } else { + for (Map.Entry e : textChanges.colorChanges().entrySet()) { + area.append("Text changed: all instances of "); + area.append(e.getKey().getTranslatedName()); + area.append(" are replaced by "); + area.append(e.getValue().getTranslatedName()); + area.append(".\n"); } + } + for (Map.Entry e : textChanges.typeChanges().entrySet()) { + area.append("Text changed: all instances of "); + area.append(e.getKey()); area.append(" are replaced by "); area.append(e.getValue()); area.append(".\n");