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/BidLifeEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/BondEffect.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/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/ChangeZoneAllEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.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 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/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/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/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/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/CardColor.java svneol=native#text/plain
forge-game/src/main/java/forge/game/card/CardDamageHistory.java -text 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 // don't play the land if it has cycling and enough lands are
// available // available
final ArrayList<SpellAbility> spellAbilities = c.getSpellAbilities(); final List<SpellAbility> spellAbilities = c.getSpellAbilities();
final List<Card> hand = player.getCardsIn(ZoneType.Hand); final List<Card> hand = player.getCardsIn(ZoneType.Hand);
List<Card> lands = player.getCardsIn(ZoneType.Battlefield); List<Card> lands = player.getCardsIn(ZoneType.Battlefield);

View File

@@ -178,4 +178,9 @@ public class TextUtil {
} }
return builder.toString(); 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 java.util.*;
import com.google.common.collect.Maps;
/** /**
* Base class for Triggers,ReplacementEffects and StaticAbilities. * Base class for Triggers,ReplacementEffects and StaticAbilities.
* *
@@ -22,7 +24,8 @@ public abstract class CardTraitBase extends GameObject {
protected Card hostCard; protected Card hostCard;
/** The map params. */ /** 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. */ /** The is intrinsic. */
protected boolean intrinsic; protected boolean intrinsic;
@@ -66,11 +69,6 @@ public abstract class CardTraitBase extends GameObject {
return this.mapParams; return this.mapParams;
} }
public final void setMapParams(Map<String,String> params) {
this.mapParams.clear();
this.mapParams.putAll(params);
}
/** /**
* Checks if is intrinsic. * Checks if is intrinsic.
* *
@@ -350,4 +348,15 @@ public abstract class CardTraitBase extends GameObject {
} }
return true; 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) { public AbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
super(sourceCard, abCost, tgt); super(sourceCard, abCost, tgt);
originalMapParams.putAll(params0);
mapParams.putAll(params0); mapParams.putAll(params0);
api = api0; api = api0;
effect = api.getSpellEffect(); effect = api.getSpellEffect();

View File

@@ -25,6 +25,7 @@ import org.apache.commons.lang3.text.WordUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -79,7 +80,7 @@ public class AbilityUtils {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static List<Card> getDefinedCards(final Card hostCard, final String def, final SpellAbility sa) { public static List<Card> getDefinedCards(final Card hostCard, final String def, final SpellAbility sa) {
final List<Card> cards = new ArrayList<Card>(); 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(); final Game game = hostCard.getGame();
Card c = null; Card c = null;
@@ -351,34 +352,37 @@ public class AbilityUtils {
// return result soon for plain numbers // return result soon for plain numbers
if (StringUtils.isNumeric(amount)) { return Integer.parseInt(amount) * multiplier; } 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. // Try to fetch variable, try ability first, then card.
String svarval = null; String svarval = null;
if (amount.indexOf('$') > 0) { // when there is a dollar sign, it's not a reference, it's a raw value! if (amount2.indexOf('$') > 0) { // when there is a dollar sign, it's not a reference, it's a raw value!
svarval = amount; svarval = amount2;
} }
else if (ability != null) { else if (ability != null) {
svarval = ability.getSVar(amount); svarval = ability.getSVar(amount2);
} }
if (StringUtils.isBlank(svarval)) { if (StringUtils.isBlank(svarval)) {
if (ability != null) { 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)) { if (StringUtils.isBlank(svarval)) {
// Some variables may be not chosen yet at this moment // Some variables may be not chosen yet at this moment
// So return 0 and don't issue an error. // So return 0 and don't issue an error.
if (amount.equals("ChosenX")) { if (amount2.equals("ChosenX")) {
// isn't made yet // isn't made yet
return 0; return 0;
} }
// cost hasn't been paid yet // cost hasn't been paid yet
if (amount.startsWith("Cost")) { if (amount2.startsWith("Cost")) {
return 0; return 0;
} }
// Nothing to do here if value is missing or blank // 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; return 0;
} }
@@ -395,6 +399,9 @@ public class AbilityUtils {
return 0; return 0;
} }
// modify amount string for text changes
calcX[1] = AbilityUtils.applyTextChangeEffects(calcX[1], card);
if (calcX[0].startsWith("Count")) { if (calcX[0].startsWith("Count")) {
return AbilityUtils.xCount(card, calcX[1], ability) * multiplier; 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) { public static List<Player> getDefinedPlayers(final Card card, final String def, final SpellAbility sa) {
final List<Player> players = new ArrayList<Player>(); 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(); final Game game = card == null ? null : card.getGame();
if (defined.equals("Targeted") || defined.equals("TargetedPlayer")) { 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, public static ArrayList<SpellAbility> getDefinedSpellAbilities(final Card card, final String def,
final SpellAbility sa) { final SpellAbility sa) {
final ArrayList<SpellAbility> sas = new ArrayList<SpellAbility>(); 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(); final Game game = sa.getActivatingPlayer().getGame();
SpellAbility s = null; SpellAbility s = null;
@@ -1398,9 +1405,9 @@ public class AbilityUtils {
* @return a int. * @return a int.
*/ */
public static int xCount(final Card c, final String s, final SpellAbility sa) { public static int xCount(final Card c, final String s, final SpellAbility sa) {
final String s2 = AbilityUtils.applyTextChangeEffects(s, c);
final String[] l = s.split("/"); final String[] l = s2.split("/");
final String expr = CardFactoryUtil.extractOperators(s); final String expr = CardFactoryUtil.extractOperators(s2);
final String[] sq; final String[] sq;
sq = l[0].split("\\."); 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) { public static final void applyManaColorConversion(final Player p, final Map<String, String> params) {
@@ -1506,4 +1513,15 @@ public class AbilityUtils {
} }
return sas; 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), BidLife (BidLifeEffect.class),
Bond (BondEffect.class), Bond (BondEffect.class),
ChangeTargets (ChangeTargetsEffect.class), ChangeTargets (ChangeTargetsEffect.class),
ChangeText (ChangeTextEffect.class),
ChangeZone (ChangeZoneEffect.class), ChangeZone (ChangeZoneEffect.class),
ChangeZoneAll (ChangeZoneAllEffect.class), ChangeZoneAll (ChangeZoneAllEffect.class),
Charm (CharmEffect.class), Charm (CharmEffect.class),
ChooseCard (ChooseCardEffect.class), ChooseCard (ChooseCardEffect.class),
ChooseColor (ChooseColorEffect.class), ChooseColor (ChooseColorEffect.class),

View File

@@ -20,6 +20,7 @@ public class SpellApiBased extends Spell {
super(sourceCard, abCost); super(sourceCard, abCost);
this.setTargetRestrictions(tgt); this.setTargetRestrictions(tgt);
originalMapParams.putAll(params0);
mapParams.putAll(params0); mapParams.putAll(params0);
api = api0; api = api0;
effect = api.getSpellEffect(); 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) { public StaticAbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
super(sourceCard, abCost, tgt); super(sourceCard, abCost, tgt);
originalMapParams.putAll(params0);
mapParams.putAll(params0); mapParams.putAll(params0);
api = api0; api = api0;
effect = api.getSpellEffect(); 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); movedCard = game.getAction().moveTo(destination, tgtC);
// If a card is Exiled from the stack, remove its spells from the stack // If a card is Exiled from the stack, remove its spells from the stack
if (sa.hasParam("Fizzle")) { if (sa.hasParam("Fizzle")) {
ArrayList<SpellAbility> spells = tgtC.getSpellAbilities(); final List<SpellAbility> spells = tgtC.getSpellAbilities();
for (SpellAbility spell : spells) { for (SpellAbility spell : spells) {
if (tgtC.isInZone(ZoneType.Exile) || tgtC.isInZone(ZoneType.Hand)) { if (tgtC.isInZone(ZoneType.Exile) || tgtC.isInZone(ZoneType.Hand)) {
final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(spell); final SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(spell);

View File

@@ -20,7 +20,9 @@ package forge.game.card;
import com.esotericsoftware.minlog.Log; import com.esotericsoftware.minlog.Log;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.GameCommand; import forge.GameCommand;
import forge.StaticData; 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, CardType> changedCardTypes = new ConcurrentSkipListMap<Long, CardType>();
private Map<Long, CardKeywords> changedCardKeywords = new ConcurrentSkipListMap<Long, CardKeywords>(); 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 ArrayList<Object> rememberedObjects = new ArrayList<Object>();
private final MapOfLists<GameEntity, Object> rememberMap = new HashMapOfLists<GameEntity, Object>(CollectionSuppliers.<Object>arrayLists()); private final MapOfLists<GameEntity, Object> rememberMap = new HashMapOfLists<GameEntity, Object>(CollectionSuppliers.<Object>arrayLists());
private final ArrayList<Card> imprintedCards = new ArrayList<Card>(); 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: // NOTE:
if (sb.toString().contains(" (NOTE: ")) { if (sb.toString().contains(" (NOTE: ")) {
sb.insert(sb.indexOf("(NOTE: "), "\r\n"); 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()); return Collections.unmodifiableList(this.getCharacteristics().getManaAbility());
} }
public final boolean canProduceSameManaTypeWith(final Card c) { public final boolean canProduceSameManaTypeWith(final Card c) {
final List<SpellAbility> manaAb = this.getManaAbility(); final List<SpellAbility> manaAb = this.getManaAbility();
if (manaAb.isEmpty()) { if (manaAb.isEmpty()) {
@@ -2691,7 +2710,6 @@ public class Card extends GameEntity implements Comparable<Card> {
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
*/ */
public final void addSpellAbility(final SpellAbility a) { public final void addSpellAbility(final SpellAbility a) {
a.setHostCard(this); a.setHostCard(this);
if (a.isManaAbility()) { if (a.isManaAbility()) {
this.getCharacteristics().getManaAbility().add(a); this.getCharacteristics().getManaAbility().add(a);
@@ -2700,7 +2718,6 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
/** /**
* <p> * <p>
* removeSpellAbility. * removeSpellAbility.
@@ -2726,7 +2743,7 @@ public class Card extends GameEntity implements Comparable<Card> {
* *
* @return a {@link java.util.ArrayList} object. * @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()); final ArrayList<SpellAbility> res = new ArrayList<SpellAbility>(this.getManaAbility());
res.addAll(this.getCharacteristics().getSpellAbility()); res.addAll(this.getCharacteristics().getSpellAbility());
return res; return res;
@@ -4483,9 +4500,10 @@ public class Card extends GameEntity implements Comparable<Card> {
* *
* @param timestamp * @param timestamp
* the timestamp * the timestamp
* @return the removed {@link CardKeywords}.
*/ */
public final void removeChangedCardKeywords(final long timestamp) { public final CardKeywords removeChangedCardKeywords(final long timestamp) {
changedCardKeywords.remove(Long.valueOf(timestamp)); return changedCardKeywords.remove(Long.valueOf(timestamp));
} }
// Hidden keywords will be left out // Hidden keywords will be left out
@@ -4518,6 +4536,97 @@ public class Card extends GameEntity implements Comparable<Card> {
return keywords; 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> * <p>
* getIntrinsicAbilities. * getIntrinsicAbilities.
@@ -4542,19 +4651,6 @@ public class Card extends GameEntity implements Comparable<Card> {
return this.getCharacteristics().getIntrinsicKeyword(); 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> * <p>
* setIntrinsicAbilities. * 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.player.Player;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.spellability.*; import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerHandler;
import forge.game.trigger.WrappedAbility; import forge.game.trigger.WrappedAbility;
@@ -288,10 +289,10 @@ public class CardFactory {
card.setState(state); card.setState(state);
CardFactoryUtil.addAbilityFactoryAbilities(card); CardFactoryUtil.addAbilityFactoryAbilities(card);
for (String stAb : card.getStaticAbilityStrings()) { 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); CardCharacteristics original = card.getState(CardCharacteristicName.Original);

View File

@@ -682,7 +682,7 @@ public class CardFactoryUtil {
return doXMath(n, m, source); 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[] l = s.split("/");
final String m = extractOperators(s); final String m = extractOperators(s);
@@ -1975,7 +1975,9 @@ public class CardFactoryUtil {
// ************************************************** // **************************************************
// AbilityFactory cards // AbilityFactory cards
for (String rawAbility : card.getUnparsedAbilities()) { 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) { public ReplacementEffect(final Map<String, String> map, final Card host, final boolean intrinsic) {
this.intrinsic = intrinsic; this.intrinsic = intrinsic;
originalMapParams.putAll(map);
mapParams.putAll(map); mapParams.putAll(map);
this.setHostCard(host); this.setHostCard(host);
} }

View File

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

View File

@@ -86,8 +86,11 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
this.setTargetRestrictions(tgt); this.setTargetRestrictions(tgt);
api = api0; api = api0;
if (params0 != null) if (params0 != null) {
originalMapParams.putAll(params0);
mapParams.putAll(params0); mapParams.putAll(params0);
}
effect = api.getSpellEffect(); effect = api.getSpellEffect();
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { 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) { public void setChosenList(List<AbilitySub> choices) {
this.chosenList = 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.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
@@ -47,7 +48,8 @@ public class TargetRestrictions {
// What this Object is restricted to targeting // What this Object is restricted to targeting
private boolean tgtValid = false; private boolean tgtValid = false;
private String[] validTgts; private String[] originalValidTgts,
validTgts;
private String uiPrompt = ""; private String uiPrompt = "";
private List<ZoneType> tgtZone = Arrays.asList(ZoneType.Battlefield); private List<ZoneType> tgtZone = Arrays.asList(ZoneType.Battlefield);
@@ -86,7 +88,8 @@ public class TargetRestrictions {
public TargetRestrictions(final TargetRestrictions target) { public TargetRestrictions(final TargetRestrictions target) {
this.tgtValid = true; this.tgtValid = true;
this.uiPrompt = target.getVTSelection(); this.uiPrompt = target.getVTSelection();
this.validTgts = target.getValidTgts(); this.originalValidTgts = target.getValidTgts();
this.validTgts = this.originalValidTgts.clone();
this.minTargets = target.getMinTargets(); this.minTargets = target.getMinTargets();
this.maxTargets = target.getMaxTargets(); this.maxTargets = target.getMaxTargets();
this.tgtZone = target.getZone(); 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) { public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) {
this.tgtValid = true; this.tgtValid = true;
this.uiPrompt = prompt; this.uiPrompt = prompt;
this.validTgts = valid; this.originalValidTgts = valid;
this.validTgts = this.originalValidTgts.clone();
this.minTargets = min; this.minTargets = min;
this.maxTargets = max; 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? final Card srcCard = sa.getHostCard(); // should there be OrginalHost at any moment?
if (this.tgtZone.contains(ZoneType.Stack)) { if (this.tgtZone.contains(ZoneType.Stack)) {
// Stack Zone targets are considered later // 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? final Card srcCard = sa.getHostCard(); // should there be OrginalHost at any moment?
if (this.tgtZone.contains(ZoneType.Stack)) { if (this.tgtZone.contains(ZoneType.Stack)) {
for (final Card c : game.getStackZone().getCards()) { for (final Card c : game.getStackZone().getCards()) {
@@ -698,4 +706,10 @@ public class TargetRestrictions {
return this.dividedMap; 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; return 2;
} }
if (this.mapParams.containsKey("ChangeText")) {
return 3;
}
if (this.mapParams.containsKey("AddType") || this.mapParams.containsKey("RemoveType") if (this.mapParams.containsKey("AddType") || this.mapParams.containsKey("RemoveType")
|| this.mapParams.containsKey("RemoveCardTypes") || this.mapParams.containsKey("RemoveSubTypes") || this.mapParams.containsKey("RemoveCardTypes") || this.mapParams.containsKey("RemoveSubTypes")
|| this.mapParams.containsKey("RemoveSuperTypes") || this.mapParams.containsKey("RemoveCreatureTypes")) { || this.mapParams.containsKey("RemoveSuperTypes") || this.mapParams.containsKey("RemoveCreatureTypes")) {
@@ -170,7 +174,9 @@ public class StaticAbility extends CardTraitBase {
* the host * the host
*/ */
public StaticAbility(final String params, final Card 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.hostCard = host;
this.layer = this.generateLayer(); this.layer = this.generateLayer();
} }
@@ -183,10 +189,9 @@ public class StaticAbility extends CardTraitBase {
* @param host * @param host
* the host * the host
*/ */
public StaticAbility(final HashMap<String, String> params, final Card host) { public StaticAbility(final Map<String, String> params, final Card host) {
for (final Map.Entry<String, String> entry : params.entrySet()) { this.originalMapParams.putAll(params);
this.mapParams.put(entry.getKey(), entry.getValue()); this.mapParams.putAll(params);
}
this.layer = this.generateLayer(); this.layer = this.generateLayer();
this.hostCard = host; this.hostCard = host;
} }

View File

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

View File

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