Add code allowing text-changing effects (+some cleanup).

This commit is contained in:
elcnesh
2014-08-11 09:22:18 +00:00
parent 2168779735
commit 0d534be21e
24 changed files with 564 additions and 65 deletions

3
.gitattributes vendored
View File

@@ -307,6 +307,7 @@ forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java -t
forge-game/src/main/java/forge/game/ability/effects/BidLifeEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/BondEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java -text
@@ -422,6 +423,8 @@ forge-game/src/main/java/forge/game/ability/effects/VoteEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java -text
forge-game/src/main/java/forge/game/ability/package-info.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/Card.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardChangedWord.java -text
forge-game/src/main/java/forge/game/card/CardChangedWords.java -text
forge-game/src/main/java/forge/game/card/CardCharacteristics.java -text
forge-game/src/main/java/forge/game/card/CardColor.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardDamageHistory.java -text

View File

@@ -481,7 +481,7 @@ public class AiController {
// don't play the land if it has cycling and enough lands are
// available
final ArrayList<SpellAbility> spellAbilities = c.getSpellAbilities();
final List<SpellAbility> spellAbilities = c.getSpellAbilities();
final List<Card> hand = player.getCardsIn(ZoneType.Hand);
List<Card> lands = player.getCardsIn(ZoneType.Battlefield);

View File

@@ -178,4 +178,9 @@ public class TextUtil {
}
return builder.toString();
}
public static String capitalize(final String s) {
return s.substring(0, 1).toUpperCase()
+ s.substring(1);
}
}

View File

