diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 0c1eef8f3b6..8d9c231e8d7 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -17,9 +17,14 @@ */ 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.Predicates; import com.google.common.collect.Lists; + import forge.ai.ability.AnimateAi; import forge.card.CardTypeView; import forge.game.GameEntity; @@ -30,6 +35,7 @@ import forge.game.card.*; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.combat.GlobalAttackRestrictions; +import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.trigger.Trigger; @@ -39,10 +45,6 @@ import forge.util.Expressions; import forge.util.MyRandom; import forge.util.collect.FCollectionView; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - //doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer() /** @@ -647,7 +649,8 @@ public class AiAttackController { && isEffectiveAttacker(ai, attacker, combat)) { mustAttack = true; } else { - for (String s : attacker.getKeywords()) { + for (KeywordInterface inst : attacker.getKeywords()) { + String s = inst.getOriginal(); if (s.equals("CARDNAME attacks each turn if able.") || s.startsWith("CARDNAME attacks specific player 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") || "Blocked".equals(attacker.getSVar("HasAttackEffect")); if (!hasCombatEffect) { - for (String keyword : attacker.getKeywords()) { + for (KeywordInterface inst : attacker.getKeywords()) { + String keyword = inst.getOriginal(); if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink") || keyword.startsWith("Afflict")) { hasCombatEffect = true; @@ -1124,7 +1128,8 @@ public class AiAttackController { if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) { canKillAllDangerous = false; } 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")) { canKillAllDangerous = false; break; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 105bbb93bb4..072ad6854c6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -5,6 +5,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; + import forge.card.CardType; import forge.card.ColorSet; import forge.card.MagicColor; @@ -22,6 +23,8 @@ import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.cost.CostPayEnergy; import forge.game.keyword.Keyword; +import forge.game.keyword.KeywordCollection; +import forge.game.keyword.KeywordInterface; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -42,7 +45,6 @@ import org.apache.commons.lang3.tuple.Pair; import java.util.*; import java.util.Map.Entry; - public class ComputerUtilCard { public static Card getMostExpensivePermanentAI(final CardCollectionView list, final SpellAbility spell, final boolean targeted) { CardCollectionView all = list; @@ -1564,19 +1566,21 @@ public class ComputerUtilCard { if (c.isTapped()) { pumped.setTapped(true); } - final List copiedKeywords = pumped.getKeywords(); - List toCopy = new ArrayList(); - for (String kw : c.getKeywords()) { - if (!copiedKeywords.contains(kw)) { - if (kw.startsWith("HIDDEN")) { - pumped.addHiddenExtrinsicKeyword(kw); + + KeywordCollection copiedKeywords = new KeywordCollection(); + copiedKeywords.insertAll(pumped.getKeywords()); + List toCopy = Lists.newArrayList(); + for (KeywordInterface k : c.getKeywords()) { + if (!copiedKeywords.contains(k.getOriginal())) { + if (k.getHidden()) { + pumped.addHiddenExtrinsicKeyword(k); } else { - toCopy.add(kw); + toCopy.add(k); } } } final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used? - pumped.addChangedCardKeywords(toCopy, new ArrayList(), false, timestamp2); + pumped.addChangedCardKeywordsInternal(toCopy, Lists.newArrayList(), false, timestamp2, true); ComputerUtilCard.applyStaticContPT(ai.getGame(), pumped, new CardCollection(c)); return pumped; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index f2d59f9a6ec..5bda8772b95 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -17,10 +17,14 @@ */ package forge.ai; +import java.util.List; +import java.util.Map; + import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; + import forge.game.CardTraitBase; import forge.game.Game; import forge.game.GameEntity; @@ -32,6 +36,7 @@ import forge.game.card.*; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.cost.CostPayment; +import forge.game.keyword.KeywordInterface; import forge.game.phase.Untap; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; @@ -46,9 +51,6 @@ import forge.game.zone.ZoneType; import forge.util.TextUtil; import forge.util.collect.FCollection; -import java.util.List; -import java.util.Map; - /** *

