diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index def2726ee4a..a9c92d3becb 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -31,7 +31,6 @@ import forge.game.ability.effects.ProtectEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
-import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
@@ -125,8 +124,7 @@ public class AiAttackController {
if (sa.getApi() == ApiType.Animate) {
if (ComputerUtilCost.canPayCost(sa, defender)
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
- Card animatedCopy = CardUtil.getLKICopy(c);
- AnimateAi.becomeAnimated(animatedCopy, c.hasSickness(), sa);
+ Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
defenders.add(animatedCopy);
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
index 1352f458712..4a61579781e 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
@@ -10,7 +10,6 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
-import forge.game.card.CardUtil;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
@@ -328,9 +327,7 @@ public class ComputerUtilCost {
* - break board stall by racing with evasive vehicle
*/
if (sa.hasParam("Crew")) {
- //Card vehicle = CardFactory.copyCard(sa.getHostCard(), true);
- Card vehicle = CardUtil.getLKICopy(source);
- AnimateAi.becomeAnimated(vehicle, source.hasSickness(), sa);
+ Card vehicle = AnimateAi.becomeAnimated(source, sa);
final int vehicleValue = ComputerUtilCard.evaluateCreature(vehicle);
String type = part.getType();
String totalP = type.split("withTotalPowerGE")[1];
diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
index aad822f0394..a7ff3bd9068 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
@@ -1,6 +1,8 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
@@ -31,11 +33,12 @@ import forge.game.trigger.TriggerHandler;
import forge.game.zone.ZoneType;
import forge.util.collect.FCollectionView;
-import java.util.ArrayList;
+
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+
/**
*
* AbilityFactoryAnimate class.
@@ -87,8 +90,7 @@ public class AnimateAi extends SpellAbilityAi {
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
ComputerUtilCard.sortByEvaluateCreature(list);
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
- Card animatedCopy = CardUtil.getLKICopy(source);//CardFactory.copyCard(source, false);
- becomeAnimated(animatedCopy, source.hasSickness(), sa);
+ Card animatedCopy = becomeAnimated(source, sa);
list.add(animatedCopy);
list = CardLists.getValidCards(list, valid.split(","), ai.getOpponent(), topStack.getHostCard(),
topStack);
@@ -144,8 +146,8 @@ public class AnimateAi extends SpellAbilityAi {
if (null == tgt) {
final List defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
- boolean givesHaste = sa.hasParam("Keywords") && sa.getParam("Keywords").contains("Haste");
- for (final Card c : defined) {
+ boolean givesHaste = sa.hasParam("Keywords") && sa.getParam("Keywords").contains("Haste");
+ for (final Card c : defined) {
bFlag |= !c.isCreature() && !c.isTapped()
&& (!c.hasSickness() || givesHaste || !ph.isPlayerTurn(aiPlayer))
&& !c.isEquipping();
@@ -161,11 +163,11 @@ public class AnimateAi extends SpellAbilityAi {
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
}
if (sa.hasParam("Keywords")) {
- for (String keyword : sa.getParam("Keywords").split(" & ")) {
- if (!source.hasKeyword(keyword)) {
- bFlag = true;
- }
- }
+ for (String keyword : sa.getParam("Keywords").split(" & ")) {
+ if (!source.hasKeyword(keyword)) {
+ bFlag = true;
+ }
+ }
}
if (power + toughness > c.getCurrentPower() + c.getCurrentToughness()) {
bFlag = true;
@@ -173,8 +175,7 @@ public class AnimateAi extends SpellAbilityAi {
}
if (!SpellAbilityAi.isSorcerySpeed(sa) && !sa.hasParam("Permanent")) {
- Card animatedCopy = CardUtil.getLKICopy(c);
- AnimateAi.becomeAnimated(animatedCopy, c.hasSickness(), sa);
+ Card animatedCopy = becomeAnimated(c, sa);
if (ph.isPlayerTurn(aiPlayer)
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(aiPlayer, animatedCopy)) {
return false;
@@ -184,8 +185,8 @@ public class AnimateAi extends SpellAbilityAi {
return false;
}
}
- }
- return bFlag; // All of the defined stuff is animated, not very useful
+ }
+ return bFlag; // All of the defined stuff is animated, not very useful
} else {
sa.resetTargets();
if (!animateTgtAI(sa)) {
@@ -213,29 +214,105 @@ public class AnimateAi extends SpellAbilityAi {
if (sa.usesTargeting() && !animateTgtAI(sa) && !mandatory) {
return false;
}
-
- // Improve AI for triggers. If source is a creature with:
- // When ETB, sacrifice a creature. Check to see if the AI has something
- // to sacrifice
-
- // Eventually, we can call the trigger of ETB abilities with
- // not mandatory as part of the checks to cast something
- if (sa.hasParam("AITgts")) {
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- final Card animateSource = sa.getHostCard();
- CardCollectionView list = aiPlayer.getGame().getCardsIn(tgt.getZone());
- list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), animateSource, sa);
- CardCollection prefList = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(),
- animateSource);
- if (!prefList.isEmpty()) {
- CardLists.shuffle(prefList);
- sa.getTargets().add(prefList.getFirst());
- }
- }
return true;
}
private boolean animateTgtAI(final SpellAbility sa) {
+ final Player ai = sa.getActivatingPlayer();
+ final PhaseHandler ph = ai.getGame().getPhaseHandler();
+
+ final CardType types = new CardType();
+ if (sa.hasParam("Types")) {
+ types.addAll(Arrays.asList(sa.getParam("Types").split(",")));
+ }
+
+ // something is used for animate into creature
+ if (types.isCreature()) {
+ final TargetRestrictions tgt = sa.getTargetRestrictions();
+ final Card source = sa.getHostCard();
+ CardCollectionView list = ai.getGame().getCardsIn(tgt.getZone());
+ list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
+ // need to targetable
+ list = CardLists.getTargetableCards(list, sa);
+
+ // try to look for AI targets if able
+ if (sa.hasParam("AITgts")) {
+ CardCollection prefList = CardLists.getValidCards(list, sa.getParam("AITgts").split(","), ai, source, sa);
+ if(!prefList.isEmpty()) {
+ list = prefList;
+ }
+ }
+
+ // list is empty, no possible targets
+ if (list.isEmpty()) {
+ return false;
+ }
+
+ Map data = Maps.newHashMap();
+ for (final Card c : list) {
+ // don't use Permanent animate on something that would leave the field
+ if (c.hasSVar("EndOfTurnLeavePlay") && sa.hasParam("Permanent")) {
+ continue;
+ }
+
+ // if tapped it might not attack or block
+ if (c.isTapped()) {
+ continue;
+ }
+
+ // make Animated copy and evaluate it
+ final Card animatedCopy = becomeAnimated(c, sa);
+ int aValue = ComputerUtilCard.evaluateCreature(animatedCopy);
+
+ // animated creature has zero toughness, don't do that
+ if (animatedCopy.getNetToughness() <= 0) {
+ continue;
+ }
+
+ // if original is already a Creature,
+ // evaluate their value to check if it becomes better
+ if (c.isCreature()) {
+ int cValue = ComputerUtilCard.evaluateCreature(c);
+ if (cValue <= aValue)
+ continue;
+ }
+
+ // if its player turn,
+ // check if its Permanent or that creature would attack
+ if (ph.isPlayerTurn(ai)) {
+ if (!sa.hasParam("Permanent") &&
+ !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)) {
+ continue;
+ }
+ }
+
+ // store in map
+ data.put(c, aValue);
+ }
+
+ // data is empty, no good targets
+ if (data.isEmpty()) {
+ return false;
+ }
+
+ // get the best creature to be animated
+ List maxList = Lists.newArrayList();
+ int maxValue = 0;
+ for (final Map.Entry e : data.entrySet()) {
+ int v = e.getValue();
+ if (v > maxValue) {
+ maxValue = v;
+ maxList.clear();
+ }
+ maxList.add(e.getKey());
+ }
+
+ // select the worst of the best
+ final Card worst = ComputerUtilCard.getWorstAI(maxList);
+ sa.getTargets().add(worst);
+ return true;
+ }
+
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
// two are the only things
// that animate a target. Those can just use SVar:RemAIDeck:True until
@@ -244,6 +321,12 @@ public class AnimateAi extends SpellAbilityAi {
return false;
}
+ public static Card becomeAnimated(final Card card, final SpellAbility sa) {
+ final Card copy = CardUtil.getLKICopy(card);
+ becomeAnimated(copy, card.hasSickness(), sa);
+ return copy;
+ }
+
public static void becomeAnimated(final Card card, final boolean hasOriginalCardSickness, final SpellAbility sa) {
// duplicating AnimateEffect.resolve
final Card source = sa.getHostCard();
@@ -278,17 +361,17 @@ public class AnimateAi extends SpellAbilityAi {
types.add(source.getChosenType());
}
- final List keywords = new ArrayList();
+ final List keywords = Lists.newArrayList();
if (sa.hasParam("Keywords")) {
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
}
- final List removeKeywords = new ArrayList();
+ final List removeKeywords = Lists.newArrayList();
if (sa.hasParam("RemoveKeywords")) {
removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & ")));
}
- final List hiddenKeywords = new ArrayList();
+ final List hiddenKeywords = Lists.newArrayList();
if (sa.hasParam("HiddenKeywords")) {
hiddenKeywords.addAll(Arrays.asList(sa.getParam("HiddenKeywords").split(" & ")));
}
@@ -308,37 +391,37 @@ public class AnimateAi extends SpellAbilityAi {
if (colors.equals("ChosenColor")) {
tmpDesc = CardUtil.getShortColorsString(source.getChosenColors());
} else {
- tmpDesc = CardUtil.getShortColorsString(new ArrayList(Arrays.asList(colors.split(","))));
+ tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
}
}
final String finalDesc = tmpDesc;
// abilities to add to the animated being
- final List abilities = new ArrayList();
+ final List abilities = Lists.newArrayList();
if (sa.hasParam("Abilities")) {
abilities.addAll(Arrays.asList(sa.getParam("Abilities").split(",")));
}
// replacement effects to add to the animated being
- final List replacements = new ArrayList();
+ final List replacements = Lists.newArrayList();
if (sa.hasParam("Replacements")) {
replacements.addAll(Arrays.asList(sa.getParam("Replacements").split(",")));
}
// triggers to add to the animated being
- final List triggers = new ArrayList();
+ final List triggers = Lists.newArrayList();
if (sa.hasParam("Triggers")) {
triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(",")));
}
// static abilities to add to the animated being
- final List stAbs = new ArrayList();
+ final List stAbs = Lists.newArrayList();
if (sa.hasParam("staticAbilities")) {
stAbs.addAll(Arrays.asList(sa.getParam("staticAbilities").split(",")));
}
// sVars to add to the animated being
- final List sVars = new ArrayList();
+ final List sVars = Lists.newArrayList();
if (sa.hasParam("sVars")) {
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
}
@@ -401,7 +484,7 @@ public class AnimateAi extends SpellAbilityAi {
// TODO will all these abilities/triggers/replacements/etc. lead to
// memory leaks or unintended effects?
// remove abilities
- final List removedAbilities = new ArrayList();
+ final List removedAbilities = Lists.newArrayList();
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
boolean clearSpells = sa.hasParam("OverwriteSpells");
boolean removeAll = sa.hasParam("RemoveAllAbilities");
@@ -416,7 +499,7 @@ public class AnimateAi extends SpellAbilityAi {
}
// give abilities
- final List addedAbilities = new ArrayList();
+ final List addedAbilities = Lists.newArrayList();
if (abilities.size() > 0) {
for (final String s : abilities) {
final String actualAbility = source.getSVar(s);
@@ -427,7 +510,7 @@ public class AnimateAi extends SpellAbilityAi {
}
// Grant triggers
- final List addedTriggers = new ArrayList();
+ final List addedTriggers = Lists.newArrayList();
if (triggers.size() > 0) {
for (final String s : triggers) {
final String actualTrigger = source.getSVar(s);
@@ -437,7 +520,7 @@ public class AnimateAi extends SpellAbilityAi {
}
// give replacement effects
- final List addedReplacements = new ArrayList();
+ final List addedReplacements = Lists.newArrayList();
if (replacements.size() > 0) {
for (final String s : replacements) {
final String actualReplacement = source.getSVar(s);
@@ -448,7 +531,7 @@ public class AnimateAi extends SpellAbilityAi {
}
// suppress triggers from the animated card
- final List removedTriggers = new ArrayList();
+ final List removedTriggers = Lists.newArrayList();
if (sa.hasParam("OverwriteTriggers") || removeAll) {
final FCollectionView triggersToRemove = card.getTriggers();
for (final Trigger trigger : triggersToRemove) {
@@ -486,7 +569,7 @@ public class AnimateAi extends SpellAbilityAi {
}
// suppress static abilities from the animated card
- final List removedStatics = new ArrayList();
+ final List removedStatics = Lists.newArrayList();
if (sa.hasParam("OverwriteStatics") || removeAll) {
final FCollectionView staticsToRemove = card.getStaticAbilities();
for (final StaticAbility stAb : staticsToRemove) {
@@ -496,7 +579,7 @@ public class AnimateAi extends SpellAbilityAi {
}
// suppress static abilities from the animated card
- final List removedReplacements = new ArrayList();
+ final List removedReplacements = Lists.newArrayList();
if (sa.hasParam("OverwriteReplacements") || removeAll) {
for (final ReplacementEffect re : card.getReplacementEffects()) {
re.setTemporarilySuppressed(true);