@@ -12,6 +12,8 @@ import forge.util.Expressions;
import java.util.*;
import com.google.common.collect.Maps;
/**
* Base class for Triggers,ReplacementEffects and StaticAbilities.
*
@@ -22,7 +24,8 @@ public abstract class CardTraitBase extends GameObject {
protected Card hostCard;
/** The map params. */
protected final Map<String, String> mapParams = new HashMap<String, String>();
protected final Map<String, String> originalMapParams = Maps.newHashMap(),
mapParams = Maps.newHashMap();
/** The is intrinsic. */
protected boolean intrinsic;
@@ -66,11 +69,6 @@ public abstract class CardTraitBase extends GameObject {
return this.mapParams;
}
public final void setMapParams(Map<String,String> params) {
this.mapParams.clear();
this.mapParams.putAll(params);
}
/**
* Checks if is intrinsic.
*
@@ -350,4 +348,15 @@ public abstract class CardTraitBase extends GameObject {
}
return true;
}
public void changeText() {
for (final String key : this.mapParams.keySet()) {
// don't change literal SVar names!
if (!this.getHostCard().hasSVar(key)) {
final String value = this.originalMapParams.get(key),
newValue = AbilityUtils.applyTextChangeEffects(value, this.getHostCard());
this.mapParams.put(key, newValue);
}
}
}
}

View File

@@ -20,6 +20,7 @@ public class AbilityApiBased extends AbilityActivated {
public AbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
super(sourceCard, abCost, tgt);
originalMapParams.putAll(params0);
mapParams.putAll(params0);
api = api0;
effect = api.getSpellEffect();

View File

@@ -25,6 +25,7 @@ import org.apache.commons.lang3.text.WordUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -79,7 +80,7 @@ public class AbilityUtils {
@SuppressWarnings("unchecked")
public static List<Card> getDefinedCards(final Card hostCard, final String def, final SpellAbility sa) {
final List<Card> cards = new ArrayList<Card>();
final String defined = (def == null) ? "Self" : def; // default to Self
final String defined = (def == null) ? "Self" : applyTextChangeEffects(def, hostCard); // default to Self
final Game game = hostCard.getGame();
Card c = null;
@@ -351,34 +352,37 @@ public class AbilityUtils {
// return result soon for plain numbers
if (StringUtils.isNumeric(amount)) { return Integer.parseInt(amount) * multiplier; }
// modify amount string for text changes
final String amount2 = AbilityUtils.applyTextChangeEffects(amount, card);
// Try to fetch variable, try ability first, then card.
String svarval = null;
if (amount.indexOf('$') > 0) { // when there is a dollar sign, it's not a reference, it's a raw value!
svarval = amount;
if (amount2.indexOf('$') > 0) { // when there is a dollar sign, it's not a reference, it's a raw value!
svarval = amount2;
}
else if (ability != null) {
svarval = ability.getSVar(amount);
svarval = ability.getSVar(amount2);
}
if (StringUtils.isBlank(svarval)) {
if (ability != null) {
System.err.printf("SVar '%s' not found in ability, fallback to Card (%s). Ability is (%s)%n", amount, card.getName(), ability);
System.err.printf("SVar '%s' not found in ability, fallback to Card (%s). Ability is (%s)%n", amount2, card.getName(), ability);
}
svarval = card.getSVar(amount);
svarval = card.getSVar(amount2);
}
if (StringUtils.isBlank(svarval)) {
// Some variables may be not chosen yet at this moment
// So return 0 and don't issue an error.
if (amount.equals("ChosenX")) {
if (amount2.equals("ChosenX")) {
// isn't made yet
return 0;
}
// cost hasn't been paid yet
if (amount.startsWith("Cost")) {
if (amount2.startsWith("Cost")) {
return 0;
}
// Nothing to do here if value is missing or blank
System.err.printf("SVar '%s' not defined in Card (%s)%n", amount, card.getName());
System.err.printf("SVar '%s' not defined in Card (%s)%n", amount2, card.getName());
return 0;
}
@@ -395,6 +399,9 @@ public class AbilityUtils {
return 0;
}
// modify amount string for text changes
calcX[1] = AbilityUtils.applyTextChangeEffects(calcX[1], card);
if (calcX[0].startsWith("Count")) {
return AbilityUtils.xCount(card, calcX[1], ability) * multiplier;
}
@@ -806,7 +813,7 @@ public class AbilityUtils {
*/
public static List<Player> getDefinedPlayers(final Card card, final String def, final SpellAbility sa) {
final List<Player> players = new ArrayList<Player>();
final String defined = (def == null) ? "You" : def;
final String defined = (def == null) ? "You" : applyTextChangeEffects(def, card);
final Game game = card == null ? null : card.getGame();
if (defined.equals("Targeted") || defined.equals("TargetedPlayer")) {
@@ -1125,7 +1132,7 @@ public class AbilityUtils {
public static ArrayList<SpellAbility> getDefinedSpellAbilities(final Card card, final String def,
final SpellAbility sa) {
final ArrayList<SpellAbility> sas = new ArrayList<SpellAbility>();
final String defined = (def == null) ? "Self" : def; // default to Self
final String defined = (def == null) ? "Self" : applyTextChangeEffects(def, card); // default to Self
final Game game = sa.getActivatingPlayer().getGame();
SpellAbility s = null;
@@ -1398,9 +1405,9 @@ public class AbilityUtils {
* @return a int.
*/
public static int xCount(final Card c, final String s, final SpellAbility sa) {
final String[] l = s.split("/");
final String expr = CardFactoryUtil.extractOperators(s);
final String s2 = AbilityUtils.applyTextChangeEffects(s, c);
final String[] l = s2.split("/");
final String expr = CardFactoryUtil.extractOperators(s2);
final String[] sq;
sq = l[0].split("\\.");
@@ -1459,7 +1466,7 @@ public class AbilityUtils {
}
}
}
return CardFactoryUtil.xCount(c, s);
return CardFactoryUtil.xCount(c, s2);
}
public static final void applyManaColorConversion(final Player p, final Map<String, String> params) {
@@ -1506,4 +1513,15 @@ public class AbilityUtils {
}
return sas;
}
public static final String applyTextChangeEffects(final String def, final Card hostCard) {
String replaced = def;
for (final Entry<String, String> e : hostCard.getChangedTextColorWords().entrySet()) {
replaced = replaced.replace(e.getKey(), e.getValue());
}
for (final Entry<String, String> e : hostCard.getChangedTextTypeWords().entrySet()) {
replaced = replaced.replace(e.getKey(), e.getValue());
}
return replaced;
}
}

View File

@@ -25,9 +25,9 @@ public enum ApiType {
BidLife (BidLifeEffect.class),
Bond (BondEffect.class),
ChangeTargets (ChangeTargetsEffect.class),
ChangeText (ChangeTextEffect.class),
ChangeZone (ChangeZoneEffect.class),
ChangeZoneAll (ChangeZoneAllEffect.class),
Charm (CharmEffect.class),
ChooseCard (ChooseCardEffect.class),
ChooseColor (ChooseColorEffect.class),

View File

@@ -20,6 +20,7 @@ public class SpellApiBased extends Spell {
super(sourceCard, abCost);
this.setTargetRestrictions(tgt);
originalMapParams.putAll(params0);
mapParams.putAll(params0);
api = api0;
effect = api.getSpellEffect();

View File

@@ -15,6 +15,7 @@ public class StaticAbilityApiBased extends AbilityStatic {
public StaticAbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
super(sourceCard, abCost, tgt);
originalMapParams.putAll(params0);
mapParams.putAll(params0);
api = api0;
effect = api.getSpellEffect();

View File

@@ -0,0 +1,235 @@
package forge.game.ability.effects;
import java.util.List;
import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.spellability.SpellAbility;
import forge.util.TextUtil;
public class ChangeTextEffect extends SpellAbilityEffect {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#resolve(forge.card.spellability.SpellAbility)
*/
@Override
public void resolve(final SpellAbility sa) {
final Card source = sa.getHostCard();
final Game game = source.getGame();
final boolean permanent = sa.hasParam("Permanent");
final String changedColorWordOriginal, changedColorWordNew;
if (sa.hasParam("ChangeColorWord")) {
byte originalColor = 0;
final String[] changedColorWordsArray = sa.getParam("ChangeColorWord").split(" ");
if (changedColorWordsArray[0].equals("Choose")) {
originalColor = sa.getActivatingPlayer().getController().chooseColor(
"Choose a color word to replace", sa, ColorSet.fromMask(MagicColor.ALL_COLORS));
changedColorWordOriginal = TextUtil.capitalize(MagicColor.toLongString(originalColor));
} else {
changedColorWordOriginal = changedColorWordsArray[0];
originalColor = MagicColor.fromName(changedColorWordOriginal);
}
if (changedColorWordsArray[1].equals("Choose")) {
final ColorSet possibleNewColors;
if (originalColor == 0) { // no original color (ie. any or absent)
possibleNewColors = ColorSet.fromMask(MagicColor.ALL_COLORS);
} else { // may choose any except original color
possibleNewColors = ColorSet.fromMask(originalColor).inverse();
}
final byte newColor = sa.getActivatingPlayer().getController().chooseColor(
"Choose a new color word", sa, possibleNewColors);
changedColorWordNew = TextUtil.capitalize(MagicColor.toLongString(newColor));
} else {
changedColorWordNew = changedColorWordsArray[1];
}
} else {
changedColorWordOriginal = null;
changedColorWordNew = null;
}
final String changedTypeWordOriginal, changedTypeWordNew;
if (sa.hasParam("ChangeTypeWord")) {
String kindOfType = "";
final List<String> validTypes = Lists.newArrayList();
final String[] changedTypeWordsArray = sa.getParam("ChangeTypeWord").split(" ");
if (changedTypeWordsArray[0].equals("ChooseBasicLandType") || changedTypeWordsArray[0].equals("ChooseCreatureType")) {
if (changedTypeWordsArray[0].equals("ChooseBasicLandType")) {
validTypes.addAll(CardType.getBasicTypes());
kindOfType = "basic land";
} else if (changedTypeWordsArray[0].equals("ChooseCreatureType")) {
validTypes.addAll(CardType.getCreatureTypes());
kindOfType = "creature";
}
changedTypeWordOriginal = sa.getActivatingPlayer().getController().chooseSomeType(kindOfType, sa, validTypes, Lists.<String>newArrayList());
} else {
changedTypeWordOriginal = changedTypeWordsArray[0];
}
validTypes.clear();
final List<String> forbiddenTypes = sa.hasParam("ForbiddenNewTypes") ? Lists.newArrayList(sa.getParam("ForbiddenNewTypes").split(",")) : Lists.<String>newArrayList();
forbiddenTypes.add(changedTypeWordOriginal);
if (changedTypeWordsArray[0].startsWith("Choose")) {
if (changedTypeWordsArray[0].equals("ChooseBasicLandType")) {
validTypes.addAll(CardType.getBasicTypes());
kindOfType = "basic land";
} else if (changedTypeWordsArray[0].equals("ChooseCreatureType")) {
validTypes.addAll(CardType.getCreatureTypes());
kindOfType = "creature";
}
changedTypeWordNew = sa.getActivatingPlayer().getController().chooseSomeType(kindOfType, sa, validTypes, forbiddenTypes);
} else {
changedTypeWordNew = changedTypeWordsArray[1];
}
} else {
changedTypeWordOriginal = null;
changedTypeWordNew = null;
}
final List<Card> tgts = getTargetCards(sa);
for (final Card c : tgts) {
final Long colorTimestamp;
if (changedColorWordNew != null) {
colorTimestamp = c.addChangedTextColorWord(changedColorWordOriginal, changedColorWordNew);
} else {
colorTimestamp = null;
}
final Long typeTimestamp;
if (changedTypeWordNew != null) {
typeTimestamp = c.addChangedTextTypeWord(changedTypeWordOriginal, changedTypeWordNew);
} else {
typeTimestamp = null;
}
if (!permanent) {
final GameCommand revert = new GameCommand() {
private static final long serialVersionUID = -7802388880114360593L;
@Override
public void run() {
if (changedColorWordNew != null) {
c.removeChangedTextColorWord(colorTimestamp);
}
if (changedTypeWordNew != null) {
c.removeChangedTextTypeWord(typeTimestamp);
}
}
};
game.getEndOfTurn().addUntil(revert);
}
game.fireEvent(new GameEventCardStatsChanged(c));
}
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected String getStackDescription(SpellAbility sa) {
final String changedColorWordOriginal, changedColorWordNew;
if (sa.hasParam("ChangeColorWord")) {
final String[] changedColorWordsArray = sa.getParam("ChangeColorWord").split(" ");
changedColorWordOriginal = changedColorWordsArray[0];
changedColorWordNew = changedColorWordsArray[1];
} else {
changedColorWordOriginal = null;
changedColorWordNew = null;
}
final String changedTypeWordOriginal, changedTypeWordNew;
if (sa.hasParam("ChangeTypeWord")) {
final String[] changedTypeWordsArray = sa.getParam("ChangeTypeWord").split(" ");
changedTypeWordOriginal = changedTypeWordsArray[0];
changedTypeWordNew = changedTypeWordsArray[1];
} else {
changedTypeWordOriginal = null;
changedTypeWordNew = null;
}
final boolean permanent = sa.hasParam("Permanent");
final StringBuilder sb = new StringBuilder();
sb.append("Change the text of ");
final List<Card> tgts = getTargetCards(sa);
for (final Card c : tgts) {
sb.append(c).append(" ");
}
if (changedColorWordOriginal != null) {
sb.append(" by replacing all instances of ");
if (changedColorWordOriginal.equals("Choose")) {
sb.append("one color word");
} else if (changedColorWordOriginal.equals("Any")) {
sb.append("each color word");
} else {
sb.append(changedColorWordOriginal);
}
sb.append(" with ");
if (changedColorWordNew.equals("Choose")) {
if (changedColorWordOriginal.equals("Choose")) {
sb.append("another");
} else {
sb.append("a color word of your choice");
}
} else {
sb.append(changedColorWordNew);
}
}
if (changedTypeWordOriginal != null) {
sb.append(" by replacing all instances of ");
if (changedTypeWordOriginal.equals("ChooseBasicLandType")) {
sb.append("one basic land type");
} else if (changedTypeWordOriginal.equals("ChooseCreatureType")) {
sb.append("one creature type");
} else {
sb.append(changedTypeWordOriginal);
}
sb.append(" with ");
if (changedTypeWordNew.equals("ChooseBasicLandType")) {
if (changedTypeWordOriginal.equals("ChooseBasicLandType")) {
sb.append("another");
} else {
sb.append("a basic land type of your choice");
}
} else if (changedTypeWordNew.equals("ChooseCreatureType")) {
if (changedTypeWordOriginal.equals("ChooseCreatureType")) {
sb.append("another");
} else {
sb.append("a creature type of your choice");
}
} else {
sb.append(changedTypeWordNew);
}
}
if (!permanent) {
sb.append(" until end of turn");
}
sb.append('.');
if (sa.hasParam("ForbiddenNewTypes")) {
sb.append(" The new creature type can't be ");
sb.append(sa.getParam("ForbiddenNewTypes"));
sb.append('.');
}
if (permanent) {
sb.append(" (This effect lasts indefinitely.)");
}
return sb.toString();
}
}

View File

@@ -518,7 +518,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
movedCard = game.getAction().moveTo(destination, tgtC);
// If a card is Exiled from the stack, remove its spells from the stack
if (sa.hasParam("Fizzle")) {
ArrayList<SpellAbility> spells = tgtC.getSpellAbilities();
final List<SpellAbility> spells = tgtC.getSpellAbilities();
for (SpellAbility spell : spells) {
if (tgtC.isInZone(ZoneType.Exile) || tgtC.isInZone(ZoneType.Hand)) {
final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(spell);

View File

@@ -20,7 +20,9 @@ package forge.game.card;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.GameCommand;
import forge.StaticData;
@@ -118,6 +120,12 @@ public class Card extends GameEntity implements Comparable<Card> {
private Map<Long, CardType> changedCardTypes = new ConcurrentSkipListMap<Long, CardType>();
private Map<Long, CardKeywords> changedCardKeywords = new ConcurrentSkipListMap<Long, CardKeywords>();
// 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();
/** List of the keywords that have been added by text changes. */
private final List<String> keywordsGrantedByTextChanges = Lists.newArrayList();
private final ArrayList<Object> rememberedObjects = new ArrayList<Object>();
private final MapOfLists<GameEntity, Object> rememberMap = new HashMapOfLists<GameEntity, Object>(CollectionSuppliers.<Object>arrayLists());
private final ArrayList<Card> imprintedCards = new ArrayList<Card>();
@@ -2389,6 +2397,18 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
for (final Entry<String, String> e : Sets.union(this.changedTextColors.toMap().entrySet(),
this.changedTextTypes.toMap().entrySet())) {
// only the upper case ones, to avoid duplicity
if (Character.isUpperCase(e.getKey().charAt(0))) {
sb.append("Text changed: all instances of ");
sb.append(e.getKey());
sb.append(" are replaced by ");
sb.append(e.getValue());
sb.append(".\r\n");
}
}
// NOTE:
if (sb.toString().contains(" (NOTE: ")) {
sb.insert(sb.indexOf("(NOTE: "), "\r\n");
@@ -2594,7 +2614,6 @@ public class Card extends GameEntity implements Comparable<Card> {
return Collections.unmodifiableList(this.getCharacteristics().getManaAbility());
}
public final boolean canProduceSameManaTypeWith(final Card c) {
final List<SpellAbility> manaAb = this.getManaAbility();
if (manaAb.isEmpty()) {
@@ -2691,7 +2710,6 @@ public class Card extends GameEntity implements Comparable<Card> {
* a {@link forge.game.spellability.SpellAbility} object.
*/
public final void addSpellAbility(final SpellAbility a) {
a.setHostCard(this);
if (a.isManaAbility()) {
this.getCharacteristics().getManaAbility().add(a);
@@ -2700,7 +2718,6 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
/**
* <p>
* removeSpellAbility.
@@ -2726,7 +2743,7 @@ public class Card extends GameEntity implements Comparable<Card> {
*
* @return a {@link java.util.ArrayList} object.
*/
public final ArrayList<SpellAbility> getSpellAbilities() {
public final List<SpellAbility> getSpellAbilities() {
final ArrayList<SpellAbility> res = new ArrayList<SpellAbility>(this.getManaAbility());
res.addAll(this.getCharacteristics().getSpellAbility());
return res;
@@ -4483,9 +4500,10 @@ public class Card extends GameEntity implements Comparable<Card> {
*
* @param timestamp
* the timestamp
* @return the removed {@link CardKeywords}.
*/
public final void removeChangedCardKeywords(final long timestamp) {
changedCardKeywords.remove(Long.valueOf(timestamp));
public final CardKeywords removeChangedCardKeywords(final long timestamp) {
return changedCardKeywords.remove(Long.valueOf(timestamp));
}
// Hidden keywords will be left out
@@ -4518,6 +4536,97 @@ public class Card extends GameEntity implements Comparable<Card> {
return keywords;
}
/**
* Replace all instances of one color word in this card's text by another.
* @param originalWord the original color word.
* @param newWord the new color word.
* @return the timestamp.
* @throws RuntimeException if either of the strings is not a valid Magic
* color.
*/
public final Long addChangedTextColorWord(final String originalWord, final String newWord) {
if (MagicColor.fromName(newWord) == 0) {
throw new RuntimeException("Not a color: " + newWord);
}
final Long timestamp = this.changedTextColors.add(this.getGame().getNextTimestamp(), originalWord, newWord);
this.updateKeywordsChangedText(originalWord, newWord, timestamp);
this.updateChangedText();
return timestamp;
}
public final void removeChangedTextColorWord(final Long timestamp) {
this.changedTextColors.remove(timestamp);
this.updateKeywordsOnRemoveChangedText(
this.removeChangedCardKeywords(timestamp.longValue()));
this.updateChangedText();
}
/**
* Replace all instances of one type in this card's text by another.
* @param originalWord the original type word.
* @param newWord the new type word.
*/
public final Long addChangedTextTypeWord(final String originalWord, final String newWord) {
final Long timestamp = this.changedTextTypes.add(this.getGame().getNextTimestamp(), originalWord, newWord);
if (this.getType().contains(originalWord)) {
this.addChangedCardTypes(Lists.newArrayList(newWord), Lists.newArrayList(originalWord), false, false, false, false, timestamp);
}
this.updateKeywordsChangedText(originalWord, newWord, timestamp);
this.updateChangedText();
return timestamp;
}
public final void removeChangedTextTypeWord(final Long timestamp) {
this.changedTextTypes.remove(timestamp);
this.removeChangedCardTypes(timestamp);
this.updateKeywordsOnRemoveChangedText(
this.removeChangedCardKeywords(timestamp.longValue()));
this.updateChangedText();
}
private final void updateKeywordsChangedText(final String originalWord, final String newWord, final Long timestamp) {
final List<String> addKeywords = Lists.newArrayList(),
removeKeywords = Lists.newArrayList(this.keywordsGrantedByTextChanges);
for (final String kw : this.getIntrinsicKeyword()) {
final String newKw = AbilityUtils.applyTextChangeEffects(kw, this);
if (!newKw.equals(kw)) {
addKeywords.add(newKw);
removeKeywords.add(kw);
this.keywordsGrantedByTextChanges.add(newKw);
}
}
this.addChangedCardKeywords(addKeywords, removeKeywords, false, timestamp.longValue());
}
private final void updateKeywordsOnRemoveChangedText(final CardKeywords k) {
this.keywordsGrantedByTextChanges.removeAll(k.getKeywords());
}
/**
* Update the changed text of the intrinsic spell abilities and keywords.
*/
private final void updateChangedText() {
final List<CardTraitBase> allAbs = Lists.newLinkedList();
allAbs.addAll(this.getSpellAbilities());
allAbs.addAll(this.getStaticAbilities());
allAbs.addAll(this.getReplacementEffects());
allAbs.addAll(this.getTriggers());
for (final CardTraitBase ctb : allAbs) {
if (ctb.isIntrinsic()) {
ctb.changeText();
}
}
}
public final ImmutableMap<String, String> getChangedTextColorWords() {
return ImmutableMap.copyOf(changedTextColors.toMap());
}
public final ImmutableMap<String, String> getChangedTextTypeWords() {
return ImmutableMap.copyOf(changedTextTypes.toMap());
}
/**
* <p>
* getIntrinsicAbilities.
@@ -4542,19 +4651,6 @@ public class Card extends GameEntity implements Comparable<Card> {
return this.getCharacteristics().getIntrinsicKeyword();
}
/**
* <p>
* Setter for the field <code>intrinsicKeyword</code>.
* </p>
*
* @param a
* a {@link java.util.ArrayList} object.
*/
public final void setIntrinsicKeyword(final List<String> a) {
this.getCharacteristics().setIntrinsicKeyword(new ArrayList<String>(a));
}
/**
* <p>
* setIntrinsicAbilities.

View File

@@ -0,0 +1,21 @@
package forge.game.card;
public class CardChangedWord {
private final String originalWord,
newWord;
public CardChangedWord(final String originalWord, final String newWord) {
this.originalWord = originalWord;
this.newWord = newWord;
}
public String getOriginalWord() {
return originalWord;
}
public String getNewWord() {
return newWord;
}
}

View File

@@ -0,0 +1,61 @@
package forge.game.card;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
public final class CardChangedWords {
private final SortedMap<Long, CardChangedWord> map = Maps.newTreeMap();
private boolean isDirty = false;
private Map<String, String> resultCache = Maps.newHashMap();
public CardChangedWords() {
}
public Long add(final long timestamp, final String originalWord, final String newWord) {
final Long stamp = Long.valueOf(timestamp);
map.put(stamp, new CardChangedWord(originalWord, newWord));
isDirty = true;
return stamp;
}
public void remove(final Long timestamp) {
map.remove(timestamp);
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.
*/
public Map<String, String> toMap() {
refreshCache();
return resultCache;
}
private void refreshCache() {
if (isDirty) {
resultCache = Maps.newHashMap();
for (final CardChangedWord ccw : this.map.values()) {
for (final Entry<String, String> e : resultCache.entrySet()) {
if (e.getValue().equals(ccw.getOriginalWord())) {
e.setValue(ccw.getNewWord());
}
}
resultCache.put(ccw.getOriginalWord(), ccw.getNewWord());
}
for (final String key : ImmutableList.copyOf(resultCache.keySet())) {
resultCache.put(key.toLowerCase(), resultCache.get(key).toLowerCase());
}
isDirty = false;
}
}
}

View File

@@ -31,6 +31,7 @@ import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.replacement.ReplacementHandler;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.WrappedAbility;
@@ -288,11 +289,11 @@ public class CardFactory {
card.setState(state);
CardFactoryUtil.addAbilityFactoryAbilities(card);
for (String stAb : card.getStaticAbilityStrings()) {
card.addStaticAbility(stAb);
final StaticAbility s = card.addStaticAbility(stAb);
s.setIntrinsic(true);
}
if ( state == CardCharacteristicName.LeftSplit || state == CardCharacteristicName.RightSplit )
if (state == CardCharacteristicName.LeftSplit || state == CardCharacteristicName.RightSplit)
{
CardCharacteristics original = card.getState(CardCharacteristicName.Original);
original.getSpellAbility().addAll(card.getCharacteristics().getSpellAbility());

View File

@@ -682,7 +682,7 @@ public class CardFactoryUtil {
return doXMath(n, m, source);
}
public static int playerXProperty(Player player, String s, Card source) {
public static int playerXProperty(final Player player, final String s, final Card source) {
final String[] l = s.split("/");
final String m = extractOperators(s);
@@ -1975,7 +1975,9 @@ public class CardFactoryUtil {
// **************************************************
// AbilityFactory cards
for (String rawAbility : card.getUnparsedAbilities()) {
card.addSpellAbility(AbilityFactory.getAbility(rawAbility, card));
final SpellAbility intrinsicAbility = AbilityFactory.getAbility(rawAbility, card);
card.addSpellAbility(intrinsicAbility);
intrinsicAbility.setIntrinsic(true);
}
}
/*

View File

@@ -58,6 +58,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
*/
public ReplacementEffect(final Map<String, String> map, final Card host, final boolean intrinsic) {
this.intrinsic = intrinsic;
originalMapParams.putAll(map);
mapParams.putAll(map);
this.setHostCard(host);
}

View File

@@ -192,6 +192,10 @@ public class ReplacementHandler implements IGameStateObject {
} while(tailend != null);
}
if (effectSA.isIntrinsic()) {
effectSA.changeText();
}
// Decider gets to choose whether or not to apply the replacement.
if (replacementEffect.getMapParams().containsKey("Optional")) {
Player optDecider = decider;

View File

@@ -86,8 +86,11 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
this.setTargetRestrictions(tgt);
api = api0;
if (params0 != null)
if (params0 != null) {
originalMapParams.putAll(params0);
mapParams.putAll(params0);
}
effect = api.getSpellEffect();
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {

View File

@@ -1773,4 +1773,17 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setChosenList(List<AbilitySub> choices) {
this.chosenList = choices;
}
@Override
public void changeText() {
super.changeText();
if (this.targetRestricions != null) {
this.targetRestricions.applyTargetTextChanges(this);
}
if (this.subAbility != null) {
this.subAbility.changeText();
}
}
}

View File

@@ -24,6 +24,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
@@ -47,7 +48,8 @@ public class TargetRestrictions {
// What this Object is restricted to targeting
private boolean tgtValid = false;
private String[] validTgts;
private String[] originalValidTgts,
validTgts;
private String uiPrompt = "";
private List<ZoneType> tgtZone = Arrays.asList(ZoneType.Battlefield);
@@ -86,7 +88,8 @@ public class TargetRestrictions {
public TargetRestrictions(final TargetRestrictions target) {
this.tgtValid = true;
this.uiPrompt = target.getVTSelection();
this.validTgts = target.getValidTgts();
this.originalValidTgts = target.getValidTgts();
this.validTgts = this.originalValidTgts.clone();
this.minTargets = target.getMinTargets();
this.maxTargets = target.getMaxTargets();
this.tgtZone = target.getZone();
@@ -120,7 +123,8 @@ public class TargetRestrictions {
public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) {
this.tgtValid = true;
this.uiPrompt = prompt;
this.validTgts = valid;
this.originalValidTgts = valid;
this.validTgts = this.originalValidTgts.clone();
this.minTargets = min;
this.maxTargets = max;
}
@@ -419,6 +423,8 @@ public class TargetRestrictions {
}
}
this.applyTargetTextChanges(sa);
final Card srcCard = sa.getHostCard(); // should there be OrginalHost at any moment?
if (this.tgtZone.contains(ZoneType.Stack)) {
// Stack Zone targets are considered later
@@ -476,6 +482,8 @@ public class TargetRestrictions {
}
}
this.applyTargetTextChanges(sa);
final Card srcCard = sa.getHostCard(); // should there be OrginalHost at any moment?
if (this.tgtZone.contains(ZoneType.Stack)) {
for (final Card c : game.getStackZone().getCards()) {
@@ -698,4 +706,10 @@ public class TargetRestrictions {
return this.dividedMap;
}
public final void applyTargetTextChanges(final SpellAbility sa) {
for (int i = 0; i < validTgts.length; i++) {
validTgts[i] = AbilityUtils.applyTextChangeEffects(originalValidTgts[i], sa.getHostCard());
}
}
}

View File

@@ -111,6 +111,10 @@ public class StaticAbility extends CardTraitBase {
return 2;
}
if (this.mapParams.containsKey("ChangeText")) {
return 3;
}
if (this.mapParams.containsKey("AddType") || this.mapParams.containsKey("RemoveType")
|| this.mapParams.containsKey("RemoveCardTypes") || this.mapParams.containsKey("RemoveSubTypes")
|| this.mapParams.containsKey("RemoveSuperTypes") || this.mapParams.containsKey("RemoveCreatureTypes")) {
@@ -170,7 +174,9 @@ public class StaticAbility extends CardTraitBase {
* the host
*/
public StaticAbility(final String params, final Card host) {
this.mapParams.putAll(this.parseParams(params, host));
final Map<String, String> parsedParams = this.parseParams(params, host);
this.originalMapParams.putAll(parsedParams);
this.mapParams.putAll(parsedParams);
this.hostCard = host;
this.layer = this.generateLayer();
}
@@ -183,10 +189,9 @@ public class StaticAbility extends CardTraitBase {
* @param host
* the host
*/
public StaticAbility(final HashMap<String, String> params, final Card host) {
for (final Map.Entry<String, String> entry : params.entrySet()) {
this.mapParams.put(entry.getKey(), entry.getValue());
}
public StaticAbility(final Map<String, String> params, final Card host) {
this.originalMapParams.putAll(params);
this.mapParams.putAll(params);
this.layer = this.generateLayer();
this.hostCard = host;
}

View File

@@ -124,6 +124,7 @@ public abstract class Trigger extends TriggerReplacementBase {
this.intrinsic = intrinsic;
this.setRunParams(new HashMap<String, Object>());
this.originalMapParams.putAll(params);
this.mapParams.putAll(params);
this.setHostCard(host);
}
@@ -441,8 +442,8 @@ public abstract class Trigger extends TriggerReplacementBase {
public final Trigger getCopyForHostCard(Card newHost) {
TriggerType tt = TriggerType.getTypeFor(this);
Trigger copy = tt.createTrigger(mapParams, newHost, intrinsic);
final TriggerType tt = TriggerType.getTypeFor(this);
final Trigger copy = tt.createTrigger(originalMapParams, newHost, intrinsic);
if (this.getOverridingAbility() != null) {
copy.setOverridingAbility(this.getOverridingAbility());

View File

@@ -390,6 +390,10 @@ public class TriggerHandler implements IGameStateObject {
host.addRemembered(sa.getActivatingPlayer());
}
if (regtrig.isIntrinsic()) {
sa.changeText();
}
sa.setStackDescription(sa.toString());
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);