Card: refactor hidden keywords into Table Structure

This commit is contained in:
Hans Mackowiak
2021-09-12 15:48:34 +00:00
committed by Michael Kamensky
parent 83a7cc8a3e
commit bfc938be57
60 changed files with 424 additions and 420 deletions

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -42,7 +42,6 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
@@ -60,7 +59,7 @@ import forge.util.collect.FCollectionView;
* <p>
* ComputerUtil_Attack2 class.
* </p>
*
*
* @author Forge
* @version $Id$
*/
@@ -72,10 +71,10 @@ public class AiAttackController {
private List<Card> oppList; // holds human player creatures
private List<Card> myList; // holds computer creatures
private final Player ai;
private Player defendingOpponent;
private int aiAggression = 0; // added by Masher, how aggressive the ai is attack will be depending on circumstances
private final boolean nextTurn;
@@ -108,7 +107,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, Card attacker) {
this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
@@ -118,7 +117,7 @@ public class AiAttackController {
this.blockers = getPossibleBlockers(oppList, this.attackers);
this.nextTurn = false;
} // overloaded constructor to evaluate single specified attacker
public static List<Card> getOpponentCreatures(final Player defender) {
List<Card> defenders = new ArrayList<>(defender.getCreaturesInPlay());
Predicate<Card> canAnimate = new Predicate<Card>() {
@@ -133,7 +132,7 @@ public class AiAttackController {
}
for (SpellAbility sa : c.getSpellAbilities()) {
if (sa.getApi() == ApiType.Animate) {
if (ComputerUtilCost.canPayCost(sa, defender)
if (ComputerUtilCost.canPayCost(sa, defender)
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
defenders.add(animatedCopy);
@@ -143,7 +142,7 @@ public class AiAttackController {
}
return defenders;
}
public void removeBlocker(Card blocker) {
this.oppList.remove(blocker);
}
@@ -200,7 +199,7 @@ public class AiAttackController {
* <p>
* isEffectiveAttacker.
* </p>
*
*
* @param attacker
* a {@link forge.game.card.Card} object.
* @param combat
@@ -501,7 +500,7 @@ public class AiAttackController {
// Conservative prediction for vehicles: the AI tries to acknowledge the fact that
// at least one creature will tap to crew a blocking vehicle when predicting if an
// alpha strike for lethal is viable
int maxBlockersAfterCrew = remainingBlockers.size();
int maxBlockersAfterCrew = remainingBlockers.size();
for (Card c : this.blockers) {
CardTypeView cardType = c.getCurrentState().getType();
CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield);
@@ -514,7 +513,7 @@ public class AiAttackController {
} else if (c.getName().equals("Peacewalker Colossus")) {
// can activate other vehicles for {1}{W}
// TODO: the AI should ideally predict how many times it can activate
// for now, unless the opponent is tapped out, break at this point
// for now, unless the opponent is tapped out, break at this point
// and do not predict the blocker limit (which is safer)
if (!CardLists.filter(oppBattlefield, Predicates.and(CardPredicates.Presets.UNTAPPED, CardPredicates.Presets.LANDS)).isEmpty()) {
maxBlockersAfterCrew = Integer.MAX_VALUE;
@@ -600,7 +599,7 @@ public class AiAttackController {
maxBlockersAfterCrew--;
}
unblockedAttackers.addAll(remainingAttackers);
int trampleDamage = 0;
for (Card attacker : blockedAttackers) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
@@ -674,7 +673,7 @@ public class AiAttackController {
* <p>
* Getter for the field <code>attackers</code>.
* </p>
*
*
* @return a {@link forge.game.combat.Combat} object.
*/
public final void declareAttackers(final Combat combat) {
@@ -742,14 +741,9 @@ public class AiAttackController {
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
mustAttack = true;
} else {
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.")) {
mustAttack = true;
break;
}
// TODO move to static Ability
if (attacker.hasKeyword("CARDNAME attacks each combat if able.") || attacker.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
mustAttack = true;
}
}
if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) {
@@ -799,7 +793,7 @@ public class AiAttackController {
}
}
}
// Exalted
if (combat.getAttackers().isEmpty()) {
boolean exalted = ai.countExaltedBonus() > 2;
@@ -857,7 +851,7 @@ public class AiAttackController {
// examine the potential forces
final List<Card> nextTurnAttackers = new ArrayList<>();
int candidateCounterAttackDamage = 0;
final Player opp = this.defendingOpponent;
// get the potential damage and strength of the AI forces
final List<Card> candidateAttackers = new ArrayList<>();
@@ -1115,7 +1109,7 @@ public class AiAttackController {
* <p>
* getAttack.
* </p>
*
*
* @param c
* a {@link forge.game.card.Card} object.
* @return a int.
@@ -1134,7 +1128,7 @@ public class AiAttackController {
* <p>
* shouldAttack.
* </p>
*
*
* @param attacker
* a {@link forge.game.card.Card} object.
* @param defenders
@@ -1179,7 +1173,7 @@ public class AiAttackController {
}
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
// contains only the defender's blockers that can actually block the attacker
@@ -1201,13 +1195,9 @@ public class AiAttackController {
int defPower = CardLists.getTotalPower(validBlockers, true, false);
if (!hasCombatEffect) {
for (KeywordInterface inst : attacker.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.equals("Wither") || keyword.equals("Infect")
|| keyword.equals("Lifelink") || keyword.startsWith("Afflict")) {
hasCombatEffect = true;
break;
}
if (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)
|| attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT)) {
hasCombatEffect = true;
}
}
@@ -1262,7 +1252,7 @@ public class AiAttackController {
}
}
}
if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
canKillAllDangerous = false;
canBeKilled = true;
@@ -1272,7 +1262,7 @@ public class AiAttackController {
} else if ((canKillAllDangerous || !canBeKilled) && ComputerUtilCard.canBeBlockedProfitably(defendingOpponent, attacker)) {
canKillAllDangerous = false;
canBeKilled = true;
}
}
// if the creature cannot block and can kill all opponents they might as
// well attack, they do nothing staying back
@@ -1286,7 +1276,7 @@ public class AiAttackController {
return true;
}
if (numberOfPossibleBlockers > 2
if (numberOfPossibleBlockers > 2
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent))
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) {
canBeBlocked = true;
@@ -1416,7 +1406,7 @@ public class AiAttackController {
return exerters;
}
/**
* Find a protection type that will make an attacker unblockable.
* @param sa ability belonging to ApiType.Protection
@@ -1485,10 +1475,10 @@ public class AiAttackController {
CardCollection attSorted = new CardCollection(attackersLeft);
CardCollection attUnsafe = new CardCollection();
CardLists.sortByToughnessDesc(attSorted);
int i = numForcedAttackers;
int refPowerValue = 0; // Aggro profiles do not account for the possible blockers' power, conservative profiles do.
if (!playAggro && this.blockers.size() > 0) {
// Conservative play: check to ensure that the card can't be killed off while damaged
// TODO: currently sorting a copy of this.blockers, but it looks safe to operate on this.blockers directly?
@@ -1498,7 +1488,7 @@ public class AiAttackController {
CardLists.sortByPowerDesc(blkSorted);
refPowerValue += blkSorted.get(0).getCurrentPower();
}
for (Card cre : attSorted) {
i++;
if (i + refPowerValue >= cre.getCurrentToughness()) {
@@ -1507,7 +1497,7 @@ public class AiAttackController {
continue;
}
}
attackersLeft.removeAll(attUnsafe);
}

View File

@@ -38,6 +38,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
@@ -183,9 +184,7 @@ public class AiBlockController {
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|| attacker.hasKeyword(Keyword.MENACE)) {
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
continue;
}
@@ -296,8 +295,7 @@ public class AiBlockController {
// 6. Blockers that don't survive until the next turn anyway
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
continue;
}
@@ -323,7 +321,17 @@ public class AiBlockController {
attackersLeft = (new ArrayList<>(currentAttackers));
}
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
private Predicate<Card> rampagesOrNeedsManyToBlock(final Combat combat) {
return Predicates.or(CardPredicates.hasKeyword(Keyword.RAMPAGE), new Predicate<Card>() {
@Override
public boolean apply(Card input) {
// select creature that has a max blocker
return StaticAbilityCantAttackBlock.getMinMaxBlocker(input, combat.getDefenderPlayerByAttacker(input)).getRight() < Integer.MAX_VALUE;
}
});
}
// Good Gang Blocks means a good trade or no trade
/**
@@ -334,7 +342,7 @@ public class AiBlockController {
* @param combat a {@link forge.game.combat.Combat} object.
*/
private void makeGangBlocks(final Combat combat) {
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
List<Card> blockers;
// Try to block an attacker without first strike with a gang of first strikers
@@ -528,7 +536,7 @@ public class AiBlockController {
// Try to block a Menace attacker with two blockers, neither of which will die
for (final Card attacker : attackersLeft) {
if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) {
continue;
}
@@ -584,9 +592,7 @@ public class AiBlockController {
List<Card> killingBlockers;
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
continue;
}
if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
@@ -635,10 +641,8 @@ public class AiBlockController {
Card attacker = attackers.get(0);
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|| attacker.hasKeyword(Keyword.MENACE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
attackers.remove(0);
makeChumpBlocks(combat, attackers);
@@ -686,9 +690,7 @@ public class AiBlockController {
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
&& !attacker.hasKeyword(Keyword.MENACE)
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) {
continue;
}
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
@@ -720,14 +722,14 @@ public class AiBlockController {
List<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : tramplingAttackers) {
if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) && !combat.isBlocked(attacker))
if (((StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) && !combat.isBlocked(attacker))
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
@@ -751,7 +753,7 @@ public class AiBlockController {
private void reinforceBlockersToKill(final Combat combat) {
List<Card> safeBlockers;
List<Card> blockers;
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
// TODO - should check here for a "rampage-like" trigger that replaced
// the keyword: "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
@@ -1076,8 +1078,7 @@ public class AiBlockController {
}
// assign blockers that have to block
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able.");
// if an attacker with lure attacks - all that can block
for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
@@ -1091,7 +1092,6 @@ public class AiBlockController {
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker);
if (blocker.getMustBlockCards() != null) {

View File

@@ -1654,10 +1654,11 @@ public class ComputerUtilCard {
Card pumped = CardFactory.copyCard(c, false);
pumped.setSickness(c.hasSickness());
final long timestamp = c.getGame().getNextTimestamp();
final List<String> kws = new ArrayList<>();
final List<String> kws = Lists.newArrayList();
final List<String> hiddenKws = Lists.newArrayList();
for (String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
pumped.addHiddenExtrinsicKeyword(kw);
hiddenKws.add(kw.substring(7));
} else {
kws.add(kw);
}
@@ -1686,7 +1687,13 @@ public class ComputerUtilCard {
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
pumped.setPTBoost(c.getPTBoostTable());
pumped.addPTBoost(power + berserkPower, toughness, timestamp, 0);
pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
if (!kws.isEmpty()) {
pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
}
if (!hiddenKws.isEmpty()) {
pumped.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws);
}
Set<CounterType> types = c.getCounters().keySet();
for(CounterType ct : types) {
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, sa, true, null);
@@ -1699,14 +1706,10 @@ public class ComputerUtilCard {
KeywordCollection copiedKeywords = new KeywordCollection();
copiedKeywords.insertAll(pumped.getKeywords());
List<KeywordInterface> toCopy = Lists.newArrayList();
for (KeywordInterface k : c.getKeywords()) {
for (KeywordInterface k : c.getUnhiddenKeywords()) {
KeywordInterface copiedKI = k.copy(c, true);
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
if (copiedKI.getHidden()) {
pumped.addHiddenExtrinsicKeyword(copiedKI);
} else {
toCopy.add(copiedKI);
}
toCopy.add(copiedKI);
}
}
final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used?
@@ -1744,7 +1747,7 @@ public class ComputerUtilCard {
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
continue;
}
if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
if (!stAb.matchesValidParam("Affected", vCard)) {
continue;
}
int att = 0;

View File

@@ -110,7 +110,17 @@ public class ComputerUtilCombat {
return false;
}
for (final KeywordInterface inst : attacker.getKeywords()) {
// TODO replace with Static Ability
for (final String keyword : attacker.getHiddenExtrinsicKeywords()) {
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1];
final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
if (!defender.equals(player)) {
return false;
}
}
}
for (final KeywordInterface inst : attacker.getKeywords(Keyword.UNDEFINED)) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1];

View File

@@ -8,7 +8,6 @@ import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.cost.CostPayEnergy;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility;
public class CreatureEvaluator implements Function<Card, Integer> {
@@ -35,16 +34,15 @@ public class CreatureEvaluator implements Function<Card, Integer> {
}
int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c);
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.")
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
power = 0;
break;
}
// TODO replace with ReplacementEffect checks
if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.")
|| c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")
|| c.hasKeyword("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| c.hasKeyword("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
power = 0;
}
if (considerPT) {
value += addValue(power * 15, "power");
value += addValue(toughness * 10, "toughness: " + toughness);
@@ -157,8 +155,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
}
if (c.hasKeyword("CARDNAME can't block.")) {
value -= subValue(10, "cant-block");
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
} else if (c.hasKeyword("CARDNAME attacks each combat if able.")) {
value -= subValue(10, "must-attack");
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
value -= subValue(10, "must-attack-player");

View File

@@ -16,7 +16,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.AiAttackController;
import forge.ai.AiBlockController;
import forge.ai.AiCardMemory;
import forge.ai.AiController;
import forge.ai.AiProps;

View File

@@ -24,7 +24,6 @@ import forge.game.card.CardFactory;
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;
@@ -295,8 +294,8 @@ public class GameCopier {
newCard.setChangedCardNames(c.getChangedCardNames());
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
newCard.addHiddenExtrinsicKeyword(kw);
//for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
// newCard.addHiddenExtrinsicKeyword(kw);
if (c.isTapped()) {
newCard.setTapped(true);
}