diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 9c1cc7cd8aa..2b97b75890d 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1646,7 +1646,8 @@ public class AiController { } private final SpellAbility getSpellAbilityToPlay() { - final CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); + CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); + cards = ComputerUtilCard.dedupeCards(cards); List saList = Lists.newArrayList(); SpellAbility top = null; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index a20541c243b..fb2b0eddda3 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -2052,6 +2052,25 @@ public class ComputerUtilCard { return false; } + public static CardCollection dedupeCards(CardCollection cc) { + CardCollection deduped = new CardCollection(); + for (Card c : cc) { + boolean unique = true; + if (c.isInZone(ZoneType.Hand)) { + for (Card d : deduped) { + if (d.isInZone(ZoneType.Hand) && d.getOwner().equals(c.getOwner()) && d.getName().equals(c.getName())) { + unique = false; + break; + } + } + } + if (unique) { + deduped.add(c); + } + } + return deduped; + } + // Determine if the AI has an AI:RemoveDeck:All or an AI:RemoveDeck:Random hint specified. // Includes a NPE guard on getRules() which might otherwise be tripped on some cards (e.g. tokens). public static boolean isCardRemAIDeck(final Card card) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 9c2a70d78dd..a1428d824b3 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -200,8 +200,8 @@ public class ComputerUtilCombat { return 0; } - damage += predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities); if (!attacker.hasKeyword(Keyword.INFECT)) { + damage += predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities); sum = predictDamageTo(attacked, damage, attacker, true); if (attacker.hasDoubleStrike()) { sum *= 2; @@ -2480,8 +2480,7 @@ public class ComputerUtilCombat { // intern toxic effect poison += attacker.getKeywordMagnitude(Keyword.TOXIC); } - if (attacker.hasDoubleStrike()) - { + if (attacker.hasDoubleStrike()) { poison *= 2; } return poison; diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java index 56e456da584..6fa75e8fcab 100644 --- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java @@ -116,6 +116,9 @@ public class CreatureEvaluator implements Function { else if (c.hasKeyword(Keyword.WITHER)) { value += addValue(power * 10, "Wither"); } + else if (c.hasKeyword(Keyword.TOXIC)) { + value += addValue(power * 10, "Toxic"); + } value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage"); value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict"); } diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 49d9697ce9f..1054469a22d 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -1066,9 +1066,7 @@ public abstract class GameState { } top.addMergedCard(bottom); - if (top.getMutatedTimestamp() != -1) { - top.removeCloneState(top.getMutatedTimestamp()); - } + top.removeMutatedStates(); final long ts = game.getNextTimestamp(); top.setMutatedTimestamp(ts); diff --git a/forge-ai/src/main/java/forge/ai/simulation/MultiTargetSelector.java b/forge-ai/src/main/java/forge/ai/simulation/MultiTargetSelector.java index 5e09ddb8f9a..00e5ad45e48 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/MultiTargetSelector.java +++ b/forge-ai/src/main/java/forge/ai/simulation/MultiTargetSelector.java @@ -73,7 +73,7 @@ public class MultiTargetSelector { public void reset() { for (PossibleTargetSelector selector : selectors) { - selector.reset(); + selector.reset(); } currentIndex = -1; } diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index e02e90c6be6..d570d012de1 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -2,7 +2,6 @@ package forge.ai.simulation; import forge.util.MyRandom; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Random; import java.util.Set; @@ -10,6 +9,7 @@ import java.util.Set; import forge.ai.AiPlayDecision; import forge.ai.ComputerUtil; import forge.ai.ComputerUtilAbility; +import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCost; import forge.ai.ability.ChangeZoneAi; import forge.ai.ability.ExploreAi; @@ -66,8 +66,8 @@ public class SpellAbilityPicker { private List getCandidateSpellsAndAbilities() { CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); + cards = ComputerUtilCard.dedupeCards(cards); List all = ComputerUtilAbility.getSpellAbilities(cards, player); - HashMap landsDeDupe = new HashMap<>(); List candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player); int writeIndex = 0; for (int i = 0; i < candidateSAs.size(); i++) { @@ -75,16 +75,6 @@ public class SpellAbilityPicker { if (sa.isManaAbility()) { continue; } - // Skip identical lands. - if (sa instanceof LandAbility) { - Card land = sa.getHostCard(); - Card previousLand = landsDeDupe.get(sa.getHostCard().getName()); - if (previousLand != null && previousLand.getZone() == land.getZone() && - previousLand.getOwner() == land.getOwner()) { - continue; - } - landsDeDupe.put(land.getName(), land); - } sa.setActivatingPlayer(player, true); AiPlayDecision opinion = canPlayAndPayForSim(sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java index 75fd27dd8ba..155d5bb282c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java @@ -66,9 +66,7 @@ public class MutateEffect extends SpellAbilityEffect { } // First remove current mutated states - if (target.getMutatedTimestamp() != -1) { - target.removeCloneState(target.getMutatedTimestamp()); - } + target.removeMutatedStates(); // Now add all abilities from bottom cards final Long ts = game.getNextTimestamp(); target.setMutatedTimestamp(ts); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 108b89f684f..2968877d344 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1240,7 +1240,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public final void removeMutatedStates() { - if (getMutatedTimestamp() != -1) { + if (isMutated()) { removeCloneState(getMutatedTimestamp()); } } @@ -5801,6 +5801,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } return getCastSA().isMadness(); } + public boolean wasDiscarded() { return discarded; } public void setDiscarded(boolean state) { discarded = state; }