@@ -103,7 +105,8 @@ public class ComputerUtilCombat { 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")) { final String defined = keyword.split(":")[1]; final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0); @@ -2507,7 +2510,8 @@ public class ComputerUtilCombat { for (Card atk : attackers) { boolean hasProtection = false; - for (String kw : atk.getKeywords()) { + for (KeywordInterface inst : atk.getKeywords()) { + String kw = inst.getOriginal(); if (kw.startsWith("Protection")) { hasProtection = true; break; diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java index ddd9f4ed29f..1a5e903059e 100644 --- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java @@ -1,11 +1,13 @@ package forge.ai; import com.google.common.base.Function; + import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CounterType; import forge.game.cost.CostPayEnergy; +import forge.game.keyword.KeywordInterface; import forge.game.spellability.SpellAbility; public class CreatureEvaluator implements Function { @@ -32,7 +34,8 @@ public class CreatureEvaluator implements Function { } int power = getEffectivePower(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.") || 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.") diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java index 3abb119a482..cc0ff7d3c41 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -1,6 +1,9 @@ package forge.ai.ability; +import org.apache.commons.lang3.StringUtils; + import com.google.common.base.Predicates; + import forge.ai.ComputerUtil; import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilMana; @@ -14,13 +17,13 @@ import forge.game.card.CardCollection; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.cost.Cost; +import forge.game.keyword.KeywordInterface; import forge.game.mana.ManaCostBeingPaid; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import org.apache.commons.lang3.StringUtils; 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 - for (String ability : card.getKeywords()) { + for (KeywordInterface inst : card.getKeywords()) { + String ability = inst.getOriginal(); if (ability.startsWith("UpkeepCost")) { final String[] k = ability.split(":"); final String costs = k[1]; diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 105895037cb..0bc31ae4e95 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -24,6 +24,7 @@ import forge.game.card.CardFactoryUtil; import forge.game.card.CounterType; import forge.game.card.token.TokenInfo; import forge.game.combat.Combat; +import forge.game.keyword.KeywordInterface; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -306,7 +307,7 @@ public class GameCopier { newCard.setChangedCardTypes(c.getChangedCardTypesMap()); newCard.setChangedCardKeywords(c.getChangedCardKeywords()); // 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.setExtrinsicKeyword(Lists.newArrayList(c.getExtrinsicKeyword())); if (c.isTapped()) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index d571ce9a719..a6f0f5490aa 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -27,6 +27,7 @@ import forge.game.ability.ApiType; import forge.game.ability.effects.AttachEffect; import forge.game.card.*; import forge.game.event.*; +import forge.game.keyword.KeywordInterface; import forge.game.player.GameLossReason; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; @@ -1824,7 +1825,8 @@ public class GameAction { // Select what can be activated from a given 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")) { String[] split = kw.split(":"); final String effName = split[1]; diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 6829f181f25..0d090796141 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -29,6 +29,7 @@ import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.card.CardPlayOption.PayManaCost; import forge.game.cost.Cost; +import forge.game.keyword.KeywordInterface; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.spellability.*; @@ -200,7 +201,8 @@ public final class GameActionUtil { 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 source has No Mana cost, and flashback doesn't have own one, // flashback can't work @@ -248,7 +250,8 @@ public final class GameActionUtil { return costs; } final Card source = sa.getHostCard(); - for (String keyword : source.getKeywords()) { + for (KeywordInterface inst : source.getKeywords()) { + final String keyword = inst.getOriginal(); if (keyword.startsWith("Buyback")) { final Cost cost = new Cost(keyword.substring(8), false); costs.add(new OptionalCostValue(OptionalCost.Buyback, cost)); @@ -319,7 +322,8 @@ public final class GameActionUtil { return abilities; } final Card source = sa.getHostCard(); - for (String keyword : source.getKeywords()) { + for (KeywordInterface inst : source.getKeywords()) { + final String keyword = inst.getOriginal(); if (keyword.startsWith("AlternateAdditionalCost")) { final List newAbilities = Lists.newArrayList(); String[] costs = TextUtil.split(keyword, ':'); @@ -370,7 +374,8 @@ public final class GameActionUtil { } // Buyback, Kicker - for (String keyword : source.getKeywords()) { + for (KeywordInterface inst : source.getKeywords()) { + final String keyword = inst.getOriginal(); if (keyword.startsWith("Buyback")) { for (int i = 0; i < abilities.size(); i++) { final SpellAbility newSA = abilities.get(i).copy(); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 44affdfecdb..5559806689f 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -18,6 +18,7 @@ import forge.game.GameObject; import forge.game.ability.AbilityFactory.AbilityRecordType; import forge.game.card.*; import forge.game.cost.Cost; +import forge.game.keyword.KeywordInterface; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.player.PlayerCollection; @@ -1823,7 +1824,8 @@ public class AbilityUtils { public static void addSpliceEffect(final SpellAbility sa, final Card c) { Cost spliceCost = null; - for (final String k : c.getKeywords()) { + for (final KeywordInterface inst : c.getKeywords()) { + final String k = inst.getOriginal(); if (k.startsWith("Splice")) { final String n[] = k.split(":"); spliceCost = new Cost(n[2], false); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java index ce6d5c107b7..eb295066e83 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java @@ -274,11 +274,11 @@ public class CloneEffect extends SpellAbilityEffect { keywords.remove(k); } k = keywords.get(i); - tgtCard.addIntrinsicKeyword(k); - KeywordInterface inst = Keyword.getInstance(k); - - inst.addKeywords(tgtCard, true); + KeywordInterface inst = tgtCard.addIntrinsicKeyword(k); + if (inst != null) { + inst.addKeywords(tgtCard, true); + } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java index 239d6468f52..d98fe862f83 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java @@ -5,6 +5,7 @@ import forge.card.MagicColor; import forge.game.Game; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; +import forge.game.keyword.KeywordInterface; import forge.game.spellability.SpellAbility; import java.util.Arrays; @@ -73,7 +74,8 @@ public class DebuffEffect extends SpellAbilityEffect { if (tgtC.isInPlay() && tgtC.canBeTargetedBy(sa)) { if (sa.hasParam("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)) { kws.add(keyword); } @@ -81,7 +83,8 @@ public class DebuffEffect extends SpellAbilityEffect { } // special for Protection:Card.:Protection from :* - for (final String keyword : tgtC.getKeywords()) { + for (final KeywordInterface inst : tgtC.getKeywords()) { + String keyword = inst.getOriginal(); if (keyword.startsWith("Protection:")) { for (final String kw : kws) { if (keyword.matches("(?i).*:" + kw)) diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java index 87ca1931caa..9db23737273 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java @@ -9,6 +9,7 @@ import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardUtil; import forge.game.event.GameEventCardStatsChanged; +import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; @@ -258,8 +259,8 @@ public class PumpEffect extends SpellAbilityEffect { List choice = Lists.newArrayList(); List total = Lists.newArrayList(keywords); if (sa.hasParam("NoRepetition")) { - final List tgtCardskws = tgtCards.get(0).getKeywords(); - for (String kws : tgtCardskws) { + for (KeywordInterface inst : tgtCards.get(0).getKeywords()) { + final String kws = inst.getOriginal(); if (total.contains(kws)) { total.remove(kws); } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 0f694b135c6..6f4dee02401 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -40,6 +40,9 @@ import forge.game.event.*; import forge.game.event.GameEventCardAttachment.AttachMethod; import forge.game.event.GameEventCardDamaged.DamageType; 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.player.Player; import forge.game.player.PlayerCollection; @@ -93,9 +96,9 @@ public class Card extends GameEntity implements Comparable { private final CardDamageHistory damageHistory = new CardDamageHistory(); private Map> countersAddedBy = Maps.newTreeMap(); - private List extrinsicKeyword = Lists.newArrayList(); + private KeywordCollection extrinsicKeyword = new KeywordCollection(); // Hidden keywords won't be displayed on the card - private final CopyOnWriteArrayList hiddenExtrinsicKeyword = new CopyOnWriteArrayList<>(); + private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection(); // cards attached or otherwise linked to this card private CardCollection equippedBy, fortifiedBy, hauntedBy, devouredCards, delvedCards, imprintedCards, encodedCards; @@ -122,7 +125,7 @@ public class Card extends GameEntity implements Comparable { private final CardChangedWords changedTextColors = new CardChangedWords(); private final CardChangedWords changedTextTypes = new CardChangedWords(); /** List of the keywords that have been added by text changes. */ - private final List keywordsGrantedByTextChanges = Lists.newArrayList(); + private final List keywordsGrantedByTextChanges = Lists.newArrayList(); /** Original values of SVars changed by text changes. */ private Map originalSVars = Maps.newHashMap(); @@ -1374,7 +1377,7 @@ public class Card extends GameEntity implements Comparable { } // convert a keyword list to the String that should be displayed in game - public final String keywordsToText(final List keywords) { + public final String keywordsToText(final Collection keywords) { final StringBuilder sb = new StringBuilder(); final StringBuilder sbLong = new StringBuilder(); @@ -1382,8 +1385,9 @@ public class Card extends GameEntity implements Comparable { final Set> textChanges = Sets.union( changedTextColors.toMap().entrySet(), changedTextTypes.toMap().entrySet()); - for (int i = 0; i < keywords.size(); i++) { - String keyword = keywords.get(i); + int i = 0; + for (KeywordInterface inst : keywords) { + String keyword = inst.getOriginal(); if (keyword.startsWith("PreventAllDamageBy") || keyword.startsWith("CantEquip") || keyword.startsWith("SpellCantTarget")) { @@ -1391,7 +1395,7 @@ public class Card extends GameEntity implements Comparable { } // format text changes if (CardUtil.isKeywordModifiable(keyword) - && keywordsGrantedByTextChanges.contains(keyword)) { + && keywordsGrantedByTextChanges.contains(inst)) { for (final Entry e : textChanges) { final String value = e.getValue(); if (keyword.contains(value)) { @@ -1453,13 +1457,13 @@ public class Card extends GameEntity implements Comparable { if (!mCost.isOnlyManaCost()) { sbLong.append("."); } - sbLong.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); + sbLong.append(" (" + inst.getReminderText() + ")"); sbLong.append("\r\n"); } } else if (keyword.startsWith("Emerge")) { final String[] k = keyword.split(":"); sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1])); - sbLong.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); + sbLong.append(" (" + inst.getReminderText() + ")"); sbLong.append("\r\n"); } else if (keyword.startsWith("Echo")) { sbLong.append("Echo "); @@ -1490,7 +1494,7 @@ public class Card extends GameEntity implements Comparable { final String[] n = keyword.split(":"); final Cost cost = new Cost(n[1], false); 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")) { if (!keyword.endsWith("Generic")) { @@ -1504,13 +1508,13 @@ public class Card extends GameEntity implements Comparable { final Cost cost2 = new Cost(n[2], false); sbx.append(cost2.toSimpleString()); } - sbx.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); + sbx.append(" (" + inst.getReminderText() + ")"); sbLong.append(sbx).append("\r\n"); } } else if (keyword.endsWith(".") && !keyword.startsWith("Haunt")) { sbLong.append(keyword).append("\r\n"); } 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, ") && keyword.contains(" unless you pay")) { sbLong.append(keyword).append("\r\n"); @@ -1524,14 +1528,14 @@ public class Card extends GameEntity implements Comparable { || keyword.equals("Changeling") || keyword.equals("Delve") || keyword.startsWith("Dredge") || (keyword.startsWith("Split second") && !sb.toString().contains("Split second")) || keyword.startsWith("Devoid")){ - sbLong.append(keyword + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); + sbLong.append(keyword + " (" + inst.getReminderText() + ")"); } else if (keyword.startsWith("Modular") || keyword.startsWith("Bloodthirst") || keyword.startsWith("Fabricate") || keyword.startsWith("Soulshift") || keyword.startsWith("Crew") || keyword.startsWith("Tribute") || keyword.startsWith("Absorb") || keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing") || keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) { 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")) { sb.append("\r\nHaunt ("); if (isCreature()) { @@ -1553,7 +1557,7 @@ public class Card extends GameEntity implements Comparable { if (sb.length() != 0) { sb.append("\r\n"); } - sb.append(keyword + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); + sb.append(keyword + " (" + inst.getReminderText() + ")"); } else if (keyword.endsWith(" offering")) { String offeringType = keyword.split(" ")[0]; if (sb.length() != 0) { @@ -1609,6 +1613,8 @@ public class Card extends GameEntity implements Comparable { if (sbLong.length() > 0) { sbLong.append("\r\n"); } + + i++; } if (sb.length() > 0) { sb.append("\r\n"); @@ -1937,9 +1943,6 @@ public class Card extends GameEntity implements Comparable { } } - // Add Keywords - final List kw = getKeywords(state); - // Triggered abilities for (final Trigger trig : state.getTriggers()) { if (!trig.isSecondary()) { @@ -1966,7 +1969,8 @@ public class Card extends GameEntity implements Comparable { // TODO A lot of these keywords should really show up before the SAs // 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")) //|| (keyword.startsWith("Dredge") && !sb.toString().contains("Dredge")) | Replaced with // 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 { || (keyword.startsWith("Miracle") && !sb.toString().contains("Miracle"))) { String[] parts = keyword.split(":"); 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.")) { sb.append(keyword).append("\r\n"); } 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") || keyword.startsWith("Cascade") || keyword.startsWith("Wither") || (keyword.startsWith("Epic") && !sb.toString().contains("Epic")) @@ -1989,7 +1993,7 @@ public class Card extends GameEntity implements Comparable { || (keyword.startsWith("Devoid"))) { if (sb.length() != 0) { 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.")) { sb.append(keyword).append("\r\n"); @@ -1997,7 +2001,7 @@ public class Card extends GameEntity implements Comparable { final String[] n = keyword.split(":"); final Cost cost = new Cost(n[2], false); 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")) { final String[] n = keyword.split(":"); final Cost cost = new Cost(n[1], false); @@ -2009,7 +2013,7 @@ public class Card extends GameEntity implements Comparable { final String[] n = keyword.split(":"); final Cost cost = new Cost(n[1], false); 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")) { if (!keyword.endsWith("Generic")) { @@ -2023,7 +2027,7 @@ public class Card extends GameEntity implements Comparable { final Cost cost2 = new Cost(n[2], false); sbx.append(cost2.toSimpleString()); } - sbx.append(" (" + Keyword.getInstance(keyword).getReminderText() + ")"); + sbx.append(" (" + inst.getReminderText() + ")"); sb.append(sbx).append("\r\n"); } } else if (keyword.startsWith("AlternateAdditionalCost")) { @@ -2098,9 +2102,9 @@ public class Card extends GameEntity implements Comparable { } 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")) { - sb.append(keyword + " (" + Keyword.getInstance(keyword).getReminderText() + ")"); + sb.append(keyword + " (" + inst.getReminderText() + ")"); } else if (keyword.startsWith("Presence")) { - sb.append(Keyword.getInstance(keyword).getReminderText()); + sb.append(inst.getReminderText()); } } return sb; @@ -2569,10 +2573,13 @@ public class Card extends GameEntity implements Comparable { getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, "Trying to equip " + c.getName() + " but it can't be equipped."); return; } - if (hasStartOfKeyword("CantEquip")) { - final int keywordPosition = getKeywordPosition("CantEquip"); - final String parse = getKeywords().get(keywordPosition); - final String[] k = parse.split(" ", 2); + + for(KeywordInterface inst : c.getKeywords()) { + String kw = inst.getOriginal(); + if (!kw.startsWith("CantEquip")) { + continue; + } + final String[] k = kw.split(" ", 2); final String[] restrictions = k[1].split(","); if (c.isValid(restrictions, getController(), this, null)) { 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 { } // keywords are like flying, fear, first strike, etc... - public final List getKeywords() { + public final List getKeywords() { return getKeywords(currentState); } - public final List getKeywords(CardState state) { + public final List getKeywords(CardState state) { ListKeywordVisitor visitor = new ListKeywordVisitor(); visitKeywords(state, visitor); return visitor.getKeywords(); } // Allows traversing the card's keywords without needing to concat a bunch // of lists. Optimizes common operations such as hasKeyword(). - public final void visitKeywords(CardState state, Visitor visitor) { + public final void visitKeywords(CardState state, Visitor visitor) { visitUnhiddenKeywords(state, visitor); visitHiddenExtreinsicKeywords(visitor); } @@ -3262,6 +3269,7 @@ public class Card extends GameEntity implements Comparable { final boolean removeAllKeywords, final long timestamp) { addChangedCardKeywords(keywords, removeKeywords, removeAllKeywords, timestamp, true); } + public final void addChangedCardKeywords(final List keywords, final List removeKeywords, final boolean removeAllKeywords, final long timestamp, final boolean updateView) { @@ -3269,14 +3277,32 @@ public class Card extends GameEntity implements Comparable { // if the key already exists - merge entries final KeywordsChange cks = changedCardKeywords.get(timestamp); if (cks != null) { - cks.removeKeywords(this); - List kws = Lists.newArrayList(keywords); - List rkws = Lists.newArrayList(removeKeywords); - boolean remAll = removeAllKeywords; - kws.addAll(cks.getKeywords()); - rkws.addAll(cks.getRemoveKeywords()); - remAll |= cks.isRemoveAllKeywords(); - final KeywordsChange newCks = new KeywordsChange(kws, rkws, remAll); + cks.removeKeywords(this); + final KeywordsChange newCks = cks.merge(keywords, removeKeywords, removeAllKeywords); + newCks.addKeywordsToCard(this); + changedCardKeywords.put(timestamp, newCks); + } + else { + final KeywordsChange newCks = new KeywordsChange(keywords, removeKeywords, removeAllKeywords); + newCks.addKeywordsToCard(this); + changedCardKeywords.put(timestamp, newCks); + } + + if (updateView) { + updateKeywords(); + } + } + + public final void addChangedCardKeywordsInternal(final List keywords, final List 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); changedCardKeywords.put(timestamp, newCks); } @@ -3322,13 +3348,16 @@ public class Card extends GameEntity implements Comparable { } // Hidden keywords will be left out - public final List getUnhiddenKeywords() { + public final Collection getUnhiddenKeywords() { return getUnhiddenKeywords(currentState); } - public final List getUnhiddenKeywords(CardState state) { - final List keywords = Lists.newArrayList(); - Iterables.addAll(keywords, state.getIntrinsicKeywords()); - keywords.addAll(extrinsicKeyword); + public final Collection getUnhiddenKeywords(CardState state) { + KeywordCollection keywords = new KeywordCollection(); + + //final List keywords = Lists.newArrayList(); + + keywords.insertAll(state.getIntrinsicKeywords()); + keywords.insertAll(extrinsicKeyword.getValues()); // see if keyword changes are in effect for (final KeywordsChange ck : changedCardKeywords.values()) { @@ -3340,22 +3369,22 @@ public class Card extends GameEntity implements Comparable { } if (ck.getKeywords() != null) { - keywords.addAll(ck.getKeywords()); + keywords.insertAll(ck.getKeywords()); } } - return keywords; + return keywords.getValues(); } - private void visitUnhiddenKeywords(CardState state, Visitor visitor) { + private void visitUnhiddenKeywords(CardState state, Visitor visitor) { if (changedCardKeywords.isEmpty()) { // Fast path that doesn't involve temp allocations. - for (String kw : state.getIntrinsicKeywords()) { + for (KeywordInterface kw : state.getIntrinsicKeywords()) { visitor.visit(kw); } - for (String kw : extrinsicKeyword) { + for (KeywordInterface kw : extrinsicKeyword.getValues()) { visitor.visit(kw); } } else { - for (String kw : getUnhiddenKeywords()) { + for (KeywordInterface kw : getUnhiddenKeywords()) { visitor.visit(kw); } } @@ -3418,18 +3447,20 @@ public class Card extends GameEntity implements Comparable { return; } - final List addKeywords = Lists.newArrayList(); - final List removeKeywords = Lists.newArrayList(keywordsGrantedByTextChanges); + final List addKeywords = Lists.newArrayList(); + final List removeKeywords = Lists.newArrayList(keywordsGrantedByTextChanges); - for (final String kw : currentState.getIntrinsicKeywords()) { - final String newKw = AbilityUtils.applyKeywordTextChangeEffects(kw, this); - if (!newKw.equals(kw)) { + for (final KeywordInterface kw : currentState.getIntrinsicKeywords()) { + String oldtxt = kw.getOriginal(); + final String newtxt = AbilityUtils.applyKeywordTextChangeEffects(oldtxt, this); + if (!newtxt.equals(oldtxt)) { + KeywordInterface newKw = Keyword.getInstance(newtxt); addKeywords.add(newKw); removeKeywords.add(kw); keywordsGrantedByTextChanges.add(newKw); } } - addChangedCardKeywords(addKeywords, removeKeywords, false, timestamp); + addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, timestamp, true); } private void updateKeywordsOnRemoveChangedText(final KeywordsChange k) { @@ -3497,10 +3528,12 @@ public class Card extends GameEntity implements Comparable { originalSVars.clear(); } - public final void addIntrinsicKeyword(final String s) { - if (currentState.addIntrinsicKeyword(s)) { + public final KeywordInterface addIntrinsicKeyword(final String s) { + KeywordInterface inst = currentState.addIntrinsicKeyword(s); + if (inst != null) { currentState.getView().updateKeywords(this, currentState); } + return inst; } public final void addIntrinsicKeywords(final Iterable s) { @@ -3515,11 +3548,16 @@ public class Card extends GameEntity implements Comparable { } } - public List getExtrinsicKeyword() { - return extrinsicKeyword; + public Collection getExtrinsicKeyword() { + return extrinsicKeyword.getValues(); } public final void setExtrinsicKeyword(final List a) { - extrinsicKeyword = Lists.newArrayList(a); + extrinsicKeyword.clear(); + extrinsicKeyword.addAll(a); + } + public void setExtrinsicKeyword(Collection extrinsicKeyword2) { + extrinsicKeyword.clear(); + extrinsicKeyword.insertAll(extrinsicKeyword2); } public void addExtrinsicKeyword(final String s) { @@ -3560,31 +3598,38 @@ public class Card extends GameEntity implements Comparable { } // Hidden Keywords will be returned without the indicator HIDDEN - public final List getHiddenExtrinsicKeywords() { + public final List getHiddenExtrinsicKeywords() { ListKeywordVisitor visitor = new ListKeywordVisitor(); visitHiddenExtreinsicKeywords(visitor); return visitor.getKeywords(); } - private void visitHiddenExtreinsicKeywords(Visitor visitor) { - for (String keyword : hiddenExtrinsicKeyword) { - if (keyword == null) { - continue; - } - if (keyword.startsWith("HIDDEN")) { - keyword = keyword.substring(7); - } - visitor.visit(keyword); + private void visitHiddenExtreinsicKeywords(Visitor visitor) { + for (KeywordInterface inst : hiddenExtrinsicKeyword.getValues()) { + visitor.visit(inst); } } - public final void addHiddenExtrinsicKeyword(final String s) { - if (hiddenExtrinsicKeyword.add(s)) { + public final void addHiddenExtrinsicKeyword(String s) { + if (s.startsWith("HIDDEN")) { + s = s.substring(7); + } + if (hiddenExtrinsicKeyword.add(s) != null) { + view.updateNonAbilityText(this); + currentState.getView().updateKeywords(this, currentState); + } + } + + public final void addHiddenExtrinsicKeyword(KeywordInterface k) { + if (hiddenExtrinsicKeyword.insert(k)) { view.updateNonAbilityText(this); currentState.getView().updateKeywords(this, currentState); } } - public final void removeHiddenExtrinsicKeyword(final String s) { + public final void removeHiddenExtrinsicKeyword(String s) { + if (s.startsWith("HIDDEN")) { + s = s.substring(7); + } if (hiddenExtrinsicKeyword.remove(s)) { view.updateNonAbilityText(this); currentState.getView().updateKeywords(this, currentState); @@ -3817,19 +3862,6 @@ public class Card extends GameEntity implements Comparable { return visitor.getCount() > 0; } - public final int getKeywordPosition(String k) { - return getKeywordPosition(k, currentState); - } - public final int getKeywordPosition(String k, CardState state) { - final List 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 keywords) { return hasAnyKeyword(keywords, currentState); } @@ -3859,7 +3891,8 @@ public class Card extends GameEntity implements Comparable { } public final int getKeywordMagnitude(final String k, CardState state) { int count = 0; - for (final String kw : getKeywords(state)) { + for (final KeywordInterface inst : getKeywords(state)) { + String kw = inst.getOriginal(); if (kw.startsWith(k)) { final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" "); final String s = parse[1]; @@ -4331,7 +4364,8 @@ public class Card extends GameEntity implements Comparable { return 0; } - for (String kw : getKeywords()) { + for (KeywordInterface inst : getKeywords()) { + String kw = inst.getOriginal(); if (kw.startsWith("PreventAllDamageBy")) { if (source.isValid(kw.split(" ", 2)[1].split(","), getController(), this, null)) { return 0; @@ -4837,93 +4871,91 @@ public class Card extends GameEntity implements Comparable { return true; } - final List keywords = getKeywords(); - if (keywords != null) { - final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source"); + final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source"); - for (final String kw : keywords) { - if (!kw.startsWith("Protection")) { - continue; - } - if (kw.equals("Protection from white")) { - if (source.isWhite() && !colorlessDamage) { - return true; - } - } 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")) { + for (final KeywordInterface inst : getKeywords()) { + String kw = inst.getOriginal(); + if (!kw.startsWith("Protection")) { + continue; + } + if (kw.equals("Protection from white")) { + if (source.isWhite() && !colorlessDamage) { return true; - } else if (kw.startsWith("Protection:")) { // uses isValid; Protection:characteristic:desc:exception - final String[] kws = kw.split(":"); - String characteristic = kws[1]; + } + } 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; + } 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) { - if (characteristic.endsWith("White") || characteristic.endsWith("Blue") - || characteristic.endsWith("Black") || characteristic.endsWith("Red") - || characteristic.endsWith("Green") || characteristic.endsWith("Colorless") - || characteristic.endsWith("ChosenColor")) { - characteristic += "Source"; - } + // if colorlessDamage then it does only check damage color.. + if (colorlessDamage) { + if (characteristic.endsWith("White") || characteristic.endsWith("Blue") + || characteristic.endsWith("Black") || characteristic.endsWith("Red") + || characteristic.endsWith("Green") || characteristic.endsWith("Colorless") + || characteristic.endsWith("ChosenColor")) { + characteristic += "Source"; } + } - final String[] characteristics = characteristic.split(","); - final String exception = kws.length > 3 ? kws[3] : null; // check "This effect cannot remove sth" - if (source.isValid(characteristics, getController(), this, null) - && (!checkSBA || exception == null || !source.isValid(exception, getController(), this, null))) { - return true; - } - } else if (kw.equals("Protection from colored spells")) { - if (source.isSpell() && !source.isColorless()) { - return true; - } - } else if (kw.equals("Protection from the chosen player")) { - if (source.getController().equals(chosenPlayer)) { - return true; - } - } else if (kw.startsWith("Protection from ")) { - final String protectType = CardType.getSingularType(kw.substring("Protection from ".length())); - if (source.getType().hasStringType(protectType)) { - return true; - } + final String[] characteristics = characteristic.split(","); + final String exception = kws.length > 3 ? kws[3] : null; // check "This effect cannot remove sth" + if (source.isValid(characteristics, getController(), this, null) + && (!checkSBA || exception == null || !source.isValid(exception, getController(), this, null))) { + return true; + } + } else if (kw.equals("Protection from colored spells")) { + if (source.isSpell() && !source.isColorless()) { + return true; + } + } else if (kw.equals("Protection from the chosen player")) { + if (source.getController().equals(chosenPlayer)) { + return true; + } + } else if (kw.startsWith("Protection from ")) { + final String protectType = CardType.getSingularType(kw.substring("Protection from ".length())); + if (source.getType().hasStringType(protectType)) { + return true; } } } @@ -4983,13 +5015,13 @@ public class Card extends GameEntity implements Comparable { final Card source = sa.getHostCard(); final MutableBoolean result = new MutableBoolean(true); - visitKeywords(currentState, new Visitor() { + visitKeywords(currentState, new Visitor() { @Override - public void visit(String kw) { + public void visit(KeywordInterface kw) { if (result.isFalse()) { return; } - switch (kw) { + switch (kw.getOriginal()) { case "Shroud": StringBuilder sb = new StringBuilder(); sb.append("Can target CardUID_").append(String.valueOf(getId())); @@ -5031,13 +5063,17 @@ public class Card extends GameEntity implements Comparable { if (result.isFalse()) { return false; } - if (sa.isSpell() && source.hasStartOfKeyword("SpellCantTarget")) { - final int keywordPosition = source.getKeywordPosition("SpellCantTarget"); - final String parse = source.getKeywords().get(keywordPosition); - final String[] k = parse.split(":"); - final String[] restrictions = k[1].split(","); - if (isValid(restrictions, source.getController(), source, null)) { - return false; + if (sa.isSpell()) { + for(KeywordInterface inst : source.getKeywords()) { + String kw = inst.getOriginal(); + if(!kw.startsWith("SpellCantTarget")) { + continue; + } + final String[] k = kw.split(":"); + final String[] restrictions = k[1].split(","); + if (isValid(restrictions, source.getController(), source, null)) { + return false; + } } } return true; @@ -5066,10 +5102,12 @@ public class Card extends GameEntity implements Comparable { } public final boolean canBeEquippedBy(final Card equip) { - if (equip.hasStartOfKeyword("CantEquip")) { - final int keywordPosition = equip.getKeywordPosition("CantEquip"); - final String parse = equip.getKeywords().get(keywordPosition); - final String[] k = parse.split(" ", 2); + for(KeywordInterface inst : equip.getKeywords()) { + String kw = inst.getOriginal(); + if(!kw.startsWith("CantEquip")) { + continue; + } + final String[] k = kw.split(":"); final String[] restrictions = k[1].split(","); if (isValid(restrictions, equip.getController(), equip, null)) { return false; @@ -5461,7 +5499,7 @@ public class Card extends GameEntity implements Comparable { } // Counts number of instances of a given keyword. - private static final class CountKeywordVisitor extends Visitor { + private static final class CountKeywordVisitor extends Visitor { private String keyword; private int count; private boolean startOf; @@ -5478,7 +5516,8 @@ public class Card extends GameEntity implements Comparable { } @Override - public void visit(String kw) { + public void visit(KeywordInterface inst) { + final String kw = inst.getOriginal(); if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) { count++; } @@ -5490,15 +5529,15 @@ public class Card extends GameEntity implements Comparable { } // Collects all the keywords into a list. - private static final class ListKeywordVisitor extends Visitor { - private List keywords = Lists.newArrayList(); + private static final class ListKeywordVisitor extends Visitor { + private List keywords = Lists.newArrayList(); @Override - public void visit(String kw) { + public void visit(KeywordInterface kw) { keywords.add(kw); } - public List getKeywords() { + public List getKeywords() { return keywords; } } @@ -5603,4 +5642,6 @@ public class Card extends GameEntity implements Comparable { removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play setSunburstValue(0); // Sunburst } + + } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index d220db8a0c8..6180d46fd39 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1861,7 +1861,8 @@ public class CardFactoryUtil { final List allkw = Lists.newArrayList(); 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 (!landkw.contains(k)) { landkw.add(k); @@ -1948,8 +1949,7 @@ public class CardFactoryUtil { // Cards with Cycling abilities // -1 means keyword "Cycling" not found - for (String keyword : card.getKeywords()) { - KeywordInterface inst = Keyword.getInstance(keyword); + for (KeywordInterface inst : card.getKeywords()) { inst.addKeywords(card, true); } diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index aa4cb3b9632..07d9ef12d79 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -19,6 +19,7 @@ package forge.game.card; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.card.*; import forge.card.mana.ManaCost; @@ -27,6 +28,7 @@ import forge.game.ForgeScript; import forge.game.GameObject; import forge.game.card.CardView.CardStateView; import forge.game.keyword.KeywordCollection; +import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; import forge.game.spellability.SpellAbility; @@ -35,6 +37,8 @@ import forge.game.trigger.Trigger; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; +import java.util.Collection; +import java.util.List; import java.util.Map; public class CardState extends GameObject { @@ -146,8 +150,8 @@ public class CardState extends GameObject { view.updateToughness(this); } - public final Iterable getIntrinsicKeywords() { - return intrinsicKeywords; + public final Collection getIntrinsicKeywords() { + return intrinsicKeywords.getValues(); } public final boolean hasIntrinsicKeyword(String k) { return intrinsicKeywords.contains(k); @@ -157,13 +161,17 @@ public class CardState extends GameObject { intrinsicKeywords.addAll(intrinsicKeyword0); } - public final boolean addIntrinsicKeyword(final String s) { - return s.trim().length() != 0 && intrinsicKeywords.add(s); + public final KeywordInterface addIntrinsicKeyword(final String s) { + if (s.trim().length() == 0) { + return null; + } + + return intrinsicKeywords.add(s); } public final boolean addIntrinsicKeywords(final Iterable keywords) { boolean changed = false; for (String k : keywords) { - if (addIntrinsicKeyword(k)) { + if (addIntrinsicKeyword(k) != null) { changed = true; } } @@ -400,6 +408,14 @@ public class CardState extends GameObject { public boolean hasProperty(String property, Player sourceController, Card source, SpellAbility spellAbility) { return ForgeScript.cardStateHasProperty(this, property, sourceController, source, spellAbility); } + + public List addIntrinsicKeywords(Collection intrinsicKeywords2) { + List names = Lists.newArrayList(); + for (KeywordInterface inst : intrinsicKeywords2) { + names.add(inst.getOriginal()); + } + return names; + } } diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java index 5dd3c4581a6..259fe223e62 100644 --- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java +++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java @@ -9,6 +9,7 @@ import forge.card.CardType; import forge.game.Game; import forge.game.card.Card; import forge.game.card.CardFactoryUtil; +import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import java.util.List; @@ -39,7 +40,13 @@ public class TokenInfo { this.imageName = ImageKeys.getTokenImageName(c.getImageKey()); this.manaCost = c.getManaCost().toString(); this.types = getCardTypes(c); - this.intrinsicKeywords = c.getKeywords().toArray(new String[0]); + + List list = Lists.newArrayList(); + for (KeywordInterface inst : c.getKeywords()) { + list.add(inst.getOriginal()); + } + + this.intrinsicKeywords = list.toArray(new String[0]); this.basePower = c.getBasePower(); this.baseToughness = c.getBaseToughness(); } diff --git a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java index eb619a8d1fb..2d109fa6ace 100644 --- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java +++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java @@ -8,6 +8,7 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CardPredicates; +import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.util.collect.FCollectionView; @@ -49,7 +50,8 @@ public class AttackRequirement { 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")) { final String defined = keyword.split(":")[1]; final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0); diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index 731d831973f..cd78ea29340 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -29,6 +29,7 @@ import forge.game.GameEntity; import forge.game.GlobalRuleChange; import forge.game.card.*; import forge.game.cost.Cost; +import forge.game.keyword.KeywordInterface; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerController.ManaPaymentPurpose; @@ -201,8 +202,8 @@ public class CombatUtil { // Keywords final boolean canAttackWithDefender = attacker.hasKeyword("CARDNAME can attack as though it didn't have defender."); - for (final String keyword : attacker.getKeywords()) { - switch (keyword) { + for (final KeywordInterface keyword : attacker.getKeywords()) { + switch (keyword.getOriginal()) { case "CARDNAME can't attack.": case "CARDNAME can't attack or block.": 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")) { walkTypes.add("Land.Legendary"); } else if (keyword.equals("Desertwalk")) { @@ -779,7 +781,8 @@ public class CombatUtil { && combat.getBlockers(attacker).isEmpty())) { attackersWithLure.add(attacker); } else { - for (String keyword : attacker.getKeywords()) { + for (KeywordInterface inst : attacker.getKeywords()) { + String keyword = inst.getOriginal(); // MustBeBlockedBy if (keyword.startsWith("MustBeBlockedBy ")) { final String valid = keyword.substring("MustBeBlockedBy ".length()); @@ -891,7 +894,8 @@ public class CombatUtil { } boolean mustBeBlockedBy = false; - for (String keyword : attacker.getKeywords()) { + for (KeywordInterface inst : attacker.getKeywords()) { + String keyword = inst.getOriginal(); // MustBeBlockedBy if (keyword.startsWith("MustBeBlockedBy ")) { final String valid = keyword.substring("MustBeBlockedBy ".length()); @@ -991,7 +995,8 @@ public class CombatUtil { return false; } - for (String k : attacker.getKeywords()) { + for (KeywordInterface inst1 : attacker.getKeywords()) { + String k = inst1.getOriginal(); if (k.startsWith("CantBeBlockedBy ")) { final String[] n = k.split(" ", 2); final String[] restrictions = n[1].split(","); @@ -999,7 +1004,8 @@ public class CombatUtil { boolean stillblock = false; //Dragon Hunter check 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")) { String n2[] = k2.split(":"); 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")) { final String[] k = keyword.split("_", 2); if (attacker.getId() == Integer.parseInt(k[1])) { @@ -1037,7 +1044,8 @@ public class CombatUtil { if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) { boolean stillblock = false; - for (String k : blocker.getKeywords()) { + for (KeywordInterface inst : blocker.getKeywords()) { + String k = inst.getOriginal(); if (k.startsWith("IfReach")) { String n[] = k.split(":"); if (attacker.getType().hasCreatureType(n[1])) { @@ -1071,7 +1079,8 @@ public class CombatUtil { return false; // no block List restrictions = Lists.newArrayList(); - for (String kw : attacker.getKeywords()) { + for (KeywordInterface inst : attacker.getKeywords()) { + String kw = inst.getOriginal(); if (kw.startsWith("CantBeBlockedByAmount")) { restrictions.add(TextUtil.split(kw, ' ', 2)[1]); } @@ -1108,7 +1117,8 @@ public class CombatUtil { public static int getMinNumBlockersForAttacker(Card attacker, Player defender) { List restrictions = Lists.newArrayList(); - for (String kw : attacker.getKeywords()) { + for (KeywordInterface inst : attacker.getKeywords()) { + String kw = inst.getOriginal(); if (kw.startsWith("CantBeBlockedByAmount")) { restrictions.add(TextUtil.split(kw, ' ', 2)[1]); } diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index d59217f57a4..6d799a30713 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -10,6 +10,7 @@ import forge.game.Game; import forge.game.GameObject; import forge.game.ability.AbilityUtils; import forge.game.card.*; +import forge.game.keyword.KeywordInterface; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.spellability.AbilityActivated; @@ -266,7 +267,8 @@ public class CostAdjustment { private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) { String offeringType = ""; - for (String kw : sa.getHostCard().getKeywords()) { + for (KeywordInterface inst : sa.getHostCard().getKeywords()) { + final String kw = inst.getOriginal(); if (kw.endsWith(" offering")) { offeringType = kw.split(" ")[0]; break; diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 6c3b91e538f..82d52ad627f 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -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."), 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."), - 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."), 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."), @@ -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."), 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."), - 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."), 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."), @@ -38,7 +38,7 @@ public enum Keyword { 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}."), 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."), 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."), @@ -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."), 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."), - 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."), - 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."), 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."), - 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."), 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."), @@ -228,9 +228,8 @@ public enum Keyword { Set keywordSet = cardKeywordSetLookup.get(key); if (keywordSet == null) { keywordSet = new HashSet(); - List keywords = Card.getCardForUi(card).getKeywords(); - for (String k : keywords) { - Keyword keyword = Keyword.getInstance(k).getKeyword(); + for (KeywordInterface inst : Card.getCardForUi(card).getKeywords()) { + final Keyword keyword = inst.getKeyword(); if (keyword != Keyword.UNDEFINED) { keywordSet.add(keyword); } diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java index 04d2dcd7c34..4074202753f 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java @@ -10,11 +10,22 @@ import com.google.common.collect.MultimapBuilder; public class KeywordCollection implements Iterable, Serializable { private static final long serialVersionUID = -2882986558147844702L; + + private boolean hidden = false; private transient KeywordCollectionView view; private final Multimap map = MultimapBuilder.enumKeys(Keyword.class) .arrayListValues().build(); + public KeywordCollection() { + super(); + this.hidden = false; + } + public KeywordCollection(boolean hidden) { + super(); + this.hidden = hidden; + } + public boolean contains(Keyword keyword) { return map.containsKey(keyword); } @@ -35,8 +46,15 @@ public class KeywordCollection implements Iterable, Serializable { return amount; } - public boolean add(String k) { + public KeywordInterface add(String 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(); Collection list = map.get(keyword); if (list.isEmpty() || !keyword.isMultipleRedundant) { @@ -44,6 +62,7 @@ public class KeywordCollection implements Iterable, Serializable { return true; } return false; + } public void addAll(Iterable keywords) { @@ -52,6 +71,16 @@ public class KeywordCollection implements Iterable, Serializable { } } + public boolean insertAll(Iterable inst) { + boolean result = false; + for (KeywordInterface k : inst) { + if (insert(k)) { + result = true; + } + } + return result; + } + public boolean remove(String keyword) { Iterator it = map.values().iterator(); @@ -67,10 +96,14 @@ public class KeywordCollection implements Iterable, Serializable { return result; } - public void removeAll(Iterable keywords) { + public boolean removeAll(Iterable keywords) { + boolean result = false; for (String k : keywords) { - remove(k); + if (remove(k)) { + result = true; + } } + return result; } public void clear() { diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java index 655052b3ba3..2b0b89d5708 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java @@ -18,6 +18,9 @@ public abstract class KeywordInstance> implements K private Keyword keyword; private String original; + + private boolean hidden; + private List triggers = Lists.newArrayList(); private List replacements = Lists.newArrayList(); private List abilities = Lists.newArrayList(); @@ -106,4 +109,19 @@ public abstract class KeywordInstance> implements K 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; + } } diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java index 09fb427dbba..cbe729b504f 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java @@ -16,6 +16,9 @@ public interface KeywordInterface { int getAmount(); + boolean getHidden(); + void setHidden(boolean val); + void addKeywords(final Card host, final boolean intrinsic); public void addTrigger(final Trigger trg); diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java b/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java index 1123edce082..1944c562c05 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordsChange.java @@ -17,16 +17,12 @@ */ package forge.game.keyword; +import java.util.Collection; import java.util.List; import com.google.common.collect.Lists; 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; /** *

@@ -38,8 +34,9 @@ import forge.game.trigger.Trigger; */ public class KeywordsChange { private final KeywordCollection keywords = new KeywordCollection(); - private final List removeKeywords; - private final boolean removeAllKeywords; + private final List removeKeywordInterfaces = Lists.newArrayList(); + private final List removeKeywords = Lists.newArrayList(); + private boolean removeAllKeywords; /** * @@ -49,12 +46,33 @@ public class KeywordsChange { * @param removeKeywordList the list of keywords to remove. * @param removeAll whether to remove all keywords. */ - public KeywordsChange(final Iterable keywordList, final List removeKeywordList, final boolean removeAll) { + public KeywordsChange( + final Iterable keywordList, + final Collection removeKeywordList, + final boolean removeAll) { if (keywordList != null) { this.keywords.addAll(keywordList); } - this.removeKeywords = removeKeywordList == null ? Lists.newArrayList() : Lists.newArrayList(removeKeywordList); + if (removeKeywordList != null) { + this.removeKeywords.addAll(removeKeywordList); + } + + this.removeAllKeywords = removeAll; + } + + public KeywordsChange( + final Collection keywordList, + final Collection removeKeywordInterfaces, + final boolean removeAll) { + if (keywordList != null) { + this.keywords.insertAll(keywordList); + } + + if (removeKeywordInterfaces != null) { + this.removeKeywordInterfaces.addAll(removeKeywordInterfaces); + } + this.removeAllKeywords = removeAll; } @@ -64,8 +82,8 @@ public class KeywordsChange { * * @return ArrayList */ - public final List getKeywords() { - return Lists.newArrayList(this.keywords); + public final Collection getKeywords() { + return this.keywords.getValues(); } /** @@ -108,4 +126,39 @@ public class KeywordsChange { 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 keywordList, + final Collection removeKeywordList, + final boolean removeAll) { + KeywordsChange result = new KeywordsChange(keywordList, removeKeywordList, removeAll); + result.__merge(this); + return result; + } + + public final KeywordsChange merge( + final Iterable keywordList, + final Collection 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; + } + } } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 8d2e61728c3..b0906223738 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -31,6 +31,7 @@ import forge.game.card.*; import forge.game.card.CardPredicates.Presets; import forge.game.event.*; import forge.game.keyword.KeywordCollection; +import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordCollection.KeywordCollectionView; import forge.game.keyword.KeywordsChange; import forge.game.mana.ManaPool; @@ -236,7 +237,7 @@ public class Player extends GameEntity implements Comparable { game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); activeScheme = getZone(ZoneType.SchemeDeck).get(0); // gameAction moveTo ? - game.getAction().moveTo(ZoneType.Command, activeScheme, null); + game.getAction().moveTo(ZoneType.Command, activeScheme, null, Maps.newHashMap()); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); // Run triggers @@ -971,12 +972,10 @@ public class Player extends GameEntity implements Comparable { public final void addChangedKeywords(final List addKeywords, final List removeKeywords, final Long timestamp) { // if the key already exists - merge entries if (changedKeywords.containsKey(timestamp)) { - final List kws = addKeywords == null ? Lists.newArrayList() : Lists.newArrayList(addKeywords); - final List rkws = removeKeywords == null ? Lists.newArrayList() : Lists.newArrayList(removeKeywords); 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(); return; } @@ -1011,8 +1010,8 @@ public class Player extends GameEntity implements Comparable { private final void replaceAllKeywordInstances(final String oldKeyword, final String newKeyword) { boolean keywordReplaced = false; for (final KeywordsChange ck : changedKeywords.values()) { - if (ck.getKeywords().remove(oldKeyword)) { - ck.getKeywords().add(newKeyword); + if (ck.removeKeywordfromAdd(oldKeyword)) { + ck.addKeyword(newKeyword); keywordReplaced = true; } } @@ -1035,7 +1034,7 @@ public class Player extends GameEntity implements Comparable { boolean keywordRemoved = false; for (final KeywordsChange ck : changedKeywords.values()) { - if (ck.getKeywords().remove(keyword)) { + if (ck.removeKeywordfromAdd(keyword)) { keywordRemoved = true; if (!allInstances) { break; @@ -1074,7 +1073,7 @@ public class Player extends GameEntity implements Comparable { } if (ck.getKeywords() != null) { - keywords.addAll(ck.getKeywords()); + keywords.insertAll(ck.getKeywords()); } } view.updateKeywords(this); @@ -1202,7 +1201,7 @@ public class Player extends GameEntity implements Comparable { if (toBottom != null) { for(Card c : toBottom) { - getGame().getAction().moveToBottomOfLibrary(c, null); + getGame().getAction().moveToBottomOfLibrary(c, null, Maps.newHashMap()); numToBottom++; } } @@ -1210,7 +1209,7 @@ public class Player extends GameEntity implements Comparable { if (toTop != null) { Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus. for(Card c : toTop) { - getGame().getAction().moveToLibrary(c, null); + getGame().getAction().moveToLibrary(c, null, Maps.newHashMap()); numToTop++; } } @@ -1283,7 +1282,7 @@ public class Player extends GameEntity implements Comparable { } } - c = game.getAction().moveToHand(c, null); + c = game.getAction().moveToHand(c, null, Maps.newHashMap()); drawn.add(c); if (topCardRevealed) { @@ -1441,7 +1440,8 @@ public class Player extends GameEntity implements Comparable { } 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")) { return Integer.parseInt("" + s.charAt(s.length() - 1)); } @@ -1492,16 +1492,16 @@ public class Player extends GameEntity implements Comparable { sb.append(this).append(" discards ").append(c); final Card newCard; if (discardToTopOfLibrary) { - newCard = game.getAction().moveToLibrary(c, 0, sa); + newCard = game.getAction().moveToLibrary(c, 0, sa, Maps.newHashMap()); sb.append(" to the library"); // Play the Discard sound } else if (discardMadness) { - newCard = game.getAction().exile(c, sa); + newCard = game.getAction().exile(c, sa, Maps.newHashMap()); sb.append(" with Madness"); } else { - newCard = game.getAction().moveToGraveyard(c, sa); + newCard = game.getAction().moveToGraveyard(c, sa, Maps.newHashMap()); // Play the Discard sound } sb.append("."); @@ -1569,7 +1569,7 @@ public class Player extends GameEntity implements Comparable { } for (Card m : milledView) { - game.getAction().moveTo(destination, m, null); + game.getAction().moveTo(destination, m, null, Maps.newHashMap()); } return milled; @@ -1637,7 +1637,7 @@ public class Player extends GameEntity implements Comparable { if (land.isFaceDown()) { land.turnFaceUp(); } - game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null); + game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null, Maps.newHashMap()); // play a sound game.fireEvent(new GameEventLandPlayed(this, land)); @@ -2419,7 +2419,7 @@ public class Player extends GameEntity implements Comparable { game.getView().updatePlanarPlayer(getView()); 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.Command).add(c); } @@ -2449,7 +2449,7 @@ public class Player extends GameEntity implements Comparable { //game.getZoneOf(plane).remove(plane); plane.clearControllers(); //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(); diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 0831b985db6..ab448c3b528 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -37,6 +37,7 @@ import forge.game.cost.Cost; import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; import forge.game.cost.CostPayment; +import forge.game.keyword.KeywordInterface; import forge.game.mana.ManaPool; import forge.game.player.Player; import forge.game.player.PlayerController; @@ -133,7 +134,8 @@ public class HumanPlaySpellAbility { final Map params = Maps.newHashMap(); params.put("ManaColorConversion", "Additive"); - for (String keyword : c.getKeywords()) { + for (KeywordInterface inst : c.getKeywords()) { + String keyword = inst.getOriginal(); if (keyword.startsWith("ManaConvert")) { final String[] k = keyword.split(":"); params.put(k[1] + "Conversion", k[2]);