- Updated tokens AI to check for static effects like Virulent Plague

This commit is contained in:
excessum
2016-05-07 04:41:34 +00:00
parent 30c6a30bd4
commit 17d75edc35
2 changed files with 92 additions and 76 deletions

View File

@@ -173,10 +173,16 @@ public class DestroyAi extends SpellAbilityAi {
} }
} }
if ("Pongify".equals(logic)) { if ("Pongify".equals(logic)) {
Card token = TokenAi.spawnToken(ai.getOpponent(), sa.getSubAbility()); final Card token = TokenAi.spawnToken(ai.getOpponent(), sa.getSubAbility());
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || //prevent surprise combatant if (token == null) {
ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) { return true; // becomes Terminate
return false; } else {
if (source.getGame().getPhaseHandler().getPhase()
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
ComputerUtilCard.evaluateCreature(choice) < 1.5
* ComputerUtilCard.evaluateCreature(token)) {
return false;
}
} }
} }
} }

View File

@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -22,11 +23,13 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostPart; import forge.game.cost.CostPart;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveCounter; import forge.game.cost.CostRemoveCounter;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
@@ -114,7 +117,7 @@ public class TokenAi extends SpellAbilityAi {
} }
} }
boolean sacOnStack = false; //check if a negative sacrifice effect is on stack boolean sacOnStack = false; // check if a negative sacrifice effect is on stack
if (!game.getStack().isEmpty()) { if (!game.getStack().isEmpty()) {
SpellAbility topStack = game.getStack().peekAbility(); SpellAbility topStack = game.getStack().peekAbility();
if (topStack.getApi() == ApiType.Sacrifice) { if (topStack.getApi() == ApiType.Sacrifice) {
@@ -122,20 +125,26 @@ public class TokenAi extends SpellAbilityAi {
} }
} }
boolean pwAbility = sa.getRestrictions().isPwAbility(); // Planeswalker-related flags
if (pwAbility) { boolean pwMinus = false;
// Planeswalker token ability with loyalty costs should be played in Main1 or it might boolean pwPlus = false;
// never be used due to other positive abilities. AI is kept from spamming them by the if (sa.getRestrictions().isPwAbility()) {
// loyalty cost of each usage. Zero/loyalty gain token abilities can be evaluated as /*
// per normal. * Planeswalker token ability with loyalty costs should be played in Main1 or it might
boolean hasCost = false; * never be used due to other positive abilities. AI is kept from spamming them by the
* loyalty cost of each usage. Zero/loyalty gain token abilities can be evaluated as
* per normal.
*/
for (CostPart c : sa.getPayCosts().getCostParts()) { for (CostPart c : sa.getPayCosts().getCostParts()) {
if (c instanceof CostRemoveCounter) { if (c instanceof CostRemoveCounter) {
hasCost = true; pwMinus = true;
break;
}
if (c instanceof CostPutCounter && c.convertAmount() > 0) {
pwPlus = true;
break; break;
} }
} }
pwAbility = hasCost;
} }
PhaseHandler ph = game.getPhaseHandler(); PhaseHandler ph = game.getPhaseHandler();
@@ -150,13 +159,13 @@ public class TokenAi extends SpellAbilityAi {
buff = true; buff = true;
} }
} }
if (!buff && !sacOnStack && !pwAbility) { if (!buff && !sacOnStack && !pwMinus) {
return false; return false;
} }
} }
if ((ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) if ((ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn") && !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn")
&& !SpellAbilityAi.isSorcerySpeed(sa) && !haste && !sacOnStack && !pwAbility) { && !SpellAbilityAi.isSorcerySpeed(sa) && !haste && !sacOnStack && !pwMinus) {
return false; return false;
} }
if ((ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN) || game.getPhaseHandler().isPlayerTurn(opp)) if ((ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN) || game.getPhaseHandler().isPlayerTurn(opp))
@@ -224,32 +233,6 @@ public class TokenAi extends SpellAbilityAi {
} }
} }
//interrupt sacrifice effect
if (sacOnStack) {
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
SpellAbility topStack = game.getStack().peekAbility();
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
String num = sa.getParam("Amount");
num = (num == null) ? "1" : num;
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
CardCollection list =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), opp, topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) { //only care about saving single creature for now
ComputerUtilCard.sortByEvaluateCreature(list);
Card token = spawnToken(ai, sa);
if (token != null) {
list.add(token);
list = CardLists.getValidCards(list, valid.split(","), ai.getOpponent(), topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
if (ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0))
&& list.contains(token)) {
return true;
}
}
}
}
if (this.tokenAmount.equals("X") || (this.tokenPower != null && this.tokenPower.equals("X")) || (this.tokenToughness != null && this.tokenToughness.equals("X"))) { if (this.tokenAmount.equals("X") || (this.tokenPower != null && this.tokenPower.equals("X")) || (this.tokenToughness != null && this.tokenToughness.equals("X"))) {
int x = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa); int x = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
if (source.getSVar("X").equals("Count$Converge")) { if (source.getSVar("X").equals("Count$Converge")) {
@@ -264,7 +247,40 @@ public class TokenAi extends SpellAbilityAi {
return false; return false;
} }
} }
final Card token = spawnToken(ai, sa);
if (token == null) {
final AbilitySub sub = sa.getSubAbility();
if (pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai))) {
return true; // planeswalker plus ability or sub-ability is useful
} else {
return false; // no token created
}
}
// interrupt sacrifice effect
if (sacOnStack) {
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
SpellAbility topStack = game.getStack().peekAbility();
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
String num = sa.getParam("Amount");
num = (num == null) ? "1" : num;
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
CardCollection list =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), opp, topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) { // only care about saving single creature for now
ComputerUtilCard.sortByEvaluateCreature(list);
list.add(token);
list = CardLists.getValidCards(list, valid.split(","), ai.getOpponent(), topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
if (ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0))
&& list.contains(token)) {
return true;
}
}
}
if (SpellAbilityAi.playReusable(ai, sa)) { if (SpellAbilityAi.playReusable(ai, sa)) {
return true; return true;
} }
@@ -318,13 +334,12 @@ public class TokenAi extends SpellAbilityAi {
* Create the token as a Card object. * Create the token as a Card object.
* @param ai owner of the new token * @param ai owner of the new token
* @param sa Token SpellAbility * @param sa Token SpellAbility
* @return token creature * @return token creature created by ability
*/ */
public static Card spawnToken(Player ai, SpellAbility sa) { public static Card spawnToken(Player ai, SpellAbility sa) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0]; String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0];
String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
String tokenPower = sa.getParam("TokenPower"); String tokenPower = sa.getParam("TokenPower");
String tokenToughness = sa.getParam("TokenToughness"); String tokenToughness = sa.getParam("TokenToughness");
String tokenName = sa.getParam("TokenName"); String tokenName = sa.getParam("TokenName");
@@ -376,10 +391,6 @@ public class TokenAi extends SpellAbilityAi {
final int finalPower = AbilityUtils.calculateAmount(host, tokenPower, sa); final int finalPower = AbilityUtils.calculateAmount(host, tokenPower, sa);
final int finalToughness = AbilityUtils.calculateAmount(host, tokenToughness, sa); final int finalToughness = AbilityUtils.calculateAmount(host, tokenToughness, sa);
final int finalAmount = AbilityUtils.calculateAmount(host, tokenAmount, sa);
if (finalAmount < 1) {
return null;
}
final String[] substitutedTypes = Arrays.copyOf(tokenTypes, tokenTypes.length); final String[] substitutedTypes = Arrays.copyOf(tokenTypes, tokenTypes.length);
for (int i = 0; i < substitutedTypes.length; i++) { for (int i = 0; i < substitutedTypes.length; i++) {
@@ -391,14 +402,16 @@ public class TokenAi extends SpellAbilityAi {
final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size())); final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size()));
final CardFactory.TokenInfo tokenInfo = new CardFactory.TokenInfo(substitutedName, imageName, final CardFactory.TokenInfo tokenInfo = new CardFactory.TokenInfo(substitutedName, imageName,
cost, substitutedTypes, tokenKeywords, finalPower, finalToughness); cost, substitutedTypes, tokenKeywords, finalPower, finalToughness);
final List<Card> tokens = CardFactory.makeToken(tokenInfo, ai); List<Card> tokens = CardFactory.makeToken(tokenInfo, ai);
if (tokens.isEmpty()) {
return null;
}
final Card c = tokens.get(0);
// Grant rule changes // Grant rule changes
if (tokenHiddenKeywords != null) { if (tokenHiddenKeywords != null) {
for (final String s : tokenHiddenKeywords) { for (final String s : tokenHiddenKeywords) {
for (final Card c : tokens) { c.addHiddenExtrinsicKeyword(s);
c.addHiddenExtrinsicKeyword(s);
}
} }
} }
@@ -406,28 +419,21 @@ public class TokenAi extends SpellAbilityAi {
if (tokenAbilities != null) { if (tokenAbilities != null) {
for (final String s : tokenAbilities) { for (final String s : tokenAbilities) {
final String actualAbility = host.getSVar(s); final String actualAbility = host.getSVar(s);
for (final Card c : tokens) { final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c);
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, c); c.addSpellAbility(grantedAbility);
c.addSpellAbility(grantedAbility); // added ability to intrinsic list so copies and clones work
// added ability to intrinsic list so copies and clones work c.getCurrentState().addUnparsedAbility(actualAbility);
c.getCurrentState().addUnparsedAbility(actualAbility);
}
} }
} }
// Grant triggers // Grant triggers
if (tokenTriggers != null) { if (tokenTriggers != null) {
for (final String s : tokenTriggers) { for (final String s : tokenTriggers) {
final String actualTrigger = host.getSVar(s); final String actualTrigger = host.getSVar(s);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true);
for (final Card c : tokens) { final String ability = host.getSVar(parsedTrigger.getMapParams().get("Execute"));
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c));
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, c, true); c.addTrigger(parsedTrigger);
final String ability = host.getSVar(parsedTrigger.getMapParams().get("Execute"));
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, c));
c.addTrigger(parsedTrigger);
}
} }
} }
@@ -441,9 +447,7 @@ public class TokenAi extends SpellAbilityAi {
name = actualSVar.split(":")[0]; name = actualSVar.split(":")[0];
actualSVar = actualSVar.split(":")[1]; actualSVar = actualSVar.split(":")[1];
} }
for (final Card c : tokens) { c.setSVar(name, actualSVar);
c.setSVar(name, actualSVar);
}
} }
} }
@@ -451,12 +455,18 @@ public class TokenAi extends SpellAbilityAi {
if (tokenStaticAbilities != null) { if (tokenStaticAbilities != null) {
for (final String s : tokenStaticAbilities) { for (final String s : tokenStaticAbilities) {
final String actualAbility = host.getSVar(s); final String actualAbility = host.getSVar(s);
for (final Card c : tokens) { c.addStaticAbilityString(actualAbility);
c.addStaticAbilityString(actualAbility); c.addStaticAbility(actualAbility);
c.addStaticAbility(actualAbility);
}
} }
} }
return tokens.get(0);
// Apply static abilities and prune dead tokens
final Game game = ai.getGame();
ComputerUtilCard.applyStaticContPT(game, c, null);
if (c.getNetToughness() < 1) {
return null;
} else {
return c;
}
} }
} }