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;
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;

View File

@@ -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<String> copiedKeywords = pumped.getKeywords();
List<String> toCopy = new ArrayList<String>();
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<KeywordInterface> 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<String>(), false, timestamp2);
pumped.addChangedCardKeywordsInternal(toCopy, Lists.<KeywordInterface>newArrayList(), false, timestamp2, true);
ComputerUtilCard.applyStaticContPT(ai.getGame(), pumped, new CardCollection(c));
return pumped;
}

View File

@@ -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;
/**
* <p>
@@ -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;

View File

@@ -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<Card, Integer> {
@@ -32,7 +34,8 @@ public class CreatureEvaluator implements Function<Card, Integer> {
}
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.")

View File

@@ -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];

View File

@@ -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()) {

View File

@@ -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];

View File

@@ -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<SpellAbility> 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();

View File

@@ -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);

View File

@@ -274,13 +274,13 @@ public class CloneEffect extends SpellAbilityEffect {
keywords.remove(k);
}
k = keywords.get(i);
tgtCard.addIntrinsicKeyword(k);
KeywordInterface inst = Keyword.getInstance(k);
KeywordInterface inst = tgtCard.addIntrinsicKeyword(k);
if (inst != null) {
inst.addKeywords(tgtCard, true);
}
}
}
// set ETB tapped of clone
if (sa.hasParam("IntoPlayTapped")) {

View File

@@ -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.<color>:Protection from <color>:*
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))

View File

@@ -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<String> choice = Lists.newArrayList();
List<String> total = Lists.newArrayList(keywords);
if (sa.hasParam("NoRepetition")) {
final List<String> 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);
}

View File

@@ -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<Card> {
private final CardDamageHistory damageHistory = new CardDamageHistory();
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
private final CopyOnWriteArrayList<String> 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<Card> {
private final CardChangedWords changedTextColors = new CardChangedWords();
private final CardChangedWords changedTextTypes = new CardChangedWords();
/** List of the keywords that have been added by text changes. */
private final List<String> keywordsGrantedByTextChanges = Lists.newArrayList();
private final List<KeywordInterface> keywordsGrantedByTextChanges = Lists.newArrayList();
/** Original values of SVars changed by text changes. */
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
public final String keywordsToText(final List<String> keywords) {
public final String keywordsToText(final Collection<KeywordInterface> keywords) {
final StringBuilder sb = 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(
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<Card> {
}
// format text changes
if (CardUtil.isKeywordModifiable(keyword)
&& keywordsGrantedByTextChanges.contains(keyword)) {
&& keywordsGrantedByTextChanges.contains(inst)) {
for (final Entry<String, String> e : textChanges) {
final String value = e.getValue();
if (keyword.contains(value)) {
@@ -1453,13 +1457,13 @@ public class Card extends GameEntity implements Comparable<Card> {
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<Card> {
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<Card> {
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<Card> {
|| 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<Card> {
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<Card> {
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<Card> {
}
}
// Add Keywords
final List<String> 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<Card> {
// 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<Card> {
|| (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<Card> {
|| (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<Card> {
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<Card> {
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<Card> {
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<Card> {
}
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<Card> {
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<Card> {
}
// keywords are like flying, fear, first strike, etc...
public final List<String> getKeywords() {
public final List<KeywordInterface> getKeywords() {
return getKeywords(currentState);
}
public final List<String> getKeywords(CardState state) {
public final List<KeywordInterface> 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<String> visitor) {
public final void visitKeywords(CardState state, Visitor<KeywordInterface> visitor) {
visitUnhiddenKeywords(state, visitor);
visitHiddenExtreinsicKeywords(visitor);
}
@@ -3263,6 +3270,7 @@ public class Card extends GameEntity implements Comparable<Card> {
addChangedCardKeywords(keywords, removeKeywords, removeAllKeywords, timestamp, true);
}
public final void addChangedCardKeywords(final List<String> keywords, final List<String> removeKeywords,
final boolean removeAllKeywords, final long timestamp, final boolean updateView) {
keywords.removeAll(getCantHaveOrGainKeyword());
@@ -3270,13 +3278,31 @@ public class Card extends GameEntity implements Comparable<Card> {
final KeywordsChange cks = changedCardKeywords.get(timestamp);
if (cks != null) {
cks.removeKeywords(this);
List<String> kws = Lists.newArrayList(keywords);
List<String> 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);
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<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);
changedCardKeywords.put(timestamp, newCks);
}
@@ -3322,13 +3348,16 @@ public class Card extends GameEntity implements Comparable<Card> {
}
// Hidden keywords will be left out
public final List<String> getUnhiddenKeywords() {
public final Collection<KeywordInterface> getUnhiddenKeywords() {
return getUnhiddenKeywords(currentState);
}
public final List<String> getUnhiddenKeywords(CardState state) {
final List<String> keywords = Lists.newArrayList();
Iterables.addAll(keywords, state.getIntrinsicKeywords());
keywords.addAll(extrinsicKeyword);
public final Collection<KeywordInterface> getUnhiddenKeywords(CardState state) {
KeywordCollection keywords = new KeywordCollection();
//final List<KeywordInterface> 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<Card> {
}
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()) {
// 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<Card> {
return;
}
final List<String> addKeywords = Lists.newArrayList();
final List<String> removeKeywords = Lists.newArrayList(keywordsGrantedByTextChanges);
final List<KeywordInterface> addKeywords = Lists.newArrayList();
final List<KeywordInterface> 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<Card> {
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<String> s) {
@@ -3515,11 +3548,16 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
public List<String> getExtrinsicKeyword() {
return extrinsicKeyword;
public Collection<KeywordInterface> getExtrinsicKeyword() {
return extrinsicKeyword.getValues();
}
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) {
@@ -3560,31 +3598,38 @@ public class Card extends GameEntity implements Comparable<Card> {
}
// Hidden Keywords will be returned without the indicator HIDDEN
public final List<String> getHiddenExtrinsicKeywords() {
public final List<KeywordInterface> getHiddenExtrinsicKeywords() {
ListKeywordVisitor visitor = new ListKeywordVisitor();
visitHiddenExtreinsicKeywords(visitor);
return visitor.getKeywords();
}
private void visitHiddenExtreinsicKeywords(Visitor<String> visitor) {
for (String keyword : hiddenExtrinsicKeyword) {
if (keyword == null) {
continue;
}
if (keyword.startsWith("HIDDEN")) {
keyword = keyword.substring(7);
}
visitor.visit(keyword);
private void visitHiddenExtreinsicKeywords(Visitor<KeywordInterface> 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 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)) {
view.updateNonAbilityText(this);
currentState.getView().updateKeywords(this, currentState);
@@ -3817,19 +3862,6 @@ public class Card extends GameEntity implements Comparable<Card> {
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) {
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) {
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<Card> {
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,11 +4871,10 @@ public class Card extends GameEntity implements Comparable<Card> {
return true;
}
final List<String> keywords = getKeywords();
if (keywords != null) {
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
for (final String kw : keywords) {
for (final KeywordInterface inst : getKeywords()) {
String kw = inst.getOriginal();
if (!kw.startsWith("Protection")) {
continue;
}
@@ -4926,7 +4959,6 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
}
}
return false;
}
@@ -4983,13 +5015,13 @@ public class Card extends GameEntity implements Comparable<Card> {
final Card source = sa.getHostCard();
final MutableBoolean result = new MutableBoolean(true);
visitKeywords(currentState, new Visitor<String>() {
visitKeywords(currentState, new Visitor<KeywordInterface>() {
@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,15 +5063,19 @@ public class Card extends GameEntity implements Comparable<Card> {
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(":");
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<Card> {
}
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<Card> {
}
// 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 int count;
private boolean startOf;
@@ -5478,7 +5516,8 @@ public class Card extends GameEntity implements Comparable<Card> {
}
@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<Card> {
}
// Collects all the keywords into a list.
private static final class ListKeywordVisitor extends Visitor<String> {
private List<String> keywords = Lists.newArrayList();
private static final class ListKeywordVisitor extends Visitor<KeywordInterface> {
private List<KeywordInterface> keywords = Lists.newArrayList();
@Override
public void visit(String kw) {
public void visit(KeywordInterface kw) {
keywords.add(kw);
}
public List<String> getKeywords() {
public List<KeywordInterface> getKeywords() {
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
setSunburstValue(0); // Sunburst
}
}

View File

@@ -1861,7 +1861,8 @@ public class CardFactoryUtil {
final List<String> 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);
}

View File

@@ -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<String> getIntrinsicKeywords() {
return intrinsicKeywords;
public final Collection<KeywordInterface> 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<String> keywords) {
boolean changed = false;
for (String k : keywords) {
if (addIntrinsicKeyword(k)) {
if (addIntrinsicKeyword(k) != null) {
changed = true;
}
}
@@ -401,5 +409,13 @@ public class CardState extends GameObject {
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.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<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.baseToughness = c.getBaseToughness();
}

View File

@@ -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);

View File

@@ -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 <valid>
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 <valid>
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<String> 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<String> 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]);
}

View File

@@ -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;

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."),
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<Keyword> keywordSet = cardKeywordSetLookup.get(key);
if (keywordSet == null) {
keywordSet = new HashSet<Keyword>();
List<String> 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);
}

View File

@@ -11,10 +11,21 @@ import com.google.common.collect.MultimapBuilder;
public class KeywordCollection implements Iterable<String>, Serializable {
private static final long serialVersionUID = -2882986558147844702L;
private boolean hidden = false;
private transient KeywordCollectionView view;
private final Multimap<Keyword, KeywordInterface> 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<String>, 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<KeywordInterface> list = map.get(keyword);
if (list.isEmpty() || !keyword.isMultipleRedundant) {
@@ -44,6 +62,7 @@ public class KeywordCollection implements Iterable<String>, Serializable {
return true;
}
return false;
}
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) {
Iterator<KeywordInterface> it = map.values().iterator();
@@ -67,11 +96,15 @@ public class KeywordCollection implements Iterable<String>, Serializable {
return result;
}
public void removeAll(Iterable<String> keywords) {
public boolean removeAll(Iterable<String> keywords) {
boolean result = false;
for (String k : keywords) {
remove(k);
if (remove(k)) {
result = true;
}
}
return result;
}
public void clear() {
map.clear();

View File

@@ -18,6 +18,9 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
private Keyword keyword;
private String original;
private boolean hidden;
private List<Trigger> triggers = Lists.<Trigger>newArrayList();
private List<ReplacementEffect> replacements = Lists.<ReplacementEffect>newArrayList();
private List<SpellAbility> abilities = Lists.<SpellAbility>newArrayList();
@@ -106,4 +109,19 @@ public abstract class KeywordInstance<T extends 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;
}
}

View File

@@ -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);

View File

@@ -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;
/**
* <p>
@@ -38,8 +34,9 @@ import forge.game.trigger.Trigger;
*/
public class KeywordsChange {
private final KeywordCollection keywords = new KeywordCollection();
private final List<String> removeKeywords;
private final boolean removeAllKeywords;
private final List<KeywordInterface> removeKeywordInterfaces = Lists.newArrayList();
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 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) {
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;
}
@@ -64,8 +82,8 @@ public class KeywordsChange {
*
* @return ArrayList<String>
*/
public final List<String> getKeywords() {
return Lists.newArrayList(this.keywords);
public final Collection<KeywordInterface> 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<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.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<Player> {
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<Player> {
public final void addChangedKeywords(final List<String> addKeywords, final List<String> removeKeywords, final Long timestamp) {
// if the key already exists - merge entries
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);
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<Player> {
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<Player> {
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<Player> {
}
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<Player> {
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<Player> {
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<Player> {
}
}
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<Player> {
}
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<Player> {
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<Player> {
}
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<Player> {
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<Player> {
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<Player> {
//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();

View File

@@ -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<String, String> 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]);