KeywordApocalypse: return KeywordInstance whenever possible

This commit is contained in:
Hanmac
2017-10-23 19:54:12 +00:00
parent 6433eff8ec
commit 2030ffcea3
26 changed files with 520 additions and 300 deletions

View File

@@ -17,9 +17,14 @@
*/ */
package forge.ai; package forge.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.ability.AnimateAi; import forge.ai.ability.AnimateAi;
import forge.card.CardTypeView; import forge.card.CardTypeView;
import forge.game.GameEntity; import forge.game.GameEntity;
@@ -30,6 +35,7 @@ import forge.game.card.*;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions; import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
@@ -39,10 +45,6 @@ import forge.util.Expressions;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer() //doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
/** /**
@@ -647,7 +649,8 @@ public class AiAttackController {
&& isEffectiveAttacker(ai, attacker, combat)) { && isEffectiveAttacker(ai, attacker, combat)) {
mustAttack = true; mustAttack = true;
} else { } else {
for (String s : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
String s = inst.getOriginal();
if (s.equals("CARDNAME attacks each turn if able.") if (s.equals("CARDNAME attacks each turn if able.")
|| s.startsWith("CARDNAME attacks specific player each combat if able") || s.startsWith("CARDNAME attacks specific player each combat if able")
|| s.equals("CARDNAME attacks each combat if able.")) { || s.equals("CARDNAME attacks each combat if able.")) {
@@ -1089,7 +1092,8 @@ public class AiAttackController {
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|| "Blocked".equals(attacker.getSVar("HasAttackEffect")); || "Blocked".equals(attacker.getSVar("HasAttackEffect"));
if (!hasCombatEffect) { if (!hasCombatEffect) {
for (String keyword : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.equals("Wither") || keyword.equals("Infect") if (keyword.equals("Wither") || keyword.equals("Infect")
|| keyword.equals("Lifelink") || keyword.startsWith("Afflict")) { || keyword.equals("Lifelink") || keyword.startsWith("Afflict")) {
hasCombatEffect = true; hasCombatEffect = true;
@@ -1124,7 +1128,8 @@ public class AiAttackController {
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) { if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false; canKillAllDangerous = false;
} else { } else {
for (String keyword : defender.getKeywords()) { for (KeywordInterface inst : defender.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) { if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) {
canKillAllDangerous = false; canKillAllDangerous = false;
break; break;

View File

@@ -5,6 +5,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.card.CardType; import forge.card.CardType;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
@@ -22,6 +23,8 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.CostPayEnergy; import forge.game.cost.CostPayEnergy;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -42,7 +45,6 @@ import org.apache.commons.lang3.tuple.Pair;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
public class ComputerUtilCard { public class ComputerUtilCard {
public static Card getMostExpensivePermanentAI(final CardCollectionView list, final SpellAbility spell, final boolean targeted) { public static Card getMostExpensivePermanentAI(final CardCollectionView list, final SpellAbility spell, final boolean targeted) {
CardCollectionView all = list; CardCollectionView all = list;
@@ -1564,19 +1566,21 @@ public class ComputerUtilCard {
if (c.isTapped()) { if (c.isTapped()) {
pumped.setTapped(true); pumped.setTapped(true);
} }
final List<String> copiedKeywords = pumped.getKeywords();
List<String> toCopy = new ArrayList<String>(); KeywordCollection copiedKeywords = new KeywordCollection();
for (String kw : c.getKeywords()) { copiedKeywords.insertAll(pumped.getKeywords());
if (!copiedKeywords.contains(kw)) { List<KeywordInterface> toCopy = Lists.newArrayList();
if (kw.startsWith("HIDDEN")) { for (KeywordInterface k : c.getKeywords()) {
pumped.addHiddenExtrinsicKeyword(kw); if (!copiedKeywords.contains(k.getOriginal())) {
if (k.getHidden()) {
pumped.addHiddenExtrinsicKeyword(k);
} else { } else {
toCopy.add(kw); toCopy.add(k);
} }
} }
} }
final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used? final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used?
pumped.addChangedCardKeywords(toCopy, new ArrayList<String>(), false, timestamp2); pumped.addChangedCardKeywordsInternal(toCopy, Lists.<KeywordInterface>newArrayList(), false, timestamp2, true);
ComputerUtilCard.applyStaticContPT(ai.getGame(), pumped, new CardCollection(c)); ComputerUtilCard.applyStaticContPT(ai.getGame(), pumped, new CardCollection(c));
return pumped; return pumped;
} }

View File

@@ -17,10 +17,14 @@
*/ */
package forge.ai; package forge.ai;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
@@ -32,6 +36,7 @@ import forge.game.card.*;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
import forge.game.cost.CostPayment; import forge.game.cost.CostPayment;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.Untap; import forge.game.phase.Untap;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
@@ -46,9 +51,6 @@ import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import java.util.List;
import java.util.Map;
/** /**
* <p> * <p>
@@ -103,7 +105,8 @@ public class ComputerUtilCombat {
return false; return false;
} }
for (final String keyword : atacker.getKeywords()) { for (final KeywordInterface inst : atacker.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1]; final String defined = keyword.split(":")[1];
final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0); final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0);
@@ -2507,7 +2510,8 @@ public class ComputerUtilCombat {
for (Card atk : attackers) { for (Card atk : attackers) {
boolean hasProtection = false; boolean hasProtection = false;
for (String kw : atk.getKeywords()) { for (KeywordInterface inst : atk.getKeywords()) {
String kw = inst.getOriginal();
if (kw.startsWith("Protection")) { if (kw.startsWith("Protection")) {
hasProtection = true; hasProtection = true;
break; break;

View File

@@ -1,11 +1,13 @@
package forge.ai; package forge.ai;
import com.google.common.base.Function; import com.google.common.base.Function;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.cost.CostPayEnergy; import forge.game.cost.CostPayEnergy;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
public class CreatureEvaluator implements Function<Card, Integer> { public class CreatureEvaluator implements Function<Card, Integer> {
@@ -32,7 +34,8 @@ public class CreatureEvaluator implements Function<Card, Integer> {
} }
int power = getEffectivePower(c); int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c); final int toughness = getEffectiveToughness(c);
for (String keyword : c.getKeywords()) { for (KeywordInterface kw : c.getKeywords()) {
String keyword = kw.getOriginal();
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.") if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.") || keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.") || keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")

View File

@@ -1,6 +1,9 @@
package forge.ai.ability; package forge.ai.ability;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
@@ -14,13 +17,13 @@ import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import org.apache.commons.lang3.StringUtils;
public class PermanentAi extends SpellAbilityAi { public class PermanentAi extends SpellAbilityAi {
@@ -144,7 +147,8 @@ public class PermanentAi extends SpellAbilityAi {
} }
// don't play cards without being able to pay the upkeep for // don't play cards without being able to pay the upkeep for
for (String ability : card.getKeywords()) { for (KeywordInterface inst : card.getKeywords()) {
String ability = inst.getOriginal();
if (ability.startsWith("UpkeepCost")) { if (ability.startsWith("UpkeepCost")) {
final String[] k = ability.split(":"); final String[] k = ability.split(":");
final String costs = k[1]; final String costs = k[1];

View File

@@ -24,6 +24,7 @@ import forge.game.card.CardFactoryUtil;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.card.token.TokenInfo; import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -306,7 +307,7 @@ public class GameCopier {
newCard.setChangedCardTypes(c.getChangedCardTypesMap()); newCard.setChangedCardTypes(c.getChangedCardTypesMap());
newCard.setChangedCardKeywords(c.getChangedCardKeywords()); newCard.setChangedCardKeywords(c.getChangedCardKeywords());
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such? // TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
for (String kw : c.getHiddenExtrinsicKeywords()) for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
newCard.addHiddenExtrinsicKeyword(kw); newCard.addHiddenExtrinsicKeyword(kw);
newCard.setExtrinsicKeyword(Lists.newArrayList(c.getExtrinsicKeyword())); newCard.setExtrinsicKeyword(Lists.newArrayList(c.getExtrinsicKeyword()));
if (c.isTapped()) { if (c.isTapped()) {

View File

@@ -27,6 +27,7 @@ import forge.game.ability.ApiType;
import forge.game.ability.effects.AttachEffect; import forge.game.ability.effects.AttachEffect;
import forge.game.card.*; import forge.game.card.*;
import forge.game.event.*; import forge.game.event.*;
import forge.game.keyword.KeywordInterface;
import forge.game.player.GameLossReason; import forge.game.player.GameLossReason;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
@@ -1824,7 +1825,8 @@ public class GameAction {
// Select what can be activated from a given hand // Select what can be activated from a given hand
for (final Card c : takesAction.getCardsIn(ZoneType.Hand)) { for (final Card c : takesAction.getCardsIn(ZoneType.Hand)) {
for (String kw : c.getKeywords()) { for (KeywordInterface inst : c.getKeywords()) {
String kw = inst.getOriginal();
if (kw.startsWith("MayEffectFromOpeningHand")) { if (kw.startsWith("MayEffectFromOpeningHand")) {
String[] split = kw.split(":"); String[] split = kw.split(":");
final String effName = split[1]; final String effName = split[1];

View File

@@ -29,6 +29,7 @@ import forge.game.ability.ApiType;
import forge.game.card.*; import forge.game.card.*;
import forge.game.card.CardPlayOption.PayManaCost; import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.*; import forge.game.spellability.*;
@@ -200,7 +201,8 @@ public final class GameActionUtil {
alternatives.add(newSA); alternatives.add(newSA);
} }
for (final String keyword : source.getKeywords()) { for (final KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (sa.isSpell() && keyword.startsWith("Flashback")) { if (sa.isSpell() && keyword.startsWith("Flashback")) {
// if source has No Mana cost, and flashback doesn't have own one, // if source has No Mana cost, and flashback doesn't have own one,
// flashback can't work // flashback can't work
@@ -248,7 +250,8 @@ public final class GameActionUtil {
return costs; return costs;
} }
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
for (String keyword : source.getKeywords()) { for (KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("Buyback")) { if (keyword.startsWith("Buyback")) {
final Cost cost = new Cost(keyword.substring(8), false); final Cost cost = new Cost(keyword.substring(8), false);
costs.add(new OptionalCostValue(OptionalCost.Buyback, cost)); costs.add(new OptionalCostValue(OptionalCost.Buyback, cost));
@@ -319,7 +322,8 @@ public final class GameActionUtil {
return abilities; return abilities;
} }
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
for (String keyword : source.getKeywords()) { for (KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("AlternateAdditionalCost")) { if (keyword.startsWith("AlternateAdditionalCost")) {
final List<SpellAbility> newAbilities = Lists.newArrayList(); final List<SpellAbility> newAbilities = Lists.newArrayList();
String[] costs = TextUtil.split(keyword, ':'); String[] costs = TextUtil.split(keyword, ':');
@@ -370,7 +374,8 @@ public final class GameActionUtil {
} }
// Buyback, Kicker // Buyback, Kicker
for (String keyword : source.getKeywords()) { for (KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("Buyback")) { if (keyword.startsWith("Buyback")) {
for (int i = 0; i < abilities.size(); i++) { for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy(); final SpellAbility newSA = abilities.get(i).copy();

View File

@@ -18,6 +18,7 @@ import forge.game.GameObject;
import forge.game.ability.AbilityFactory.AbilityRecordType; import forge.game.ability.AbilityFactory.AbilityRecordType;
import forge.game.card.*; import forge.game.card.*;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
@@ -1823,7 +1824,8 @@ public class AbilityUtils {
public static void addSpliceEffect(final SpellAbility sa, final Card c) { public static void addSpliceEffect(final SpellAbility sa, final Card c) {
Cost spliceCost = null; Cost spliceCost = null;
for (final String k : c.getKeywords()) { for (final KeywordInterface inst : c.getKeywords()) {
final String k = inst.getOriginal();
if (k.startsWith("Splice")) { if (k.startsWith("Splice")) {
final String n[] = k.split(":"); final String n[] = k.split(":");
spliceCost = new Cost(n[2], false); spliceCost = new Cost(n[2], false);

View File

@@ -274,11 +274,11 @@ public class CloneEffect extends SpellAbilityEffect {
keywords.remove(k); keywords.remove(k);
} }
k = keywords.get(i); k = keywords.get(i);
tgtCard.addIntrinsicKeyword(k);
KeywordInterface inst = Keyword.getInstance(k); KeywordInterface inst = tgtCard.addIntrinsicKeyword(k);
if (inst != null) {
inst.addKeywords(tgtCard, true); inst.addKeywords(tgtCard, true);
}
} }
} }

View File

@@ -5,6 +5,7 @@ import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import java.util.Arrays; import java.util.Arrays;
@@ -73,7 +74,8 @@ public class DebuffEffect extends SpellAbilityEffect {
if (tgtC.isInPlay() && tgtC.canBeTargetedBy(sa)) { if (tgtC.isInPlay() && tgtC.canBeTargetedBy(sa)) {
if (sa.hasParam("AllSuffixKeywords")) { if (sa.hasParam("AllSuffixKeywords")) {
String suffix = sa.getParam("AllSuffixKeywords"); String suffix = sa.getParam("AllSuffixKeywords");
for (final String keyword : tgtC.getKeywords()) { for (final KeywordInterface kw : tgtC.getKeywords()) {
String keyword = kw.getOriginal();
if (keyword.endsWith(suffix)) { if (keyword.endsWith(suffix)) {
kws.add(keyword); kws.add(keyword);
} }
@@ -81,7 +83,8 @@ public class DebuffEffect extends SpellAbilityEffect {
} }
// special for Protection:Card.<color>:Protection from <color>:* // special for Protection:Card.<color>:Protection from <color>:*
for (final String keyword : tgtC.getKeywords()) { for (final KeywordInterface inst : tgtC.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.startsWith("Protection:")) { if (keyword.startsWith("Protection:")) {
for (final String kw : kws) { for (final String kw : kws) {
if (keyword.matches("(?i).*:" + kw)) if (keyword.matches("(?i).*:" + kw))

View File

@@ -9,6 +9,7 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
@@ -258,8 +259,8 @@ public class PumpEffect extends SpellAbilityEffect {
List<String> choice = Lists.newArrayList(); List<String> choice = Lists.newArrayList();
List<String> total = Lists.newArrayList(keywords); List<String> total = Lists.newArrayList(keywords);
if (sa.hasParam("NoRepetition")) { if (sa.hasParam("NoRepetition")) {
final List<String> tgtCardskws = tgtCards.get(0).getKeywords(); for (KeywordInterface inst : tgtCards.get(0).getKeywords()) {
for (String kws : tgtCardskws) { final String kws = inst.getOriginal();
if (total.contains(kws)) { if (total.contains(kws)) {
total.remove(kws); total.remove(kws);
} }

View File

@@ -40,6 +40,9 @@ import forge.game.event.*;
import forge.game.event.GameEventCardAttachment.AttachMethod; import forge.game.event.GameEventCardAttachment.AttachMethod;
import forge.game.event.GameEventCardDamaged.DamageType; import forge.game.event.GameEventCardDamaged.DamageType;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInstance;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordsChange; import forge.game.keyword.KeywordsChange;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
@@ -93,9 +96,9 @@ public class Card extends GameEntity implements Comparable<Card> {
private final CardDamageHistory damageHistory = new CardDamageHistory(); private final CardDamageHistory damageHistory = new CardDamageHistory();
private Map<Card, Map<CounterType, Integer>> countersAddedBy = Maps.newTreeMap(); private Map<Card, Map<CounterType, Integer>> countersAddedBy = Maps.newTreeMap();
private List<String> extrinsicKeyword = Lists.newArrayList(); private KeywordCollection extrinsicKeyword = new KeywordCollection();
// Hidden keywords won't be displayed on the card // Hidden keywords won't be displayed on the card
private final CopyOnWriteArrayList<String> hiddenExtrinsicKeyword = new CopyOnWriteArrayList<>(); private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection();
// cards attached or otherwise linked to this card // cards attached or otherwise linked to this card
private CardCollection equippedBy, fortifiedBy, hauntedBy, devouredCards, delvedCards, imprintedCards, encodedCards; private CardCollection equippedBy, fortifiedBy, hauntedBy, devouredCards, delvedCards, imprintedCards, encodedCards;
@@ -122,7 +125,7 @@ public class Card extends GameEntity implements Comparable<Card> {
private final CardChangedWords changedTextColors = new CardChangedWords(); private final CardChangedWords changedTextColors = new CardChangedWords();
private final CardChangedWords changedTextTypes = new CardChangedWords(); private final CardChangedWords changedTextTypes = new CardChangedWords();
/** List of the keywords that have been added by text changes. */ /** List of the keywords that have been added by text changes. */
private final List<String> keywordsGrantedByTextChanges = Lists.newArrayList(); private final List<KeywordInterface> keywordsGrantedByTextChanges = Lists.newArrayList();
/** Original values of SVars changed by text changes. */ /** Original values of SVars changed by text changes. */
private Map<String, String> originalSVars = Maps.newHashMap(); private Map<String, String> originalSVars = Maps.newHashMap();
@@ -1374,7 +1377,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// convert a keyword list to the String that should be displayed in game // convert a keyword list to the String that should be displayed in game
public final String keywordsToText(final List<String> keywords) { public final String keywordsToText(final Collection<KeywordInterface> keywords) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final StringBuilder sbLong = new StringBuilder(); final StringBuilder sbLong = new StringBuilder();
@@ -1382,8 +1385,9 @@ public class Card extends GameEntity implements Comparable<Card> {
final Set<Entry<String, String>> textChanges = Sets.union( final Set<Entry<String, String>> textChanges = Sets.union(
changedTextColors.toMap().entrySet(), changedTextTypes.toMap().entrySet()); changedTextColors.toMap().entrySet(), changedTextTypes.toMap().entrySet());
for (int i = 0; i < keywords.size(); i++) { int i = 0;
String keyword = keywords.get(i); for (KeywordInterface inst : keywords) {
String keyword = inst.getOriginal();
if (keyword.startsWith("PreventAllDamageBy") if (keyword.startsWith("PreventAllDamageBy")
|| keyword.startsWith("CantEquip") || keyword.startsWith("CantEquip")
|| keyword.startsWith("SpellCantTarget")) { || keyword.startsWith("SpellCantTarget")) {
@@ -1391,7 +1395,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// format text changes // format text changes
if (CardUtil.isKeywordModifiable(keyword) if (CardUtil.isKeywordModifiable(keyword)
&& keywordsGrantedByTextChanges.contains(keyword)) { && keywordsGrantedByTextChanges.contains(inst)) {
for (final Entry<String, String> e : textChanges) { for (final Entry<String, String> e : textChanges) {
final String value = e.getValue(); final String value = e.getValue();
if (keyword.contains(value)) { if (keyword.contains(value)) {
@@ -1453,13 +1457,13 @@ public class Card extends GameEntity implements Comparable<Card> {
if (!mCost.isOnlyManaCost()) { if (!mCost.isOnlyManaCost()) {
sbLong.append("."); sbLong.append(".");
} }
sbLong.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); sbLong.append(" (" + inst.getReminderText() + ")");
sbLong.append("\r\n"); sbLong.append("\r\n");
} }
} else if (keyword.startsWith("Emerge")) { } else if (keyword.startsWith("Emerge")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1])); sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1]));
sbLong.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); sbLong.append(" (" + inst.getReminderText() + ")");
sbLong.append("\r\n"); sbLong.append("\r\n");
} else if (keyword.startsWith("Echo")) { } else if (keyword.startsWith("Echo")) {
sbLong.append("Echo "); sbLong.append("Echo ");
@@ -1490,7 +1494,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final String[] n = keyword.split(":"); final String[] n = keyword.split(":");
final Cost cost = new Cost(n[1], false); final Cost cost = new Cost(n[1], false);
sbLong.append("Multikicker ").append(cost.toSimpleString()); sbLong.append("Multikicker ").append(cost.toSimpleString());
sbLong.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")").append("\r\n"); sbLong.append(" (" + inst.getReminderText() + ")").append("\r\n");
} }
} else if (keyword.startsWith("Kicker")) { } else if (keyword.startsWith("Kicker")) {
if (!keyword.endsWith("Generic")) { if (!keyword.endsWith("Generic")) {
@@ -1504,13 +1508,13 @@ public class Card extends GameEntity implements Comparable<Card> {
final Cost cost2 = new Cost(n[2], false); final Cost cost2 = new Cost(n[2], false);
sbx.append(cost2.toSimpleString()); sbx.append(cost2.toSimpleString());
} }
sbx.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); sbx.append(" (" + inst.getReminderText() + ")");
sbLong.append(sbx).append("\r\n"); sbLong.append(sbx).append("\r\n");
} }
} else if (keyword.endsWith(".") && !keyword.startsWith("Haunt")) { } else if (keyword.endsWith(".") && !keyword.startsWith("Haunt")) {
sbLong.append(keyword).append("\r\n"); sbLong.append(keyword).append("\r\n");
} else if (keyword.startsWith("Presence")) { } else if (keyword.startsWith("Presence")) {
sbLong.append(Keyword.getInstance(keyword).getReminderText()); sbLong.append(inst.getReminderText());
} else if (keyword.contains("At the beginning of your upkeep, ") } else if (keyword.contains("At the beginning of your upkeep, ")
&& keyword.contains(" unless you pay")) { && keyword.contains(" unless you pay")) {
sbLong.append(keyword).append("\r\n"); sbLong.append(keyword).append("\r\n");
@@ -1524,14 +1528,14 @@ public class Card extends GameEntity implements Comparable<Card> {
|| keyword.equals("Changeling") || keyword.equals("Delve") || keyword.startsWith("Dredge") || keyword.equals("Changeling") || keyword.equals("Delve") || keyword.startsWith("Dredge")
|| (keyword.startsWith("Split second") && !sb.toString().contains("Split second")) || (keyword.startsWith("Split second") && !sb.toString().contains("Split second"))
|| keyword.startsWith("Devoid")){ || keyword.startsWith("Devoid")){
sbLong.append(keyword + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); sbLong.append(keyword + " (" + inst.getReminderText() + ")");
} else if (keyword.startsWith("Modular") || keyword.startsWith("Bloodthirst") } else if (keyword.startsWith("Modular") || keyword.startsWith("Bloodthirst")
|| keyword.startsWith("Fabricate") || keyword.startsWith("Soulshift") || keyword.startsWith("Fabricate") || keyword.startsWith("Soulshift")
|| keyword.startsWith("Crew") || keyword.startsWith("Tribute") || keyword.startsWith("Absorb") || keyword.startsWith("Crew") || keyword.startsWith("Tribute") || keyword.startsWith("Absorb")
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing") || keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing")
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) { || keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
sbLong.append(k[0] + " " + k[1] + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); sbLong.append(k[0] + " " + k[1] + " (" + inst.getReminderText() + ")");
} else if (keyword.contains("Haunt")) { } else if (keyword.contains("Haunt")) {
sb.append("\r\nHaunt ("); sb.append("\r\nHaunt (");
if (isCreature()) { if (isCreature()) {
@@ -1553,7 +1557,7 @@ public class Card extends GameEntity implements Comparable<Card> {
if (sb.length() != 0) { if (sb.length() != 0) {
sb.append("\r\n"); sb.append("\r\n");
} }
sb.append(keyword + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); sb.append(keyword + " (" + inst.getReminderText() + ")");
} else if (keyword.endsWith(" offering")) { } else if (keyword.endsWith(" offering")) {
String offeringType = keyword.split(" ")[0]; String offeringType = keyword.split(" ")[0];
if (sb.length() != 0) { if (sb.length() != 0) {
@@ -1609,6 +1613,8 @@ public class Card extends GameEntity implements Comparable<Card> {
if (sbLong.length() > 0) { if (sbLong.length() > 0) {
sbLong.append("\r\n"); sbLong.append("\r\n");
} }
i++;
} }
if (sb.length() > 0) { if (sb.length() > 0) {
sb.append("\r\n"); sb.append("\r\n");
@@ -1937,9 +1943,6 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
// Add Keywords
final List<String> kw = getKeywords(state);
// Triggered abilities // Triggered abilities
for (final Trigger trig : state.getTriggers()) { for (final Trigger trig : state.getTriggers()) {
if (!trig.isSecondary()) { if (!trig.isSecondary()) {
@@ -1966,7 +1969,8 @@ public class Card extends GameEntity implements Comparable<Card> {
// TODO A lot of these keywords should really show up before the SAs // TODO A lot of these keywords should really show up before the SAs
// keyword descriptions // keyword descriptions
for (final String keyword : kw) { for (final KeywordInterface inst : getKeywords(state)) {
final String keyword = inst.getOriginal();
if ((keyword.startsWith("Ripple") && !sb.toString().contains("Ripple")) if ((keyword.startsWith("Ripple") && !sb.toString().contains("Ripple"))
//|| (keyword.startsWith("Dredge") && !sb.toString().contains("Dredge")) | Replaced with //|| (keyword.startsWith("Dredge") && !sb.toString().contains("Dredge")) | Replaced with
// keyword.startsWith("Dredge") Hopefully that doesn't break anything. -Indigo Dragon 8/9/2017 // keyword.startsWith("Dredge") Hopefully that doesn't break anything. -Indigo Dragon 8/9/2017
@@ -1977,11 +1981,11 @@ public class Card extends GameEntity implements Comparable<Card> {
|| (keyword.startsWith("Miracle") && !sb.toString().contains("Miracle"))) { || (keyword.startsWith("Miracle") && !sb.toString().contains("Miracle"))) {
String[] parts = keyword.split(":"); String[] parts = keyword.split(":");
sb.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])) sb.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1]))
.append(" (").append(Keyword.getInstance(keyword).getReminderText()).append(")").append("\r\n"); .append(" (").append(inst.getReminderText()).append(")").append("\r\n");
} else if (keyword.equals("CARDNAME can't be countered.")) { } else if (keyword.equals("CARDNAME can't be countered.")) {
sb.append(keyword).append("\r\n"); sb.append(keyword).append("\r\n");
} else if (keyword.equals("Aftermath")) { } else if (keyword.equals("Aftermath")) {
sb.append(Keyword.getInstance(keyword).getReminderText()).append("\r\n"); sb.append(inst.getReminderText()).append("\r\n");
} else if (keyword.startsWith("Conspire") || keyword.startsWith("Dredge") } else if (keyword.startsWith("Conspire") || keyword.startsWith("Dredge")
|| keyword.startsWith("Cascade") || keyword.startsWith("Wither") || keyword.startsWith("Cascade") || keyword.startsWith("Wither")
|| (keyword.startsWith("Epic") && !sb.toString().contains("Epic")) || (keyword.startsWith("Epic") && !sb.toString().contains("Epic"))
@@ -1989,7 +1993,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|| (keyword.startsWith("Devoid"))) { || (keyword.startsWith("Devoid"))) {
if (sb.length() != 0) { if (sb.length() != 0) {
sb.append("\r\n"); sb.append("\r\n");
sb.append(keyword + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); sb.append(keyword + " (" + inst.getReminderText() + ")");
} }
} else if (keyword.equals("You may cast CARDNAME as though it had flash if you pay {2} more to cast it.")) { } else if (keyword.equals("You may cast CARDNAME as though it had flash if you pay {2} more to cast it.")) {
sb.append(keyword).append("\r\n"); sb.append(keyword).append("\r\n");
@@ -1997,7 +2001,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final String[] n = keyword.split(":"); final String[] n = keyword.split(":");
final Cost cost = new Cost(n[2], false); final Cost cost = new Cost(n[2], false);
sb.append("Splice onto ").append(n[1]).append(" ").append(cost.toSimpleString()); sb.append("Splice onto ").append(n[1]).append(" ").append(cost.toSimpleString());
sb.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")").append("\r\n"); sb.append(" (" + inst.getReminderText() + ")").append("\r\n");
} else if (keyword.startsWith("Entwine")) { } else if (keyword.startsWith("Entwine")) {
final String[] n = keyword.split(":"); final String[] n = keyword.split(":");
final Cost cost = new Cost(n[1], false); final Cost cost = new Cost(n[1], false);
@@ -2009,7 +2013,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final String[] n = keyword.split(":"); final String[] n = keyword.split(":");
final Cost cost = new Cost(n[1], false); final Cost cost = new Cost(n[1], false);
sb.append("Multikicker ").append(cost.toSimpleString()); sb.append("Multikicker ").append(cost.toSimpleString());
sb.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")").append("\r\n"); sb.append(" (" + inst.getReminderText() + ")").append("\r\n");
} }
} else if (keyword.startsWith("Kicker")) { } else if (keyword.startsWith("Kicker")) {
if (!keyword.endsWith("Generic")) { if (!keyword.endsWith("Generic")) {
@@ -2023,7 +2027,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final Cost cost2 = new Cost(n[2], false); final Cost cost2 = new Cost(n[2], false);
sbx.append(cost2.toSimpleString()); sbx.append(cost2.toSimpleString());
} }
sbx.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); sbx.append(" (" + inst.getReminderText() + ")");
sb.append(sbx).append("\r\n"); sb.append(sbx).append("\r\n");
} }
} else if (keyword.startsWith("AlternateAdditionalCost")) { } else if (keyword.startsWith("AlternateAdditionalCost")) {
@@ -2098,9 +2102,9 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
sb.append("Remove CARDNAME from your deck before playing if you're not playing for ante.\r\n"); sb.append("Remove CARDNAME from your deck before playing if you're not playing for ante.\r\n");
} else if (keyword.equals("Retrace") || keyword.equals("Changeling")) { } else if (keyword.equals("Retrace") || keyword.equals("Changeling")) {
sb.append(keyword + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); sb.append(keyword + " (" + inst.getReminderText() + ")");
} else if (keyword.startsWith("Presence")) { } else if (keyword.startsWith("Presence")) {
sb.append(Keyword.getInstance(keyword).getReminderText()); sb.append(inst.getReminderText());
} }
} }
return sb; return sb;
@@ -2569,10 +2573,13 @@ public class Card extends GameEntity implements Comparable<Card> {
getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped."); getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped.");
return; return;
} }
if (hasStartOfKeyword("CantEquip")) {
final int keywordPosition = getKeywordPosition("CantEquip"); for(KeywordInterface inst : c.getKeywords()) {
final String parse = getKeywords().get(keywordPosition); String kw = inst.getOriginal();
final String[] k = parse.split(" ", 2); if (!kw.startsWith("CantEquip")) {
continue;
}
final String[] k = kw.split(" ", 2);
final String[] restrictions = k[1].split(","); final String[] restrictions = k[1].split(",");
if (c.isValid(restrictions, getController(), this, null)) { if (c.isValid(restrictions, getController(), this, null)) {
getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped."); getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped.");
@@ -3224,17 +3231,17 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// keywords are like flying, fear, first strike, etc... // keywords are like flying, fear, first strike, etc...
public final List<String> getKeywords() { public final List<KeywordInterface> getKeywords() {
return getKeywords(currentState); return getKeywords(currentState);
} }
public final List<String> getKeywords(CardState state) { public final List<KeywordInterface> getKeywords(CardState state) {
ListKeywordVisitor visitor = new ListKeywordVisitor(); ListKeywordVisitor visitor = new ListKeywordVisitor();
visitKeywords(state, visitor); visitKeywords(state, visitor);
return visitor.getKeywords(); return visitor.getKeywords();
} }
// Allows traversing the card's keywords without needing to concat a bunch // Allows traversing the card's keywords without needing to concat a bunch
// of lists. Optimizes common operations such as hasKeyword(). // of lists. Optimizes common operations such as hasKeyword().
public final void visitKeywords(CardState state, Visitor<String> visitor) { public final void visitKeywords(CardState state, Visitor<KeywordInterface> visitor) {
visitUnhiddenKeywords(state, visitor); visitUnhiddenKeywords(state, visitor);
visitHiddenExtreinsicKeywords(visitor); visitHiddenExtreinsicKeywords(visitor);
} }
@@ -3263,20 +3270,39 @@ public class Card extends GameEntity implements Comparable<Card> {
addChangedCardKeywords(keywords, removeKeywords, removeAllKeywords, timestamp, true); addChangedCardKeywords(keywords, removeKeywords, removeAllKeywords, timestamp, true);
} }
public final void addChangedCardKeywords(final List<String> keywords, final List<String> removeKeywords, public final void addChangedCardKeywords(final List<String> keywords, final List<String> removeKeywords,
final boolean removeAllKeywords, final long timestamp, final boolean updateView) { final boolean removeAllKeywords, final long timestamp, final boolean updateView) {
keywords.removeAll(getCantHaveOrGainKeyword()); keywords.removeAll(getCantHaveOrGainKeyword());
// if the key already exists - merge entries // if the key already exists - merge entries
final KeywordsChange cks = changedCardKeywords.get(timestamp); final KeywordsChange cks = changedCardKeywords.get(timestamp);
if (cks != null) { if (cks != null) {
cks.removeKeywords(this); cks.removeKeywords(this);
List<String> kws = Lists.newArrayList(keywords); final KeywordsChange newCks = cks.merge(keywords, removeKeywords, removeAllKeywords);
List<String> rkws = Lists.newArrayList(removeKeywords); newCks.addKeywordsToCard(this);
boolean remAll = removeAllKeywords; changedCardKeywords.put(timestamp, newCks);
kws.addAll(cks.getKeywords()); }
rkws.addAll(cks.getRemoveKeywords()); else {
remAll |= cks.isRemoveAllKeywords(); final KeywordsChange newCks = new KeywordsChange(keywords, removeKeywords, removeAllKeywords);
final KeywordsChange newCks = new KeywordsChange(kws, rkws, remAll); newCks.addKeywordsToCard(this);
changedCardKeywords.put(timestamp, newCks);
}
if (updateView) {
updateKeywords();
}
}
public final void addChangedCardKeywordsInternal(final List<KeywordInterface> keywords, final List<KeywordInterface> removeKeywords,
final boolean removeAllKeywords, final long timestamp, final boolean updateView) {
KeywordCollection list = new KeywordCollection();
list.insertAll(keywords);
list.removeAll(getCantHaveOrGainKeyword());
// if the key already exists - merge entries
final KeywordsChange cks = changedCardKeywords.get(timestamp);
if (cks != null) {
cks.removeKeywords(this);
final KeywordsChange newCks = cks.merge(keywords, removeKeywords, removeAllKeywords);
newCks.addKeywordsToCard(this); newCks.addKeywordsToCard(this);
changedCardKeywords.put(timestamp, newCks); changedCardKeywords.put(timestamp, newCks);
} }
@@ -3322,13 +3348,16 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// Hidden keywords will be left out // Hidden keywords will be left out
public final List<String> getUnhiddenKeywords() { public final Collection<KeywordInterface> getUnhiddenKeywords() {
return getUnhiddenKeywords(currentState); return getUnhiddenKeywords(currentState);
} }
public final List<String> getUnhiddenKeywords(CardState state) { public final Collection<KeywordInterface> getUnhiddenKeywords(CardState state) {
final List<String> keywords = Lists.newArrayList(); KeywordCollection keywords = new KeywordCollection();
Iterables.addAll(keywords, state.getIntrinsicKeywords());
keywords.addAll(extrinsicKeyword); //final List<KeywordInterface> keywords = Lists.newArrayList();
keywords.insertAll(state.getIntrinsicKeywords());
keywords.insertAll(extrinsicKeyword.getValues());
// see if keyword changes are in effect // see if keyword changes are in effect
for (final KeywordsChange ck : changedCardKeywords.values()) { for (final KeywordsChange ck : changedCardKeywords.values()) {
@@ -3340,22 +3369,22 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
if (ck.getKeywords() != null) { if (ck.getKeywords() != null) {
keywords.addAll(ck.getKeywords()); keywords.insertAll(ck.getKeywords());
} }
} }
return keywords; return keywords.getValues();
} }
private void visitUnhiddenKeywords(CardState state, Visitor<String> visitor) { private void visitUnhiddenKeywords(CardState state, Visitor<KeywordInterface> visitor) {
if (changedCardKeywords.isEmpty()) { if (changedCardKeywords.isEmpty()) {
// Fast path that doesn't involve temp allocations. // Fast path that doesn't involve temp allocations.
for (String kw : state.getIntrinsicKeywords()) { for (KeywordInterface kw : state.getIntrinsicKeywords()) {
visitor.visit(kw); visitor.visit(kw);
} }
for (String kw : extrinsicKeyword) { for (KeywordInterface kw : extrinsicKeyword.getValues()) {
visitor.visit(kw); visitor.visit(kw);
} }
} else { } else {
for (String kw : getUnhiddenKeywords()) { for (KeywordInterface kw : getUnhiddenKeywords()) {
visitor.visit(kw); visitor.visit(kw);
} }
} }
@@ -3418,18 +3447,20 @@ public class Card extends GameEntity implements Comparable<Card> {
return; return;
} }
final List<String> addKeywords = Lists.newArrayList(); final List<KeywordInterface> addKeywords = Lists.newArrayList();
final List<String> removeKeywords = Lists.newArrayList(keywordsGrantedByTextChanges); final List<KeywordInterface> removeKeywords = Lists.newArrayList(keywordsGrantedByTextChanges);
for (final String kw : currentState.getIntrinsicKeywords()) { for (final KeywordInterface kw : currentState.getIntrinsicKeywords()) {
final String newKw = AbilityUtils.applyKeywordTextChangeEffects(kw, this); String oldtxt = kw.getOriginal();
if (!newKw.equals(kw)) { final String newtxt = AbilityUtils.applyKeywordTextChangeEffects(oldtxt, this);
if (!newtxt.equals(oldtxt)) {
KeywordInterface newKw = Keyword.getInstance(newtxt);
addKeywords.add(newKw); addKeywords.add(newKw);
removeKeywords.add(kw); removeKeywords.add(kw);
keywordsGrantedByTextChanges.add(newKw); keywordsGrantedByTextChanges.add(newKw);
} }
} }
addChangedCardKeywords(addKeywords, removeKeywords, false, timestamp); addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, timestamp, true);
} }
private void updateKeywordsOnRemoveChangedText(final KeywordsChange k) { private void updateKeywordsOnRemoveChangedText(final KeywordsChange k) {
@@ -3497,10 +3528,12 @@ public class Card extends GameEntity implements Comparable<Card> {
originalSVars.clear(); originalSVars.clear();
} }
public final void addIntrinsicKeyword(final String s) { public final KeywordInterface addIntrinsicKeyword(final String s) {
if (currentState.addIntrinsicKeyword(s)) { KeywordInterface inst = currentState.addIntrinsicKeyword(s);
if (inst != null) {
currentState.getView().updateKeywords(this, currentState); currentState.getView().updateKeywords(this, currentState);
} }
return inst;
} }
public final void addIntrinsicKeywords(final Iterable<String> s) { public final void addIntrinsicKeywords(final Iterable<String> s) {
@@ -3515,11 +3548,16 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
public List<String> getExtrinsicKeyword() { public Collection<KeywordInterface> getExtrinsicKeyword() {
return extrinsicKeyword; return extrinsicKeyword.getValues();
} }
public final void setExtrinsicKeyword(final List<String> a) { public final void setExtrinsicKeyword(final List<String> a) {
extrinsicKeyword = Lists.newArrayList(a); extrinsicKeyword.clear();
extrinsicKeyword.addAll(a);
}
public void setExtrinsicKeyword(Collection<KeywordInterface> extrinsicKeyword2) {
extrinsicKeyword.clear();
extrinsicKeyword.insertAll(extrinsicKeyword2);
} }
public void addExtrinsicKeyword(final String s) { public void addExtrinsicKeyword(final String s) {
@@ -3560,31 +3598,38 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// Hidden Keywords will be returned without the indicator HIDDEN // Hidden Keywords will be returned without the indicator HIDDEN
public final List<String> getHiddenExtrinsicKeywords() { public final List<KeywordInterface> getHiddenExtrinsicKeywords() {
ListKeywordVisitor visitor = new ListKeywordVisitor(); ListKeywordVisitor visitor = new ListKeywordVisitor();
visitHiddenExtreinsicKeywords(visitor); visitHiddenExtreinsicKeywords(visitor);
return visitor.getKeywords(); return visitor.getKeywords();
} }
private void visitHiddenExtreinsicKeywords(Visitor<String> visitor) { private void visitHiddenExtreinsicKeywords(Visitor<KeywordInterface> visitor) {
for (String keyword : hiddenExtrinsicKeyword) { for (KeywordInterface inst : hiddenExtrinsicKeyword.getValues()) {
if (keyword == null) { visitor.visit(inst);
continue;
}
if (keyword.startsWith("HIDDEN")) {
keyword = keyword.substring(7);
}
visitor.visit(keyword);
} }
} }
public final void addHiddenExtrinsicKeyword(final String s) { public final void addHiddenExtrinsicKeyword(String s) {
if (hiddenExtrinsicKeyword.add(s)) { if (s.startsWith("HIDDEN")) {
s = s.substring(7);
}
if (hiddenExtrinsicKeyword.add(s) != null) {
view.updateNonAbilityText(this); view.updateNonAbilityText(this);
currentState.getView().updateKeywords(this, currentState); currentState.getView().updateKeywords(this, currentState);
} }
} }
public final void removeHiddenExtrinsicKeyword(final String s) { public final void addHiddenExtrinsicKeyword(KeywordInterface k) {
if (hiddenExtrinsicKeyword.insert(k)) {
view.updateNonAbilityText(this);
currentState.getView().updateKeywords(this, currentState);
}
}
public final void removeHiddenExtrinsicKeyword(String s) {
if (s.startsWith("HIDDEN")) {
s = s.substring(7);
}
if (hiddenExtrinsicKeyword.remove(s)) { if (hiddenExtrinsicKeyword.remove(s)) {
view.updateNonAbilityText(this); view.updateNonAbilityText(this);
currentState.getView().updateKeywords(this, currentState); currentState.getView().updateKeywords(this, currentState);
@@ -3817,19 +3862,6 @@ public class Card extends GameEntity implements Comparable<Card> {
return visitor.getCount() > 0; return visitor.getCount() > 0;
} }
public final int getKeywordPosition(String k) {
return getKeywordPosition(k, currentState);
}
public final int getKeywordPosition(String k, CardState state) {
final List<String> a = getKeywords(state);
for (int i = 0; i < a.size(); i++) {
if (a.get(i).startsWith(k)) {
return i;
}
}
return -1;
}
public final boolean hasAnyKeyword(final Iterable<String> keywords) { public final boolean hasAnyKeyword(final Iterable<String> keywords) {
return hasAnyKeyword(keywords, currentState); return hasAnyKeyword(keywords, currentState);
} }
@@ -3859,7 +3891,8 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
public final int getKeywordMagnitude(final String k, CardState state) { public final int getKeywordMagnitude(final String k, CardState state) {
int count = 0; int count = 0;
for (final String kw : getKeywords(state)) { for (final KeywordInterface inst : getKeywords(state)) {
String kw = inst.getOriginal();
if (kw.startsWith(k)) { if (kw.startsWith(k)) {
final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" "); final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" ");
final String s = parse[1]; final String s = parse[1];
@@ -4331,7 +4364,8 @@ public class Card extends GameEntity implements Comparable<Card> {
return 0; return 0;
} }
for (String kw : getKeywords()) { for (KeywordInterface inst : getKeywords()) {
String kw = inst.getOriginal();
if (kw.startsWith("PreventAllDamageBy")) { if (kw.startsWith("PreventAllDamageBy")) {
if (source.isValid(kw.split(" ", 2)[1].split(","), getController(), this, null)) { if (source.isValid(kw.split(" ", 2)[1].split(","), getController(), this, null)) {
return 0; return 0;
@@ -4837,93 +4871,91 @@ public class Card extends GameEntity implements Comparable<Card> {
return true; return true;
} }
final List<String> keywords = getKeywords(); final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
if (keywords != null) {
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
for (final String kw : keywords) { for (final KeywordInterface inst : getKeywords()) {
if (!kw.startsWith("Protection")) { String kw = inst.getOriginal();
continue; if (!kw.startsWith("Protection")) {
} continue;
if (kw.equals("Protection from white")) { }
if (source.isWhite() && !colorlessDamage) { if (kw.equals("Protection from white")) {
return true; if (source.isWhite() && !colorlessDamage) {
}
} else if (kw.equals("Protection from blue")) {
if (source.isBlue() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from black")) {
if (source.isBlack() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from red")) {
if (source.isRed() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from green")) {
if (source.isGreen() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from monocolored")) {
if (CardUtil.getColors(source).isMonoColor() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from multicolored")) {
if (CardUtil.getColors(source).isMulticolor() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from all colors")) {
if (!source.isColorless() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from creatures")) {
if (source.isCreature()) {
return true;
}
} else if (kw.equals("Protection from artifacts")) {
if (source.isArtifact()) {
return true;
}
} else if (kw.equals("Protection from enchantments")) {
if (source.isEnchantment()) {
return true;
}
} else if (kw.equals("Protection from everything")) {
return true; return true;
} else if (kw.startsWith("Protection:")) { // uses isValid; Protection:characteristic:desc:exception }
final String[] kws = kw.split(":"); } else if (kw.equals("Protection from blue")) {
String characteristic = kws[1]; if (source.isBlue() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from black")) {
if (source.isBlack() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from red")) {
if (source.isRed() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from green")) {
if (source.isGreen() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from monocolored")) {
if (CardUtil.getColors(source).isMonoColor() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from multicolored")) {
if (CardUtil.getColors(source).isMulticolor() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from all colors")) {
if (!source.isColorless() && !colorlessDamage) {
return true;
}
} else if (kw.equals("Protection from creatures")) {
if (source.isCreature()) {
return true;
}
} else if (kw.equals("Protection from artifacts")) {
if (source.isArtifact()) {
return true;
}
} else if (kw.equals("Protection from enchantments")) {
if (source.isEnchantment()) {
return true;
}
} else if (kw.equals("Protection from everything")) {
return true;
} else if (kw.startsWith("Protection:")) { // uses isValid; Protection:characteristic:desc:exception
final String[] kws = kw.split(":");
String characteristic = kws[1];
// if colorlessDamage then it does only check damage color.. // if colorlessDamage then it does only check damage color..
if (colorlessDamage) { if (colorlessDamage) {
if (characteristic.endsWith("White") || characteristic.endsWith("Blue") if (characteristic.endsWith("White") || characteristic.endsWith("Blue")
|| characteristic.endsWith("Black") || characteristic.endsWith("Red") || characteristic.endsWith("Black") || characteristic.endsWith("Red")
|| characteristic.endsWith("Green") || characteristic.endsWith("Colorless") || characteristic.endsWith("Green") || characteristic.endsWith("Colorless")
|| characteristic.endsWith("ChosenColor")) { || characteristic.endsWith("ChosenColor")) {
characteristic += "Source"; characteristic += "Source";
}
} }
}
final String[] characteristics = characteristic.split(","); final String[] characteristics = characteristic.split(",");
final String exception = kws.length > 3 ? kws[3] : null; // check "This effect cannot remove sth" final String exception = kws.length > 3 ? kws[3] : null; // check "This effect cannot remove sth"
if (source.isValid(characteristics, getController(), this, null) if (source.isValid(characteristics, getController(), this, null)
&& (!checkSBA || exception == null || !source.isValid(exception, getController(), this, null))) { && (!checkSBA || exception == null || !source.isValid(exception, getController(), this, null))) {
return true; return true;
} }
} else if (kw.equals("Protection from colored spells")) { } else if (kw.equals("Protection from colored spells")) {
if (source.isSpell() && !source.isColorless()) { if (source.isSpell() && !source.isColorless()) {
return true; return true;
} }
} else if (kw.equals("Protection from the chosen player")) { } else if (kw.equals("Protection from the chosen player")) {
if (source.getController().equals(chosenPlayer)) { if (source.getController().equals(chosenPlayer)) {
return true; return true;
} }
} else if (kw.startsWith("Protection from ")) { } else if (kw.startsWith("Protection from ")) {
final String protectType = CardType.getSingularType(kw.substring("Protection from ".length())); final String protectType = CardType.getSingularType(kw.substring("Protection from ".length()));
if (source.getType().hasStringType(protectType)) { if (source.getType().hasStringType(protectType)) {
return true; return true;
}
} }
} }
} }
@@ -4983,13 +5015,13 @@ public class Card extends GameEntity implements Comparable<Card> {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final MutableBoolean result = new MutableBoolean(true); final MutableBoolean result = new MutableBoolean(true);
visitKeywords(currentState, new Visitor<String>() { visitKeywords(currentState, new Visitor<KeywordInterface>() {
@Override @Override
public void visit(String kw) { public void visit(KeywordInterface kw) {
if (result.isFalse()) { if (result.isFalse()) {
return; return;
} }
switch (kw) { switch (kw.getOriginal()) {
case "Shroud": case "Shroud":
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Can target CardUID_").append(String.valueOf(getId())); sb.append("Can target CardUID_").append(String.valueOf(getId()));
@@ -5031,13 +5063,17 @@ public class Card extends GameEntity implements Comparable<Card> {
if (result.isFalse()) { if (result.isFalse()) {
return false; return false;
} }
if (sa.isSpell() && source.hasStartOfKeyword("SpellCantTarget")) { if (sa.isSpell()) {
final int keywordPosition = source.getKeywordPosition("SpellCantTarget"); for(KeywordInterface inst : source.getKeywords()) {
final String parse = source.getKeywords().get(keywordPosition); String kw = inst.getOriginal();
final String[] k = parse.split(":"); if(!kw.startsWith("SpellCantTarget")) {
final String[] restrictions = k[1].split(","); continue;
if (isValid(restrictions, source.getController(), source, null)) { }
return false; final String[] k = kw.split(":");
final String[] restrictions = k[1].split(",");
if (isValid(restrictions, source.getController(), source, null)) {
return false;
}
} }
} }
return true; return true;
@@ -5066,10 +5102,12 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
public final boolean canBeEquippedBy(final Card equip) { public final boolean canBeEquippedBy(final Card equip) {
if (equip.hasStartOfKeyword("CantEquip")) { for(KeywordInterface inst : equip.getKeywords()) {
final int keywordPosition = equip.getKeywordPosition("CantEquip"); String kw = inst.getOriginal();
final String parse = equip.getKeywords().get(keywordPosition); if(!kw.startsWith("CantEquip")) {
final String[] k = parse.split(" ", 2); continue;
}
final String[] k = kw.split(":");
final String[] restrictions = k[1].split(","); final String[] restrictions = k[1].split(",");
if (isValid(restrictions, equip.getController(), equip, null)) { if (isValid(restrictions, equip.getController(), equip, null)) {
return false; return false;
@@ -5461,7 +5499,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// Counts number of instances of a given keyword. // Counts number of instances of a given keyword.
private static final class CountKeywordVisitor extends Visitor<String> { private static final class CountKeywordVisitor extends Visitor<KeywordInterface> {
private String keyword; private String keyword;
private int count; private int count;
private boolean startOf; private boolean startOf;
@@ -5478,7 +5516,8 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
@Override @Override
public void visit(String kw) { public void visit(KeywordInterface inst) {
final String kw = inst.getOriginal();
if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) { if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) {
count++; count++;
} }
@@ -5490,15 +5529,15 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// Collects all the keywords into a list. // Collects all the keywords into a list.
private static final class ListKeywordVisitor extends Visitor<String> { private static final class ListKeywordVisitor extends Visitor<KeywordInterface> {
private List<String> keywords = Lists.newArrayList(); private List<KeywordInterface> keywords = Lists.newArrayList();
@Override @Override
public void visit(String kw) { public void visit(KeywordInterface kw) {
keywords.add(kw); keywords.add(kw);
} }
public List<String> getKeywords() { public List<KeywordInterface> getKeywords() {
return keywords; return keywords;
} }
} }
@@ -5603,4 +5642,6 @@ public class Card extends GameEntity implements Comparable<Card> {
removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play
setSunburstValue(0); // Sunburst setSunburstValue(0); // Sunburst
} }
} }

View File

@@ -1861,7 +1861,8 @@ public class CardFactoryUtil {
final List<String> allkw = Lists.newArrayList(); final List<String> allkw = Lists.newArrayList();
for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) { for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) {
for (String k : c.getKeywords()) { for (KeywordInterface inst : c.getKeywords()) {
final String k = inst.getOriginal();
if (k.endsWith("walk")) { if (k.endsWith("walk")) {
if (!landkw.contains(k)) { if (!landkw.contains(k)) {
landkw.add(k); landkw.add(k);
@@ -1948,8 +1949,7 @@ public class CardFactoryUtil {
// Cards with Cycling abilities // Cards with Cycling abilities
// -1 means keyword "Cycling" not found // -1 means keyword "Cycling" not found
for (String keyword : card.getKeywords()) { for (KeywordInterface inst : card.getKeywords()) {
KeywordInterface inst = Keyword.getInstance(keyword);
inst.addKeywords(card, true); inst.addKeywords(card, true);
} }

View File

@@ -19,6 +19,7 @@ package forge.game.card;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.card.*; import forge.card.*;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
@@ -27,6 +28,7 @@ import forge.game.ForgeScript;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.card.CardView.CardStateView; import forge.game.card.CardView.CardStateView;
import forge.game.keyword.KeywordCollection; import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -35,6 +37,8 @@ import forge.game.trigger.Trigger;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
public class CardState extends GameObject { public class CardState extends GameObject {
@@ -146,8 +150,8 @@ public class CardState extends GameObject {
view.updateToughness(this); view.updateToughness(this);
} }
public final Iterable<String> getIntrinsicKeywords() { public final Collection<KeywordInterface> getIntrinsicKeywords() {
return intrinsicKeywords; return intrinsicKeywords.getValues();
} }
public final boolean hasIntrinsicKeyword(String k) { public final boolean hasIntrinsicKeyword(String k) {
return intrinsicKeywords.contains(k); return intrinsicKeywords.contains(k);
@@ -157,13 +161,17 @@ public class CardState extends GameObject {
intrinsicKeywords.addAll(intrinsicKeyword0); intrinsicKeywords.addAll(intrinsicKeyword0);
} }
public final boolean addIntrinsicKeyword(final String s) { public final KeywordInterface addIntrinsicKeyword(final String s) {
return s.trim().length() != 0 && intrinsicKeywords.add(s); if (s.trim().length() == 0) {
return null;
}
return intrinsicKeywords.add(s);
} }
public final boolean addIntrinsicKeywords(final Iterable<String> keywords) { public final boolean addIntrinsicKeywords(final Iterable<String> keywords) {
boolean changed = false; boolean changed = false;
for (String k : keywords) { for (String k : keywords) {
if (addIntrinsicKeyword(k)) { if (addIntrinsicKeyword(k) != null) {
changed = true; changed = true;
} }
} }
@@ -401,5 +409,13 @@ public class CardState extends GameObject {
return ForgeScript.cardStateHasProperty(this, property, sourceController, source, spellAbility); return ForgeScript.cardStateHasProperty(this, property, sourceController, source, spellAbility);
} }
public List<String> addIntrinsicKeywords(Collection<KeywordInterface> intrinsicKeywords2) {
List<String> names = Lists.newArrayList();
for (KeywordInterface inst : intrinsicKeywords2) {
names.add(inst.getOriginal());
}
return names;
}
} }

View File

@@ -9,6 +9,7 @@ import forge.card.CardType;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import java.util.List; import java.util.List;
@@ -39,7 +40,13 @@ public class TokenInfo {
this.imageName = ImageKeys.getTokenImageName(c.getImageKey()); this.imageName = ImageKeys.getTokenImageName(c.getImageKey());
this.manaCost = c.getManaCost().toString(); this.manaCost = c.getManaCost().toString();
this.types = getCardTypes(c); this.types = getCardTypes(c);
this.intrinsicKeywords = c.getKeywords().toArray(new String[0]);
List<String> list = Lists.newArrayList();
for (KeywordInterface inst : c.getKeywords()) {
list.add(inst.getOriginal());
}
this.intrinsicKeywords = list.toArray(new String[0]);
this.basePower = c.getBasePower(); this.basePower = c.getBasePower();
this.baseToughness = c.getBaseToughness(); this.baseToughness = c.getBaseToughness();
} }

View File

@@ -8,6 +8,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
@@ -49,7 +50,8 @@ public class AttackRequirement {
nAttackAnything += attacker.getGoaded().size(); nAttackAnything += attacker.getGoaded().size();
} }
for (final String keyword : attacker.getKeywords()) { for (final KeywordInterface inst : attacker.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1]; final String defined = keyword.split(":")[1];
final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0); final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);

View File

@@ -29,6 +29,7 @@ import forge.game.GameEntity;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.card.*; import forge.game.card.*;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.player.PlayerController.ManaPaymentPurpose;
@@ -201,8 +202,8 @@ public class CombatUtil {
// Keywords // Keywords
final boolean canAttackWithDefender = attacker.hasKeyword("CARDNAME can attack as though it didn't have defender."); final boolean canAttackWithDefender = attacker.hasKeyword("CARDNAME can attack as though it didn't have defender.");
for (final String keyword : attacker.getKeywords()) { for (final KeywordInterface keyword : attacker.getKeywords()) {
switch (keyword) { switch (keyword.getOriginal()) {
case "CARDNAME can't attack.": case "CARDNAME can't attack.":
case "CARDNAME can't attack or block.": case "CARDNAME can't attack or block.":
return false; return false;
@@ -543,7 +544,8 @@ public class CombatUtil {
} }
} }
for (final String keyword : attacker.getKeywords()) { for (final KeywordInterface inst : attacker.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.equals("Legendary landwalk")) { if (keyword.equals("Legendary landwalk")) {
walkTypes.add("Land.Legendary"); walkTypes.add("Land.Legendary");
} else if (keyword.equals("Desertwalk")) { } else if (keyword.equals("Desertwalk")) {
@@ -779,7 +781,8 @@ public class CombatUtil {
&& combat.getBlockers(attacker).isEmpty())) { && combat.getBlockers(attacker).isEmpty())) {
attackersWithLure.add(attacker); attackersWithLure.add(attacker);
} else { } else {
for (String keyword : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
String keyword = inst.getOriginal();
// MustBeBlockedBy <valid> // MustBeBlockedBy <valid>
if (keyword.startsWith("MustBeBlockedBy ")) { if (keyword.startsWith("MustBeBlockedBy ")) {
final String valid = keyword.substring("MustBeBlockedBy ".length()); final String valid = keyword.substring("MustBeBlockedBy ".length());
@@ -891,7 +894,8 @@ public class CombatUtil {
} }
boolean mustBeBlockedBy = false; boolean mustBeBlockedBy = false;
for (String keyword : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
String keyword = inst.getOriginal();
// MustBeBlockedBy <valid> // MustBeBlockedBy <valid>
if (keyword.startsWith("MustBeBlockedBy ")) { if (keyword.startsWith("MustBeBlockedBy ")) {
final String valid = keyword.substring("MustBeBlockedBy ".length()); final String valid = keyword.substring("MustBeBlockedBy ".length());
@@ -991,7 +995,8 @@ public class CombatUtil {
return false; return false;
} }
for (String k : attacker.getKeywords()) { for (KeywordInterface inst1 : attacker.getKeywords()) {
String k = inst1.getOriginal();
if (k.startsWith("CantBeBlockedBy ")) { if (k.startsWith("CantBeBlockedBy ")) {
final String[] n = k.split(" ", 2); final String[] n = k.split(" ", 2);
final String[] restrictions = n[1].split(","); final String[] restrictions = n[1].split(",");
@@ -999,7 +1004,8 @@ public class CombatUtil {
boolean stillblock = false; boolean stillblock = false;
//Dragon Hunter check //Dragon Hunter check
if (n[1].contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) { if (n[1].contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) {
for (String k2 : blocker.getKeywords()) { for (KeywordInterface inst2 : blocker.getKeywords()) {
String k2 = inst2.getOriginal();
if (k2.startsWith("IfReach")) { if (k2.startsWith("IfReach")) {
String n2[] = k2.split(":"); String n2[] = k2.split(":");
if (attacker.getType().hasCreatureType(n2[1])) { if (attacker.getType().hasCreatureType(n2[1])) {
@@ -1015,7 +1021,8 @@ public class CombatUtil {
} }
} }
} }
for (String keyword : blocker.getKeywords()) { for (KeywordInterface inst : blocker.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.startsWith("CantBlockCardUID")) { if (keyword.startsWith("CantBlockCardUID")) {
final String[] k = keyword.split("_", 2); final String[] k = keyword.split("_", 2);
if (attacker.getId() == Integer.parseInt(k[1])) { if (attacker.getId() == Integer.parseInt(k[1])) {
@@ -1037,7 +1044,8 @@ public class CombatUtil {
if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) { if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) {
boolean stillblock = false; boolean stillblock = false;
for (String k : blocker.getKeywords()) { for (KeywordInterface inst : blocker.getKeywords()) {
String k = inst.getOriginal();
if (k.startsWith("IfReach")) { if (k.startsWith("IfReach")) {
String n[] = k.split(":"); String n[] = k.split(":");
if (attacker.getType().hasCreatureType(n[1])) { if (attacker.getType().hasCreatureType(n[1])) {
@@ -1071,7 +1079,8 @@ public class CombatUtil {
return false; // no block return false; // no block
List<String> restrictions = Lists.newArrayList(); List<String> restrictions = Lists.newArrayList();
for (String kw : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
String kw = inst.getOriginal();
if (kw.startsWith("CantBeBlockedByAmount")) { if (kw.startsWith("CantBeBlockedByAmount")) {
restrictions.add(TextUtil.split(kw, ' ', 2)[1]); restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
} }
@@ -1108,7 +1117,8 @@ public class CombatUtil {
public static int getMinNumBlockersForAttacker(Card attacker, Player defender) { public static int getMinNumBlockersForAttacker(Card attacker, Player defender) {
List<String> restrictions = Lists.newArrayList(); List<String> restrictions = Lists.newArrayList();
for (String kw : attacker.getKeywords()) { for (KeywordInterface inst : attacker.getKeywords()) {
String kw = inst.getOriginal();
if (kw.startsWith("CantBeBlockedByAmount")) { if (kw.startsWith("CantBeBlockedByAmount")) {
restrictions.add(TextUtil.split(kw, ' ', 2)[1]); restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
} }

View File

@@ -10,6 +10,7 @@ import forge.game.Game;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.*; import forge.game.card.*;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilityActivated; import forge.game.spellability.AbilityActivated;
@@ -266,7 +267,8 @@ public class CostAdjustment {
private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) { private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) {
String offeringType = ""; String offeringType = "";
for (String kw : sa.getHostCard().getKeywords()) { for (KeywordInterface inst : sa.getHostCard().getKeywords()) {
final String kw = inst.getOriginal();
if (kw.endsWith(" offering")) { if (kw.endsWith(" offering")) {
offeringType = kw.split(" ")[0]; offeringType = kw.split(" ")[0];
break; break;

View File

@@ -17,7 +17,7 @@ public enum Keyword {
AMPLIFY(KeywordWithAmountAndType.class, false, "As this creature enters the battlefield, put {%d:+1/+1 counter} on it for each %s card you reveal in your hand."), AMPLIFY(KeywordWithAmountAndType.class, false, "As this creature enters the battlefield, put {%d:+1/+1 counter} on it for each %s card you reveal in your hand."),
ANNIHILATOR(KeywordWithAmount.class, false, "Whenever this creature attacks, defending player sacrifices {%d:permanent}."), ANNIHILATOR(KeywordWithAmount.class, false, "Whenever this creature attacks, defending player sacrifices {%d:permanent}."),
AURA_SWAP(KeywordWithCost.class, false, "%s: You may exchange this Aura with an Aura card in your hand."), AURA_SWAP(KeywordWithCost.class, false, "%s: You may exchange this Aura with an Aura card in your hand."),
AWAKEN(KeywordWithCostAndAmount.class, true, "If you cast this spell for %s, also put {%d:+1/+1 counter} on target land you control and it becomes a 0/0 Elemental creature with haste. It's still a land."), AWAKEN(KeywordWithCostAndAmount.class, false, "If you cast this spell for %s, also put {%d:+1/+1 counter} on target land you control and it becomes a 0/0 Elemental creature with haste. It's still a land."),
BANDING(SimpleKeyword.class, true, "Any creatures with banding, and up to one without, can attack in a band. Bands are blocked as a group. If any creatures with banding you control are blocking or being blocked by a creature, you divide that creature's combat damage, not its controller, among any of the creatures it's being blocked by or is blocking."), BANDING(SimpleKeyword.class, true, "Any creatures with banding, and up to one without, can attack in a band. Bands are blocked as a group. If any creatures with banding you control are blocking or being blocked by a creature, you divide that creature's combat damage, not its controller, among any of the creatures it's being blocked by or is blocking."),
BATTLE_CRY(SimpleKeyword.class, false, "Whenever this creature attacks, each other attacking creature gets +1/+0 until end of turn."), BATTLE_CRY(SimpleKeyword.class, false, "Whenever this creature attacks, each other attacking creature gets +1/+0 until end of turn."),
BESTOW(KeywordWithCost.class, true, "If you cast this card for its bestow cost, it's an Aura spell with enchant creature. It becomes a creature again if it's not attached to a creature."), BESTOW(KeywordWithCost.class, true, "If you cast this card for its bestow cost, it's an Aura spell with enchant creature. It becomes a creature again if it's not attached to a creature."),
@@ -30,7 +30,7 @@ public enum Keyword {
CIPHER(SimpleKeyword.class, true, "Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost."), CIPHER(SimpleKeyword.class, true, "Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost."),
CONSPIRE(SimpleKeyword.class, false, "As an additional cost to cast this spell, you may tap two untapped creatures you control that each share a color with it. If you do, copy it."), CONSPIRE(SimpleKeyword.class, false, "As an additional cost to cast this spell, you may tap two untapped creatures you control that each share a color with it. If you do, copy it."),
CONVOKE(SimpleKeyword.class, true, "Your creatures can help cast this spell. Each creature you tap while playing this spell reduces its cost by {1} or by one mana of that creature's color."), CONVOKE(SimpleKeyword.class, true, "Your creatures can help cast this spell. Each creature you tap while playing this spell reduces its cost by {1} or by one mana of that creature's color."),
CREW(KeywordWithAmount.class, true, "Tap any number of creatures you control with total power %1$d or more: This Vehicle becomes an artifact creature until end of turn."), CREW(KeywordWithAmount.class, false, "Tap any number of creatures you control with total power %1$d or more: This Vehicle becomes an artifact creature until end of turn."),
CUMULATIVE_UPKEEP(KeywordWithCost.class, false, "At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it."), CUMULATIVE_UPKEEP(KeywordWithCost.class, false, "At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it."),
CYCLING(KeywordWithCost.class, false, "%s, Discard this card: Draw a card."), //Typecycling reminder text handled by Cycling class CYCLING(KeywordWithCost.class, false, "%s, Discard this card: Draw a card."), //Typecycling reminder text handled by Cycling class
DASH(KeywordWithCost.class, true, "You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step."), DASH(KeywordWithCost.class, true, "You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step."),
@@ -38,7 +38,7 @@ public enum Keyword {
DEFENDER(SimpleKeyword.class, true, "This creature can't attack."), DEFENDER(SimpleKeyword.class, true, "This creature can't attack."),
DELVE(SimpleKeyword.class, true, "As an additional cost to cast this spell, you may exile any number of cards from your graveyard. Each card exiled this way reduces the cost to cast this spell by {1}."), DELVE(SimpleKeyword.class, true, "As an additional cost to cast this spell, you may exile any number of cards from your graveyard. Each card exiled this way reduces the cost to cast this spell by {1}."),
DETHRONE(SimpleKeyword.class, false, "Whenever this creature attacks the player with the most life or tied for the most life, put a +1/+1 counter on it."), DETHRONE(SimpleKeyword.class, false, "Whenever this creature attacks the player with the most life or tied for the most life, put a +1/+1 counter on it."),
DEVOUR(KeywordWithAmount.class, true, "As this creature enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with {%d:+1/+1 counter} on it for each creature sacrificed this way."), DEVOUR(KeywordWithAmount.class, false, "As this creature enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with {%d:+1/+1 counter} on it for each creature sacrificed this way."),
DEVOID(SimpleKeyword.class, true, "This card has no color."), DEVOID(SimpleKeyword.class, true, "This card has no color."),
DOUBLE_STRIKE(SimpleKeyword.class, true, "This creature deals both first-strike and regular combat damage."), DOUBLE_STRIKE(SimpleKeyword.class, true, "This creature deals both first-strike and regular combat damage."),
DREDGE(KeywordWithAmount.class, true, "If you would draw a card, instead you may put exactly {%d:card} from the top of your library into your graveyard. If you do, return this card from your graveyard to your hand. Otherwise, draw a card."), DREDGE(KeywordWithAmount.class, true, "If you would draw a card, instead you may put exactly {%d:card} from the top of your library into your graveyard. If you do, return this card from your graveyard to your hand. Otherwise, draw a card."),
@@ -86,13 +86,13 @@ public enum Keyword {
LIFELINK(SimpleKeyword.class, true, "Damage dealt by this creature also causes its controller to gain that much life."), LIFELINK(SimpleKeyword.class, true, "Damage dealt by this creature also causes its controller to gain that much life."),
LIVING_WEAPON(SimpleKeyword.class, true, "When this Equipment enters the battlefield, create a 0/0 black Germ creature token, then attach this Equipment to it."), LIVING_WEAPON(SimpleKeyword.class, true, "When this Equipment enters the battlefield, create a 0/0 black Germ creature token, then attach this Equipment to it."),
MADNESS(KeywordWithCost.class, true, "If you discard this card, discard it into exile. When you do, cast it for %s or put it into your graveyard."), MADNESS(KeywordWithCost.class, true, "If you discard this card, discard it into exile. When you do, cast it for %s or put it into your graveyard."),
MELEE(SimpleKeyword.class, true, "Whenever this creature attacks, it gets +1/+1 until end of turn for each opponent you attacked with a creature this combat."), MELEE(SimpleKeyword.class, false, "Whenever this creature attacks, it gets +1/+1 until end of turn for each opponent you attacked with a creature this combat."),
MENACE(SimpleKeyword.class, true, "This creature can't be blocked except by two or more creatures."), MENACE(SimpleKeyword.class, true, "This creature can't be blocked except by two or more creatures."),
MEGAMORPH(KeywordWithCost.class, true, "You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its megamorph cost and put a +1/+1 counter on it."), MEGAMORPH(KeywordWithCost.class, false, "You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its megamorph cost and put a +1/+1 counter on it."),
MIRACLE(KeywordWithCost.class, false, "You may cast this card for its miracle cost when you draw it if it's the first card you drew this turn."), MIRACLE(KeywordWithCost.class, false, "You may cast this card for its miracle cost when you draw it if it's the first card you drew this turn."),
MONSTROSITY(KeywordWithCostAndAmount.class, false, "If this creature isn't monstrous, put {%2$d:+1/+1 counter} on it and it becomes monstrous."), MONSTROSITY(KeywordWithCostAndAmount.class, false, "If this creature isn't monstrous, put {%2$d:+1/+1 counter} on it and it becomes monstrous."),
MODULAR(Modular.class, false, "This creature enters the battlefield with {%d:+1/+1 counter} on it. When it dies, you may put its +1/+1 counters on target artifact creature."), MODULAR(Modular.class, false, "This creature enters the battlefield with {%d:+1/+1 counter} on it. When it dies, you may put its +1/+1 counters on target artifact creature."),
MORPH(KeywordWithCost.class, true, "You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost."), MORPH(KeywordWithCost.class, false, "You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost."),
MULTIKICKER(KeywordWithCost.class, false, "You may pay an additional %s any number of times as you cast this spell."), MULTIKICKER(KeywordWithCost.class, false, "You may pay an additional %s any number of times as you cast this spell."),
MYRIAD(SimpleKeyword.class, false, "Whenever this creature attacks, for each opponent other than defending player, you may create a token that's a copy of this creature that's tapped and attacking that player or a planeswalker he or she controls. If one or more tokens are created this way, exile the tokens at end of combat."), MYRIAD(SimpleKeyword.class, false, "Whenever this creature attacks, for each opponent other than defending player, you may create a token that's a copy of this creature that's tapped and attacking that player or a planeswalker he or she controls. If one or more tokens are created this way, exile the tokens at end of combat."),
NINJUTSU(KeywordWithCost.class, false, "%s, Return an unblocked attacker you control to hand: Put this card onto the battlefield from your hand tapped and attacking."), NINJUTSU(KeywordWithCost.class, false, "%s, Return an unblocked attacker you control to hand: Put this card onto the battlefield from your hand tapped and attacking."),
@@ -228,9 +228,8 @@ public enum Keyword {
Set<Keyword> keywordSet = cardKeywordSetLookup.get(key); Set<Keyword> keywordSet = cardKeywordSetLookup.get(key);
if (keywordSet == null) { if (keywordSet == null) {
keywordSet = new HashSet<Keyword>(); keywordSet = new HashSet<Keyword>();
List<String> keywords = Card.getCardForUi(card).getKeywords(); for (KeywordInterface inst : Card.getCardForUi(card).getKeywords()) {
for (String k : keywords) { final Keyword keyword = inst.getKeyword();
Keyword keyword = Keyword.getInstance(k).getKeyword();
if (keyword != Keyword.UNDEFINED) { if (keyword != Keyword.UNDEFINED) {
keywordSet.add(keyword); keywordSet.add(keyword);
} }

View File

@@ -11,10 +11,21 @@ import com.google.common.collect.MultimapBuilder;
public class KeywordCollection implements Iterable<String>, Serializable { public class KeywordCollection implements Iterable<String>, Serializable {
private static final long serialVersionUID = -2882986558147844702L; private static final long serialVersionUID = -2882986558147844702L;
private boolean hidden = false;
private transient KeywordCollectionView view; private transient KeywordCollectionView view;
private final Multimap<Keyword, KeywordInterface> map = MultimapBuilder.enumKeys(Keyword.class) private final Multimap<Keyword, KeywordInterface> map = MultimapBuilder.enumKeys(Keyword.class)
.arrayListValues().build(); .arrayListValues().build();
public KeywordCollection() {
super();
this.hidden = false;
}
public KeywordCollection(boolean hidden) {
super();
this.hidden = hidden;
}
public boolean contains(Keyword keyword) { public boolean contains(Keyword keyword) {
return map.containsKey(keyword); return map.containsKey(keyword);
} }
@@ -35,8 +46,15 @@ public class KeywordCollection implements Iterable<String>, Serializable {
return amount; return amount;
} }
public boolean add(String k) { public KeywordInterface add(String k) {
KeywordInterface inst = Keyword.getInstance(k); KeywordInterface inst = Keyword.getInstance(k);
inst.setHidden(hidden);
if (insert(inst)) {
return inst;
}
return null;
}
public boolean insert(KeywordInterface inst) {
Keyword keyword = inst.getKeyword(); Keyword keyword = inst.getKeyword();
Collection<KeywordInterface> list = map.get(keyword); Collection<KeywordInterface> list = map.get(keyword);
if (list.isEmpty() || !keyword.isMultipleRedundant) { if (list.isEmpty() || !keyword.isMultipleRedundant) {
@@ -44,6 +62,7 @@ public class KeywordCollection implements Iterable<String>, Serializable {
return true; return true;
} }
return false; return false;
} }
public void addAll(Iterable<String> keywords) { public void addAll(Iterable<String> keywords) {
@@ -52,6 +71,16 @@ public class KeywordCollection implements Iterable<String>, Serializable {
} }
} }
public boolean insertAll(Iterable<KeywordInterface> inst) {
boolean result = false;
for (KeywordInterface k : inst) {
if (insert(k)) {
result = true;
}
}
return result;
}
public boolean remove(String keyword) { public boolean remove(String keyword) {
Iterator<KeywordInterface> it = map.values().iterator(); Iterator<KeywordInterface> it = map.values().iterator();
@@ -67,10 +96,14 @@ public class KeywordCollection implements Iterable<String>, Serializable {
return result; return result;
} }
public void removeAll(Iterable<String> keywords) { public boolean removeAll(Iterable<String> keywords) {
boolean result = false;
for (String k : keywords) { for (String k : keywords) {
remove(k); if (remove(k)) {
result = true;
}
} }
return result;
} }
public void clear() { public void clear() {

View File

@@ -18,6 +18,9 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
private Keyword keyword; private Keyword keyword;
private String original; private String original;
private boolean hidden;
private List<Trigger> triggers = Lists.<Trigger>newArrayList(); private List<Trigger> triggers = Lists.<Trigger>newArrayList();
private List<ReplacementEffect> replacements = Lists.<ReplacementEffect>newArrayList(); private List<ReplacementEffect> replacements = Lists.<ReplacementEffect>newArrayList();
private List<SpellAbility> abilities = Lists.<SpellAbility>newArrayList(); private List<SpellAbility> abilities = Lists.<SpellAbility>newArrayList();
@@ -106,4 +109,19 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
host.removeStaticAbility(st); host.removeStaticAbility(st);
} }
} }
/* (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#getHidden()
*/
@Override
public boolean getHidden() {
return hidden;
}
/* (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#setHidden(boolean)
*/
@Override
public void setHidden(boolean val) {
hidden = val;
}
} }

View File

@@ -16,6 +16,9 @@ public interface KeywordInterface {
int getAmount(); int getAmount();
boolean getHidden();
void setHidden(boolean val);
void addKeywords(final Card host, final boolean intrinsic); void addKeywords(final Card host, final boolean intrinsic);
public void addTrigger(final Trigger trg); public void addTrigger(final Trigger trg);

View File

@@ -17,16 +17,12 @@
*/ */
package forge.game.keyword; package forge.game.keyword;
import java.util.Collection;
import java.util.List; import java.util.List;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
/** /**
* <p> * <p>
@@ -38,8 +34,9 @@ import forge.game.trigger.Trigger;
*/ */
public class KeywordsChange { public class KeywordsChange {
private final KeywordCollection keywords = new KeywordCollection(); private final KeywordCollection keywords = new KeywordCollection();
private final List<String> removeKeywords; private final List<KeywordInterface> removeKeywordInterfaces = Lists.newArrayList();
private final boolean removeAllKeywords; private final List<String> removeKeywords = Lists.newArrayList();
private boolean removeAllKeywords;
/** /**
* *
@@ -49,12 +46,33 @@ public class KeywordsChange {
* @param removeKeywordList the list of keywords to remove. * @param removeKeywordList the list of keywords to remove.
* @param removeAll whether to remove all keywords. * @param removeAll whether to remove all keywords.
*/ */
public KeywordsChange(final Iterable<String> keywordList, final List<String> removeKeywordList, final boolean removeAll) { public KeywordsChange(
final Iterable<String> keywordList,
final Collection<String> removeKeywordList,
final boolean removeAll) {
if (keywordList != null) { if (keywordList != null) {
this.keywords.addAll(keywordList); this.keywords.addAll(keywordList);
} }
this.removeKeywords = removeKeywordList == null ? Lists.<String>newArrayList() : Lists.newArrayList(removeKeywordList); if (removeKeywordList != null) {
this.removeKeywords.addAll(removeKeywordList);
}
this.removeAllKeywords = removeAll;
}
public KeywordsChange(
final Collection<KeywordInterface> keywordList,
final Collection<KeywordInterface> removeKeywordInterfaces,
final boolean removeAll) {
if (keywordList != null) {
this.keywords.insertAll(keywordList);
}
if (removeKeywordInterfaces != null) {
this.removeKeywordInterfaces.addAll(removeKeywordInterfaces);
}
this.removeAllKeywords = removeAll; this.removeAllKeywords = removeAll;
} }
@@ -64,8 +82,8 @@ public class KeywordsChange {
* *
* @return ArrayList<String> * @return ArrayList<String>
*/ */
public final List<String> getKeywords() { public final Collection<KeywordInterface> getKeywords() {
return Lists.newArrayList(this.keywords); return this.keywords.getValues();
} }
/** /**
@@ -108,4 +126,39 @@ public class KeywordsChange {
inst.removeKeywords(host); inst.removeKeywords(host);
} }
} }
public final boolean removeKeywordfromAdd(final String keyword) {
return keywords.remove(keyword);
}
public final void addKeyword(final String keyword) {
keywords.add(keyword);
}
public final KeywordsChange merge(
final Collection<KeywordInterface> keywordList,
final Collection<KeywordInterface> removeKeywordList,
final boolean removeAll) {
KeywordsChange result = new KeywordsChange(keywordList, removeKeywordList, removeAll);
result.__merge(this);
return result;
}
public final KeywordsChange merge(
final Iterable<String> keywordList,
final Collection<String> removeKeywordList,
final boolean removeAll) {
KeywordsChange result = new KeywordsChange(keywordList, removeKeywordList, removeAll);
result.__merge(this);
return result;
}
private void __merge(KeywordsChange other) {
keywords.insertAll(other.getKeywords());
removeKeywords.addAll(other.removeKeywords);
removeKeywordInterfaces.addAll(other.removeKeywordInterfaces);
if (other.removeAllKeywords) {
removeAllKeywords = true;
}
}
} }

View File

@@ -31,6 +31,7 @@ import forge.game.card.*;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.event.*; import forge.game.event.*;
import forge.game.keyword.KeywordCollection; import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordCollection.KeywordCollectionView; import forge.game.keyword.KeywordCollection.KeywordCollectionView;
import forge.game.keyword.KeywordsChange; import forge.game.keyword.KeywordsChange;
import forge.game.mana.ManaPool; import forge.game.mana.ManaPool;
@@ -236,7 +237,7 @@ public class Player extends GameEntity implements Comparable<Player> {
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
activeScheme = getZone(ZoneType.SchemeDeck).get(0); activeScheme = getZone(ZoneType.SchemeDeck).get(0);
// gameAction moveTo ? // gameAction moveTo ?
game.getAction().moveTo(ZoneType.Command, activeScheme, null); game.getAction().moveTo(ZoneType.Command, activeScheme, null, Maps.newHashMap());
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
// Run triggers // Run triggers
@@ -971,12 +972,10 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp) { public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp) {
// if the key already exists - merge entries // if the key already exists - merge entries
if (changedKeywords.containsKey(timestamp)) { if (changedKeywords.containsKey(timestamp)) {
final List<String> kws = addKeywords == null ? Lists.<String>newArrayList() : Lists.newArrayList(addKeywords);
final List<String> rkws = removeKeywords == null ? Lists.<String>newArrayList() : Lists.newArrayList(removeKeywords);
final KeywordsChange cks = changedKeywords.get(timestamp); final KeywordsChange cks = changedKeywords.get(timestamp);
kws.addAll(cks.getKeywords());
rkws.addAll(cks.getRemoveKeywords()); ;
changedKeywords.put(timestamp, new KeywordsChange(kws, rkws, cks.isRemoveAllKeywords())); changedKeywords.put(timestamp, cks.merge(addKeywords, removeKeywords, cks.isRemoveAllKeywords()));
updateKeywords(); updateKeywords();
return; return;
} }
@@ -1011,8 +1010,8 @@ public class Player extends GameEntity implements Comparable<Player> {
private final void replaceAllKeywordInstances(final String oldKeyword, final String newKeyword) { private final void replaceAllKeywordInstances(final String oldKeyword, final String newKeyword) {
boolean keywordReplaced = false; boolean keywordReplaced = false;
for (final KeywordsChange ck : changedKeywords.values()) { for (final KeywordsChange ck : changedKeywords.values()) {
if (ck.getKeywords().remove(oldKeyword)) { if (ck.removeKeywordfromAdd(oldKeyword)) {
ck.getKeywords().add(newKeyword); ck.addKeyword(newKeyword);
keywordReplaced = true; keywordReplaced = true;
} }
} }
@@ -1035,7 +1034,7 @@ public class Player extends GameEntity implements Comparable<Player> {
boolean keywordRemoved = false; boolean keywordRemoved = false;
for (final KeywordsChange ck : changedKeywords.values()) { for (final KeywordsChange ck : changedKeywords.values()) {
if (ck.getKeywords().remove(keyword)) { if (ck.removeKeywordfromAdd(keyword)) {
keywordRemoved = true; keywordRemoved = true;
if (!allInstances) { if (!allInstances) {
break; break;
@@ -1074,7 +1073,7 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
if (ck.getKeywords() != null) { if (ck.getKeywords() != null) {
keywords.addAll(ck.getKeywords()); keywords.insertAll(ck.getKeywords());
} }
} }
view.updateKeywords(this); view.updateKeywords(this);
@@ -1202,7 +1201,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (toBottom != null) { if (toBottom != null) {
for(Card c : toBottom) { for(Card c : toBottom) {
getGame().getAction().moveToBottomOfLibrary(c, null); getGame().getAction().moveToBottomOfLibrary(c, null, Maps.newHashMap());
numToBottom++; numToBottom++;
} }
} }
@@ -1210,7 +1209,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (toTop != null) { if (toTop != null) {
Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus. Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus.
for(Card c : toTop) { for(Card c : toTop) {
getGame().getAction().moveToLibrary(c, null); getGame().getAction().moveToLibrary(c, null, Maps.newHashMap());
numToTop++; numToTop++;
} }
} }
@@ -1283,7 +1282,7 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
} }
c = game.getAction().moveToHand(c, null); c = game.getAction().moveToHand(c, null, Maps.newHashMap());
drawn.add(c); drawn.add(c);
if (topCardRevealed) { if (topCardRevealed) {
@@ -1441,7 +1440,8 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
private static int getDredgeNumber(final Card c) { private static int getDredgeNumber(final Card c) {
for (final String s : c.getKeywords()) { for (final KeywordInterface k : c.getKeywords()) {
final String s = k.getOriginal();
if (s.startsWith("Dredge")) { if (s.startsWith("Dredge")) {
return Integer.parseInt("" + s.charAt(s.length() - 1)); return Integer.parseInt("" + s.charAt(s.length() - 1));
} }
@@ -1492,16 +1492,16 @@ public class Player extends GameEntity implements Comparable<Player> {
sb.append(this).append(" discards ").append(c); sb.append(this).append(" discards ").append(c);
final Card newCard; final Card newCard;
if (discardToTopOfLibrary) { if (discardToTopOfLibrary) {
newCard = game.getAction().moveToLibrary(c, 0, sa); newCard = game.getAction().moveToLibrary(c, 0, sa, Maps.newHashMap());
sb.append(" to the library"); sb.append(" to the library");
// Play the Discard sound // Play the Discard sound
} }
else if (discardMadness) { else if (discardMadness) {
newCard = game.getAction().exile(c, sa); newCard = game.getAction().exile(c, sa, Maps.newHashMap());
sb.append(" with Madness"); sb.append(" with Madness");
} }
else { else {
newCard = game.getAction().moveToGraveyard(c, sa); newCard = game.getAction().moveToGraveyard(c, sa, Maps.newHashMap());
// Play the Discard sound // Play the Discard sound
} }
sb.append("."); sb.append(".");
@@ -1569,7 +1569,7 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
for (Card m : milledView) { for (Card m : milledView) {
game.getAction().moveTo(destination, m, null); game.getAction().moveTo(destination, m, null, Maps.newHashMap());
} }
return milled; return milled;
@@ -1637,7 +1637,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (land.isFaceDown()) { if (land.isFaceDown()) {
land.turnFaceUp(); land.turnFaceUp();
} }
game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null); game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null, Maps.newHashMap());
// play a sound // play a sound
game.fireEvent(new GameEventLandPlayed(this, land)); game.fireEvent(new GameEventLandPlayed(this, land));
@@ -2419,7 +2419,7 @@ public class Player extends GameEntity implements Comparable<Player> {
game.getView().updatePlanarPlayer(getView()); game.getView().updatePlanarPlayer(getView());
for (Card c : currentPlanes) { for (Card c : currentPlanes) {
game.getAction().moveTo(ZoneType.Command,c, null); game.getAction().moveTo(ZoneType.Command,c, null, Maps.newHashMap());
//getZone(ZoneType.PlanarDeck).remove(c); //getZone(ZoneType.PlanarDeck).remove(c);
//getZone(ZoneType.Command).add(c); //getZone(ZoneType.Command).add(c);
} }
@@ -2449,7 +2449,7 @@ public class Player extends GameEntity implements Comparable<Player> {
//game.getZoneOf(plane).remove(plane); //game.getZoneOf(plane).remove(plane);
plane.clearControllers(); plane.clearControllers();
//getZone(ZoneType.PlanarDeck).add(plane); //getZone(ZoneType.PlanarDeck).add(plane);
game.getAction().moveTo(ZoneType.PlanarDeck, plane,-1, null); game.getAction().moveTo(ZoneType.PlanarDeck, plane,-1, null, Maps.newHashMap());
} }
currentPlanes.clear(); currentPlanes.clear();

View File

@@ -37,6 +37,7 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart; import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayment; import forge.game.cost.CostPayment;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaPool; import forge.game.mana.ManaPool;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController; import forge.game.player.PlayerController;
@@ -133,7 +134,8 @@ public class HumanPlaySpellAbility {
final Map<String, String> params = Maps.newHashMap(); final Map<String, String> params = Maps.newHashMap();
params.put("ManaColorConversion", "Additive"); params.put("ManaColorConversion", "Additive");
for (String keyword : c.getKeywords()) { for (KeywordInterface inst : c.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.startsWith("ManaConvert")) { if (keyword.startsWith("ManaConvert")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
params.put(k[1] + "Conversion", k[2]); params.put(k[1] + "Conversion", k[2]);