Separate AI decision making from SpellAbility classes

This commit is contained in:
Maxmtg
2014-02-09 20:05:05 +00:00
parent b3f019420f
commit 06484f81f6
28 changed files with 563 additions and 699 deletions

1
.gitattributes vendored
View File

@@ -128,6 +128,7 @@ forge-game/src/main/java/forge/ai/AiAttackController.java svneol=native#text/pla
forge-game/src/main/java/forge/ai/AiBlockController.java svneol=native#text/plain
forge-game/src/main/java/forge/ai/AiController.java svneol=native#text/plain
forge-game/src/main/java/forge/ai/AiCostDecision.java -text
forge-game/src/main/java/forge/ai/AiPlayDecision.java -text
forge-game/src/main/java/forge/ai/AiProfileUtil.java -text
forge-game/src/main/java/forge/ai/AiProps.java -text
forge-game/src/main/java/forge/ai/ComputerUtil.java svneol=native#text/plain

View File

@@ -17,39 +17,69 @@
*/
package forge.ai;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.ability.SpellApiBased;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.*;
import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellPermanent;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.Expressions;
import forge.util.MyRandom;
import java.util.*;
import java.util.Map.Entry;
/**
* <p>
* ComputerAI_General class.
@@ -137,7 +167,7 @@ public class AiController {
for (final SpellAbility sa : c.getNonManaSpellAbilities()) {
if (sa instanceof SpellPermanent) {
sa.setActivatingPlayer(player);
if (SpellPermanent.checkETBEffects(c, sa, ApiType.Counter, player)) {
if (checkETBEffects(c, sa, ApiType.Counter)) {
spellAbilities.add(sa);
}
}
@@ -146,6 +176,156 @@ public class AiController {
return spellAbilities;
}
public boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api) {
boolean rightapi = false;
if (card.isCreature()
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) {
return api == null;
}
// Trigger play improvements
for (final Trigger tr : card.getTriggers()) {
// These triggers all care for ETB effects
final Map<String, String> params = tr.getMapParams();
if (tr.getMode() != TriggerType.ChangesZone) {
continue;
}
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
continue;
}
if (params.containsKey("ValidCard")) {
if (!params.get("ValidCard").contains("Self")) {
continue;
}
if (params.get("ValidCard").contains("notkicked")) {
if (sa.isKicked()) {
continue;
}
} else if (params.get("ValidCard").contains("kicked")) {
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
String s = params.get("ValidCard").split("kicked ")[1];
if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else if (!sa.isKicked()) {
continue;
}
}
}
if (!tr.requirementsCheck(game)) {
continue;
}
if (tr.getOverridingAbility() != null) {
// Abilities yet
continue;
}
// if trigger is not mandatory - no problem
if (params.get("OptionalDecider") != null) {
continue;
}
// Maybe better considerations
final String execute = params.get("Execute");
if (execute == null) {
continue;
}
final SpellAbility exSA = AbilityFactory.getAbility(card.getSVar(execute), card);
if (api != null) {
if (exSA.getApi() != api) {
continue;
} else {
rightapi = true;
}
}
if (sa != null) {
exSA.setActivatingPlayer(sa.getActivatingPlayer());
}
else {
exSA.setActivatingPlayer(player);
}
exSA.setTrigger(true);
// Run non-mandatory trigger.
// These checks only work if the Executing SpellAbility is an Ability_Sub.
if ((exSA instanceof AbilitySub) && !doTrigger(exSA, false)) {
// AI would not run this trigger if given the chance
return false;
}
}
if (api != null && !rightapi) {
return false;
}
// Replacement effects
for (final ReplacementEffect re : card.getReplacementEffects()) {
// These Replacements all care for ETB effects
final Map<String, String> params = re.getMapParams();
if (!(re instanceof ReplaceMoved)) {
continue;
}
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
continue;
}
if (params.containsKey("ValidCard")) {
if (!params.get("ValidCard").contains("Self")) {
continue;
}
if (params.get("ValidCard").contains("notkicked")) {
if (sa.isKicked()) {
continue;
}
} else if (params.get("ValidCard").contains("kicked")) {
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
String s = params.get("ValidCard").split("kicked ")[1];
if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else if (!sa.isKicked()) { // otherwise just any must be present
continue;
}
}
}
if (!re.requirementsCheck(game)) {
continue;
}
final SpellAbility exSA = re.getOverridingAbility();
if (exSA != null) {
if (sa != null) {
exSA.setActivatingPlayer(sa.getActivatingPlayer());
}
else {
exSA.setActivatingPlayer(player);
}
if (exSA.getActivatingPlayer() == null) {
throw new InvalidParameterException("Executing SpellAbility for Replacement Effect has no activating player");
}
}
// ETBReplacement uses overriding abilities.
// These checks only work if the Executing SpellAbility is an Ability_Sub.
if (exSA != null && (exSA instanceof AbilitySub) && !doTrigger(exSA, false)) {
return false;
}
}
return true;
}
private List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList)
{
final ArrayList<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
@@ -266,36 +446,7 @@ public class AiController {
landList = CardLists.filter(landList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getSVar("NeedsToPlay").length() > 0) {
final String needsToPlay = c.getSVar("NeedsToPlay");
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, needsToPlay.split(","), c.getController(), c);
if (list.isEmpty()) {
return false;
}
}
if (c.getSVar("NeedsToPlayVar").length() > 0) {
final String needsToPlay = c.getSVar("NeedsToPlayVar");
int x = 0;
int y = 0;
String sVar = needsToPlay.split(" ")[0];
String comparator = needsToPlay.split(" ")[1];
String compareTo = comparator.substring(2);
try {
x = Integer.parseInt(sVar);
} catch (final NumberFormatException e) {
x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
}
try {
y = Integer.parseInt(compareTo);
} catch (final NumberFormatException e) {
y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
}
if (!Expressions.compare(x, comparator, y)) {
return false;
}
}
canPlaySpellBasic(c);
if (c.isType("Legendary") && !c.getName().equals("Flagstones of Trokair")) {
final List<Card> list = player.getCardsIn(ZoneType.Battlefield);
if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) {
@@ -420,7 +571,12 @@ public class AiController {
SpellAbility currentSA = sa;
sa.setActivatingPlayer(player);
// check everything necessary
if (canPlayAndPayFor(currentSA)) {
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
//PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
if (opinion == AiPlayDecision.WillPlay) {
if (bestSA == null) {
bestSA = currentSA;
bestRestriction = ComputerUtil.counterSpellRestriction(player, currentSA);
@@ -441,51 +597,108 @@ public class AiController {
return bestSA;
} // playCounterSpell()
// if return true, go to next phase
/**
* <p>
* playSpellAbilities.
* </p>
*
* @param all
* an array of {@link forge.game.spellability.SpellAbility}
* objects.
* @return a boolean.
*/
private SpellAbility chooseSpellAbilyToPlay(final List<SpellAbility> all, boolean skipCounter) {
if ( all == null || all.isEmpty() )
return null;
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) {
// Don't add Counterspells to the "normal" playcard lookups
if (sa.getApi() == ApiType.Counter && skipCounter) {
continue;
}
sa.setActivatingPlayer(player);
if (!canPlayAndPayFor(sa))
continue;
return sa;
}
return null;
} // playCards()
// This is for playing spells regularly (no Cascade/Ripple etc.)
private boolean canPlayAndPayFor(final SpellAbility sa) {
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
if (!sa.canPlay()) {
return false;
return AiPlayDecision.CantPlaySa;
}
//System.out.printf("Ai thinks of %s @ %s >>> ", sa, sa.getActivatingPlayer().getGame().getPhaseHandler().debugPrintState());
if (!sa.canPlayAI(player)) {
return false;
AiPlayDecision op = canPlaySa(sa);
if (op != AiPlayDecision.WillPlay) {
return op;
}
//System.out.printf("wouldPlay: %s, canPay: %s%n", aiWouldPlay, canPay);
return ComputerUtilCost.canPayCost(sa, player);
return ComputerUtilCost.canPayCost(sa, player) ? AiPlayDecision.WillPlay : AiPlayDecision.CantAfford;
}
public AiPlayDecision canPlaySa(SpellAbility sa) {
final Card card = sa.getHostCard();
if ( sa instanceof WrappedAbility ) {
return canPlaySa(((WrappedAbility) sa).getWrappedAbility());
}
if( sa.getApi() != null ) {
boolean canPlay = sa.getApi().getAi().canPlayAIWithSubs(player, sa);
if(!canPlay)
return AiPlayDecision.CantPlayAi;
}
if( sa instanceof SpellPermanent ) {
ManaCost mana = sa.getPayCosts().getTotalMana();
if (mana.countX() > 0) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, player);
if (xPay <= 0) {
return AiPlayDecision.CantAffordX;
}
card.setSVar("PayX", Integer.toString(xPay));
}
// Prevent the computer from summoning Ball Lightning type creatures after attacking
if (card.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")
&& (game.getPhaseHandler().isPlayerTurn(player.getOpponent())
|| game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS))) {
return AiPlayDecision.AnotherTime;
}
// Prevent the computer from summoning Ball Lightning type creatures after attacking
if (card.hasStartOfKeyword("You may cast CARDNAME as though it had flash. If") && !card.getController().couldCastSorcery(sa)) {
return AiPlayDecision.AnotherTime;
}
// Wait for Main2 if possible
if (game.getPhaseHandler().is(PhaseType.MAIN1)
&& game.getPhaseHandler().isPlayerTurn(player)
&& player.getManaPool().totalMana() <= 0
&& !ComputerUtil.castPermanentInMain1(player, sa)) {
return AiPlayDecision.WaitForMain2;
}
// save cards with flash for surprise blocking
if (card.hasKeyword("Flash")
&& (player.isUnlimitedHandSize() || player.getCardsIn(ZoneType.Hand).size() <= player.getMaxHandSize())
&& player.getManaPool().totalMana() <= 0
&& (game.getPhaseHandler().isPlayerTurn(player)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !card.hasETBTrigger())
&& !ComputerUtil.castPermanentInMain1(player, sa)) {
return AiPlayDecision.AnotherTime;
}
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
} else if( sa instanceof Spell ) {
return canPlaySpellBasic(card);
}
return AiPlayDecision.WillPlay;
}
private AiPlayDecision canPlaySpellBasic(final Card card) {
if (card.getSVar("NeedsToPlay").length() > 0) {
final String needsToPlay = card.getSVar("NeedsToPlay");
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card);
if (list.isEmpty()) {
return AiPlayDecision.MissingNeededCards;
}
}
if (card.getSVar("NeedsToPlayVar").length() > 0) {
final String needsToPlay = card.getSVar("NeedsToPlayVar");
int x = 0;
int y = 0;
String sVar = needsToPlay.split(" ")[0];
String comparator = needsToPlay.split(" ")[1];
String compareTo = comparator.substring(2);
try {
x = Integer.parseInt(sVar);
} catch (final NumberFormatException e) {
x = CardFactoryUtil.xCount(card, card.getSVar(sVar));
}
try {
y = Integer.parseInt(compareTo);
} catch (final NumberFormatException e) {
y = CardFactoryUtil.xCount(card, card.getSVar(compareTo));
}
if (!Expressions.compare(x, comparator, y)) {
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
}
}
return AiPlayDecision.WillPlay;
}
// not sure "playing biggest spell" matters?
@@ -700,11 +913,11 @@ public class AiController {
sa.setActivatingPlayer(player);
//Spells
if (sa instanceof Spell) {
if (!((Spell) sa).canPlayFromEffectAI(player, mandatory, withoutPayingManaCost)) {
if (AiPlayDecision.WillPlay != canPlayFromEffectAI((Spell) sa, mandatory, withoutPayingManaCost)) {
continue;
}
} else {
if (sa.canPlayAI(player)) {
if (AiPlayDecision.WillPlay == canPlaySa(sa)) {
continue;
}
}
@@ -720,6 +933,91 @@ public class AiController {
return null;
}
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
final Card card = spell.getHostCard();
if( spell instanceof SpellApiBased )
{
boolean chance = false;
if (withoutPayingManaCost) {
chance = spell.getApi().getAi().doTriggerNoCostWithSubs(player, spell, mandatory);
} else {
chance = spell.getApi().getAi().doTriggerAI(player, spell, mandatory);
}
if (!chance)
return AiPlayDecision.TargetingFailed;
return canPlaySa(spell);
}
if ( spell instanceof SpellPermanent) {
if (mandatory) {
return AiPlayDecision.WillPlay;
}
ManaCost mana = spell.getPayCosts().getTotalMana();
final Cost cost = spell.getPayCosts();
if (cost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(player, cost, card, 4, null)) {
return AiPlayDecision.CostNotAcceptable;
}
if (!ComputerUtilCost.checkDiscardCost(player, cost, card)) {
return AiPlayDecision.CostNotAcceptable;
}
if (!ComputerUtilCost.checkSacrificeCost(player, cost, card)) {
return AiPlayDecision.CostNotAcceptable;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, card)) {
return AiPlayDecision.CostNotAcceptable;
}
}
// check on legendary
if (card.isType("Legendary")
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
final List<Card> list = player.getCardsIn(ZoneType.Battlefield);
if (Iterables.any(list, CardPredicates.nameEquals(card.getName()))) {
return AiPlayDecision.WouldDestroyLegend;
}
}
if (card.isPlaneswalker()) {
List<Card> list = player.getCardsIn(ZoneType.Battlefield);
list = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS);
List<String> type = card.getType();
final String subtype = type.get(type.size() - 1);
final List<Card> cl = CardLists.getType(list, subtype);
if (!cl.isEmpty()) {
return AiPlayDecision.WouldDestroyOtherPlaneswalker;
}
}
if (card.isType("World")) {
List<Card> list = player.getCardsIn(ZoneType.Battlefield);
list = CardLists.getType(list, "World");
if (!list.isEmpty()) {
return AiPlayDecision.WouldDestroyWorldEnchantment;
}
}
if (card.isCreature() && (card.getNetDefense() <= 0) && !card.hasStartOfKeyword("etbCounter")
&& mana.countX() == 0 && !card.hasETBTrigger()
&& !card.hasETBReplacement()) {
return AiPlayDecision.WouldBecomeZeroToughnessCreature;
}
if (!checkETBEffects(card, spell, null)) {
return AiPlayDecision.BadEtbEffects;
}
if (ComputerUtil.damageFromETB(player, card) >= player.getLife() && player.canLoseLife()) {
return AiPlayDecision.BadEtbEffects;
}
}
return canPlaySpellBasic(card);
}
public SpellAbility choooseSpellAbilityToPlay() {
final PhaseType phase = game.getPhaseHandler().getPhase();
@@ -784,7 +1082,36 @@ public class AiController {
return counterETB;
}
return chooseSpellAbilyToPlay(getSpellAbilities(cards), true);
SpellAbility result = chooseSpellAbilyToPlay(getSpellAbilities(cards), true);
if( null == result)
return null;
return result;
}
private SpellAbility chooseSpellAbilyToPlay(final List<SpellAbility> all, boolean skipCounter) {
if ( all == null || all.isEmpty() )
return null;
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) {
// Don't add Counterspells to the "normal" playcard lookups
if (sa.getApi() == ApiType.Counter && skipCounter) {
continue;
}
sa.setActivatingPlayer(player);
AiPlayDecision opinion = canPlayAndPayFor(sa);
// PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
if (opinion != AiPlayDecision.WillPlay)
continue;
return sa;
}
return null;
}
public List<Card> chooseCardsToDelve(int colorlessCost, List<Card> grave) {
@@ -818,13 +1145,74 @@ public class AiController {
return toExile;
}
public boolean doTrigger(SpellAbility spell, boolean mandatory) {
if ( spell.getApi() != null )
return spell.getApi().getAi().doTriggerAI(player, spell, mandatory);
if ( spell instanceof WrappedAbility )
return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory);
return false;
}
/**
* Ai should run.
*
* @param sa the sa
* @param ai
* @return true, if successful
*/
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa) {
Card hostCard = effect.getHostCard();
if (effect.getMapParams().containsKey("AICheckSVar")) {
System.out.println("aiShouldRun?" + sa);
final String svarToCheck = effect.getMapParams().get("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
if (effect.getMapParams().containsKey("AISVarCompare")) {
final String fullCmp = effect.getMapParams().get("AISVarCompare");
comparator = fullCmp.substring(0, 2);
final String strCmpTo = fullCmp.substring(2);
try {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
if (sa == null) {
compareTo = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(strCmpTo));
} else {
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
}
}
}
int left = 0;
if (sa == null) {
left = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(svarToCheck));
} else {
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
}
System.out.println("aiShouldRun?" + left + comparator + compareTo);
if (Expressions.compare(left, comparator, compareTo)) {
return true;
}
} else if (effect.getMapParams().containsKey("AICheckDredge")) {
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
} else if (sa != null && doTrigger(sa, false)) {
return true;
}
return false;
}
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
List<SpellAbility> result = new ArrayList<SpellAbility>();
for(SpellAbility sa : usableFromOpeningHand) {
// Is there a better way for the AI to decide this?
if (sa.doTrigger(false, player)) {
if (doTrigger(sa, false)) {
result.add(sa);
}
}

View File

@@ -0,0 +1,20 @@
package forge.ai;
public enum AiPlayDecision {
WillPlay,
CantPlaySa,
CantPlayAi,
CantAfford,
CantAffordX,
WaitForMain2,
AnotherTime,
MissingNeededCards,
NeedsToPlayCriteriaNotMet,
TargetingFailed,
CostNotAcceptable,
WouldDestroyLegend,
WouldDestroyOtherPlaneswalker,
WouldBecomeZeroToughnessCreature,
WouldDestroyWorldEnchantment,
BadEtbEffects;
}

View File

@@ -630,7 +630,7 @@ public class ComputerUtilMana {
// don't use abilities with dangerous drawbacks
AbilitySub sub = m.getSubAbility();
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
if (!sub.getAi().chkDrawbackWithSubs(ai, sub)) {
if (!sub.getApi().getAi().chkDrawbackWithSubs(ai, sub)) {
continue;
}
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
@@ -688,7 +688,7 @@ public class ComputerUtilMana {
// don't use abilities with dangerous drawbacks
AbilitySub sub = m.getSubAbility();
if (sub != null) {
if (!sub.getAi().chkDrawbackWithSubs(ai, sub)) {
if (!sub.getApi().getAi().chkDrawbackWithSubs(ai, sub)) {
continue;
}
}

View File

@@ -136,7 +136,7 @@ public abstract class SpellAbilityAi extends SaTargetRoutines {
*/
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
final AbilitySub subAb = ab.getSubAbility();
return ab.getAi().chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
return ab.getApi().getAi().chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
}
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {

View File

@@ -307,7 +307,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || subAb.getAi().chkDrawbackWithSubs(ai, subAb);
return subAb == null || subAb.getApi().getAi().chkDrawbackWithSubs(ai, subAb);
}
/**
@@ -645,7 +645,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
final AbilitySub subAb = sa.getSubAbility();
chance &= subAb == null || subAb.getAi().chkDrawbackWithSubs(ai, subAb);
chance &= subAb == null || subAb.getApi().getAi().chkDrawbackWithSubs(ai, subAb);
return chance;
}

View File

@@ -1,8 +1,11 @@
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.AiPlayDecision;
import forge.ai.SpellAbilityAi;
import forge.game.ability.effects.CharmEffect;
import forge.game.player.Player;
import forge.game.player.PlayerControllerAi;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
@@ -45,18 +48,18 @@ public class CharmAi extends SpellAbilityAi {
return choices.subList(1, choices.size());
}
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
for (int i = 0; i < num; i++) {
AbilitySub thisPick = null;
for (SpellAbility sub : choices) {
sub.setActivatingPlayer(ai);
if (!playNow && sub.canPlayAI(ai)) {
if (!playNow && AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
playNow = true;
break;
}
if ((playNow || i < num - 1) && sub.doTrigger(false, ai)) {
if ((playNow || i < num - 1) && aic.doTrigger(sub, false)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
break;
@@ -71,7 +74,7 @@ public class CharmAi extends SpellAbilityAi {
AbilitySub thisPick = null;
for (SpellAbility sub : choices) {
sub.setActivatingPlayer(ai);
if (sub.doTrigger(true, ai)) {
if (aic.doTrigger(sub, true)) {
thisPick = (AbilitySub) sub;
choices.remove(sub);
break;

View File

@@ -1,8 +1,11 @@
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.AiPlayDecision;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.player.PlayerControllerAi;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
@@ -15,9 +18,9 @@ public class DelayedTriggerAi extends SpellAbilityAi {
trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) {
return ((AbilitySub) trigsa).getAi().chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
return ((AbilitySub) trigsa).getApi().getAi().chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
} else {
return trigsa.canPlayAI(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}
@@ -25,12 +28,13 @@ public class DelayedTriggerAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final String svarName = sa.getParam("Execute");
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai);
if (!sa.hasParam("OptionalDecider")) {
return trigsa.doTrigger(true, ai);
return aic.doTrigger(trigsa, true);
} else {
return trigsa.doTrigger(!sa.getParam("OptionalDecider").equals("You"), ai);
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
}
}
@@ -39,7 +43,7 @@ public class DelayedTriggerAi extends SpellAbilityAi {
final String svarName = sa.getParam("Execute");
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
trigsa.setActivatingPlayer(ai);
return trigsa.canPlayAI(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}

View File

@@ -32,7 +32,7 @@ public class PeekAndRevealAi extends SpellAbilityAi {
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
AbilitySub subAb = sa.getSubAbility();
return subAb != null && subAb.getAi().chkDrawbackWithSubs(player, subAb);
return subAb != null && subAb.getApi().getAi().chkDrawbackWithSubs(player, subAb);
}
}

View File

@@ -1,6 +1,8 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.AiPlayDecision;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
@@ -10,6 +12,7 @@ import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerControllerAi;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
@@ -120,7 +123,9 @@ public class PlayAi extends SpellAbilityAi {
Spell spell = (Spell) s;
s.setActivatingPlayer(ai);
// timing restrictions still apply
if (s.getRestrictions().checkTimingRestrictions(c, s) && spell.canPlayFromEffectAI(ai, false, true)) {
if (!s.getRestrictions().checkTimingRestrictions(c, s))
continue;
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, false, true)) {
return true;
}
}

View File

@@ -1,10 +1,12 @@
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerControllerAi;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
@@ -54,6 +56,7 @@ public class RepeatAi extends SpellAbilityAi {
repeat.setActivatingPlayer(sa.getActivatingPlayer());
((AbilitySub) repeat).setParent(sa);
return repeat.doTrigger(mandatory, ai);
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
return aic.doTrigger(repeat, mandatory);
}
}

View File

@@ -1,6 +1,5 @@
package forge.game.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.effects.ChangeZoneAllEffect;
import forge.game.ability.effects.ChangeZoneEffect;
import forge.game.ability.effects.ManaEffect;
@@ -8,7 +7,6 @@ import forge.game.ability.effects.ManaReflectedEffect;
import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.AbilityActivated;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.TargetRestrictions;
@@ -17,7 +15,6 @@ import java.util.Map;
public class AbilityApiBased extends AbilityActivated {
private final SpellAbilityEffect effect;
private final SpellAbilityAi ai;
private static final long serialVersionUID = -4183793555528531978L;
@@ -26,7 +23,6 @@ public class AbilityApiBased extends AbilityActivated {
mapParams.putAll(params0);
api = api0;
effect = api.getSpellEffect();
ai = api.getAi();
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
@@ -62,14 +58,4 @@ public class AbilityApiBased extends AbilityActivated {
public void resolve() {
effect.resolve(this);
}
@Override
public boolean canPlayAI(Player aiPlayer) {
return ai.canPlayAIWithSubs(aiPlayer, this);
}
@Override
public boolean doTrigger(final boolean mandatory, Player aiPlayer) {
return ai.doTriggerAI(aiPlayer, this, mandatory);
}
}

View File

@@ -1,13 +1,11 @@
package forge.game.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.effects.ChangeZoneAllEffect;
import forge.game.ability.effects.ChangeZoneEffect;
import forge.game.ability.effects.ManaEffect;
import forge.game.ability.effects.ManaReflectedEffect;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.Spell;
import forge.game.spellability.TargetRestrictions;
@@ -17,7 +15,6 @@ import java.util.Map;
public class SpellApiBased extends Spell {
private static final long serialVersionUID = -6741797239508483250L;
private final SpellAbilityEffect effect;
private final SpellAbilityAi ai;
public SpellApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
super(sourceCard, abCost);
@@ -26,7 +23,6 @@ public class SpellApiBased extends Spell {
mapParams.putAll(params0);
api = api0;
effect = api.getSpellEffect();
ai = api.getAi();
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
this.setManaPart(new AbilityManaPart(sourceCard, mapParams));
@@ -46,24 +42,8 @@ public class SpellApiBased extends Spell {
* @see forge.card.spellability.SpellAbility#resolve()
*/
@Override
public boolean canPlayAI(Player aiPlayer) {
return ai.canPlayAIWithSubs(aiPlayer, this) && super.canPlayAI(aiPlayer);
}
@Override
public void resolve() {
effect.resolve(this);
}
@Override
public boolean canPlayFromEffectAI(Player aiPlayer, final boolean mandatory, final boolean withOutManaCost) {
boolean chance = false;
if (withOutManaCost) {
chance = ai.doTriggerNoCostWithSubs(aiPlayer, this, mandatory);
} else {
chance = ai.doTriggerAI(aiPlayer, this, mandatory);
}
return chance && super.canPlayAI(aiPlayer);
}
}

View File

@@ -1,11 +1,9 @@
package forge.game.ability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.effects.ChangeZoneAllEffect;
import forge.game.ability.effects.ChangeZoneEffect;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.TargetRestrictions;
@@ -14,14 +12,12 @@ import java.util.Map;
public class StaticAbilityApiBased extends AbilityStatic {
private final SpellAbilityEffect effect;
private final SpellAbilityAi ai;
public StaticAbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
super(sourceCard, abCost, tgt);
mapParams.putAll(params0);
api = api0;
effect = api.getSpellEffect();
ai = api.getAi();
if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
@@ -41,14 +37,4 @@ public class StaticAbilityApiBased extends AbilityStatic {
public void resolve() {
effect.resolve(this);
}
@Override
public boolean canPlayAI(Player aiPlayer) {
return ai.canPlayAIWithSubs(aiPlayer, this);
}
@Override
public boolean doTrigger(final boolean mandatory, Player aiPlayer) {
return ai.doTriggerAI(aiPlayer, this, mandatory);
}
}

View File

@@ -20,8 +20,6 @@ package forge.game.card;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.Command;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.card.CardCharacteristicName;
import forge.card.CardType;
import forge.card.ColorSet;
@@ -39,12 +37,19 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.*;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilityActivated;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityRestriction;
import forge.game.spellability.TargetRestrictions;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.zone.Zone;
@@ -107,15 +112,6 @@ public class CardFactoryUtil {
card.addIntrinsicKeyword("Haste");
card.setUnearthed(true);
}
@Override
public boolean canPlayAI(Player aiPlayer) {
PhaseHandler phase = sourceCard.getGame().getPhaseHandler();
if (phase.getPhase().isAfter(PhaseType.MAIN1) || !phase.isPlayerTurn(getActivatingPlayer())) {
return false;
}
return ComputerUtilCost.canPayCost(this, getActivatingPlayer());
}
}
final AbilityActivated unearth = new AbilityUnearth(sourceCard, cost, null);
@@ -308,13 +304,6 @@ public class CardFactoryUtil {
return sourceCard.getOwner().canCastSorcery();
}
@Override
public boolean canPlayAI(Player aiPlayer) {
return true;
// Suspend currently not functional for the AI,
// seems to be an issue with regaining Priority after Suspension
}
@Override
public void resolve() {
final Game game = sourceCard.getGame();
@@ -2888,23 +2877,6 @@ public class CardFactoryUtil {
card.setEvoked(true);
game.getAction().moveToPlay(card);
}
@Override
public boolean canPlayAI(Player aiPlayer) {
final Game game = card.getGame();
if (!SpellPermanent.checkETBEffects(card, aiPlayer)) {
return false;
}
// Wait for Main2 if possible
if (game.getPhaseHandler().is(PhaseType.MAIN1)
&& game.getPhaseHandler().isPlayerTurn(aiPlayer)
&& aiPlayer.getManaPool().totalMana() <= 0
&& !ComputerUtil.castPermanentInMain1(aiPlayer, this)) {
return false;
}
return super.canPlayAI(aiPlayer);
}
};
card.removeIntrinsicKeyword(evokeKeyword);
final StringBuilder desc = new StringBuilder();

View File

@@ -91,9 +91,6 @@ public class CostPayLife extends CostPart {
return true;
}
/* (non-Javadoc)
* @see forge.card.cost.CostPart#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
// TODO Auto-generated method stub

View File

@@ -18,6 +18,7 @@
package forge.game.cost;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -128,9 +129,6 @@ public class CostRemoveAnyCounter extends CostPartWithList {
return sb.toString();
}
/* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
final String amount = this.getAmount();

View File

@@ -153,9 +153,6 @@ public class CostRemoveCounter extends CostPartWithList {
return true;
}
/* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
Card source = ability.getHostCard();

View File

@@ -42,42 +42,23 @@ public class CostTap extends CostPart {
@Override
public boolean isRenewable() { return true; }
/*
* (non-Javadoc)
*
* @see forge.card.cost.CostPart#toString()
*/
@Override
public final String toString() {
return "{T}";
}
/*
* (non-Javadoc)
*
* @see forge.card.cost.CostPart#refund(forge.Card)
*/
@Override
public final void refund(final Card source) {
source.setTapped(false);
}
/*
* (non-Javadoc)
*
* @see
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability) {
final Card source = ability.getHostCard();
return source.isUntapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste."));
}
/* (non-Javadoc)
* @see forge.card.cost.CostPart#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
ability.getHostCard().tap();

View File

@@ -75,9 +75,6 @@ public class CostUntap extends CostPart {
return source.isTapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste."));
}
/* (non-Javadoc)
* @see forge.card.cost.CostPart#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
ability.getHostCard().untap();

View File

@@ -6,6 +6,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import forge.ai.*;
import forge.ai.ability.CharmAi;
import forge.card.ColorSet;
@@ -35,6 +36,7 @@ import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
@@ -175,7 +177,7 @@ public class PlayerControllerAi extends PlayerController {
}
// There is no way this doTrigger here will have the same target as stored above
// So it's possible it's making a different decision here than will actually happen
if (!sa.doTrigger(isMandatory, player)) {
if (!brains.doTrigger(sa, isMandatory)) {
ret = false;
}
if (storeChoices) {
@@ -265,12 +267,12 @@ public class PlayerControllerAi extends PlayerController {
if (mayChooseNewTargets) {
if (copySA instanceof Spell) {
Spell spell = (Spell) copySA;
if (!spell.canPlayFromEffectAI(player, true, true)) {
if (AiPlayDecision.WillPlay != ((PlayerControllerAi)player.getController()).getAi().canPlayFromEffectAI(spell, true, true)) {
return; // is this legal at all?
}
}
else {
copySA.canPlayAI(player);
getAi().canPlaySa(copySA);
}
}
ComputerUtil.playSpellAbilityForFree(player, copySA);
@@ -279,7 +281,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
if (canSetupTargets)
effectSA.doTrigger(true, player); // first parameter does not matter, since return value won't be used
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
ComputerUtil.playNoStack(player, effectSA, game);
}
@@ -344,7 +346,7 @@ public class PlayerControllerAi extends PlayerController {
*/
@Override
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
return ReplacementEffect.aiShouldRun(replacementEffect, effectSA, player);
return brains.aiShouldRun(replacementEffect, effectSA);
}
@Override
@@ -629,7 +631,7 @@ public class PlayerControllerAi extends PlayerController {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
targetingPlayer.getController().chooseTargetsFor(sa);
} else {
sa.doTrigger(isMandatory, player);
brains.doTrigger(sa, isMandatory);
}
}
@@ -645,7 +647,7 @@ public class PlayerControllerAi extends PlayerController {
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
Spell spell = (Spell) tgtSA;
if (spell.canPlayFromEffectAI(player, !optional, noManaCost) || !optional) {
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
if (noManaCost) {
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
} else {
@@ -664,7 +666,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean chooseTargetsFor(SpellAbility currentAbility) {
return currentAbility.doTrigger(true, player);
return brains.doTrigger(currentAbility, true);
}
@Override

View File

@@ -19,14 +19,9 @@ package forge.game.replacement;
import forge.game.Game;
import forge.game.TriggerReplacementBase;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import java.util.List;
import java.util.Map;
@@ -74,55 +69,6 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
return this.getMapParams().containsKey("Secondary");
}
/**
* Ai should run.
*
* @param sa the sa
* @param ai
* @return true, if successful
*/
public final static boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, Player ai) {
if (effect.getMapParams().containsKey("AICheckSVar")) {
System.out.println("aiShouldRun?" + sa);
final String svarToCheck = effect.getMapParams().get("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
if (effect.getMapParams().containsKey("AISVarCompare")) {
final String fullCmp = effect.getMapParams().get("AISVarCompare");
comparator = fullCmp.substring(0, 2);
final String strCmpTo = fullCmp.substring(2);
try {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
if (sa == null) {
compareTo = CardFactoryUtil.xCount(effect.hostCard, effect.hostCard.getSVar(strCmpTo));
} else {
compareTo = AbilityUtils.calculateAmount(effect.hostCard, effect.hostCard.getSVar(strCmpTo), sa);
}
}
}
int left = 0;
if (sa == null) {
left = CardFactoryUtil.xCount(effect.hostCard, effect.hostCard.getSVar(svarToCheck));
} else {
left = AbilityUtils.calculateAmount(effect.hostCard, svarToCheck, sa);
}
System.out.println("aiShouldRun?" + left + comparator + compareTo);
if (Expressions.compare(left, comparator, compareTo)) {
return true;
}
} else if (effect.getMapParams().containsKey("AICheckDredge")) {
return ai.getCardsIn(ZoneType.Library).size() > 8 || ai.isCardInPlay("Laboratory Maniac");
} else if (sa != null && sa.doTrigger(false, ai)) {
return true;
}
return false;
}
/**
* Sets the checks for run.
*
@@ -133,7 +79,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
this.hasRun = hasRun;
}
/**
/**
* Can replace.
*
* @param runParams

View File

@@ -17,7 +17,6 @@
*/
package forge.game.spellability;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityFactory;
import forge.game.ability.ApiType;
import forge.game.ability.SpellAbilityEffect;
@@ -28,7 +27,6 @@ import forge.game.ability.effects.ManaReflectedEffect;
import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.cost.Cost;
import forge.game.player.Player;
import java.util.Map;
@@ -81,14 +79,7 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
private final SpellAbilityEffect effect;
private final SpellAbilityAi ai;
/**
* @return the ai
*/
public SpellAbilityAi getAi() {
return ai;
}
public AbilitySub(ApiType api0, final Card ca, final TargetRestrictions tgt, Map<String, String> params0) {
super(ca, Cost.Zero);
@@ -96,7 +87,6 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
api = api0;
mapParams.putAll(params0);
ai = api.getAi();
effect = api.getSpellEffect();
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
@@ -120,18 +110,8 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
return effect.getStackDescriptionWithSubs(mapParams, this);
}
@Override
public boolean canPlayAI(Player aiPlayer) {
return ai.canPlayAIWithSubs(aiPlayer, this);
}
@Override
public void resolve() {
effect.resolve(this);
}
@Override
public boolean doTrigger(final boolean mandatory, Player aiPlayer) {
return ai.doTriggerAI(aiPlayer, this, mandatory);
}
}

View File

@@ -19,14 +19,11 @@ package forge.game.spellability;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.cost.CostPayment;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import java.util.ArrayList;
import java.util.List;
@@ -124,46 +121,6 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
return true;
}
/** {@inheritDoc} */
@Override
public boolean canPlayAI(Player aiPlayer) {
final Card card = this.getHostCard();
final Game game = getActivatingPlayer().getGame();
if (card.getSVar("NeedsToPlay").length() > 0) {
final String needsToPlay = card.getSVar("NeedsToPlay");
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card);
if (list.isEmpty()) {
return false;
}
}
if (card.getSVar("NeedsToPlayVar").length() > 0) {
final String needsToPlay = card.getSVar("NeedsToPlayVar");
int x = 0;
int y = 0;
String sVar = needsToPlay.split(" ")[0];
String comparator = needsToPlay.split(" ")[1];
String compareTo = comparator.substring(2);
try {
x = Integer.parseInt(sVar);
} catch (final NumberFormatException e) {
x = CardFactoryUtil.xCount(card, card.getSVar(sVar));
}
try {
y = Integer.parseInt(compareTo);
} catch (final NumberFormatException e) {
y = CardFactoryUtil.xCount(card, card.getSVar(compareTo));
}
if (!Expressions.compare(x, comparator, y)) {
return false;
}
}
return super.canPlayAI(aiPlayer);
}
/** {@inheritDoc} */
@Override
public final Object clone() {
@@ -180,21 +137,6 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
public boolean isAbility() { return false; }
/**
* <p>
* canPlayFromEffectAI.
* </p>
*
* @param mandatory
* can the controller chose not to play the spell
* @param withOutManaCost
* is the spell cast without paying mana
* @return a boolean.
*/
public boolean canPlayFromEffectAI(Player aiPlayer, boolean mandatory, boolean withOutManaCost) {
return canPlayAI(aiPlayer);
}
/**
* @return the castFaceDown
*/

View File

@@ -210,32 +210,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
*/
public abstract void resolve();
/**
* <p>
* canPlayAI.
* </p>
*
* @return a boolean.
*/
public /*final*/ boolean canPlayAI(Player aiPlayer) {
return true;
}
// This should be overridden by ALL AFs
/**
* <p>
* doTrigger.
* </p>
*
* @param mandatory
* a boolean.
* @param ai TODO
* @return a boolean.
*/
public boolean doTrigger(final boolean mandatory, Player ai) {
return false;
}
/**
* <p>
* Getter for the field <code>multiKickerManaCost</code>.

View File

@@ -17,31 +17,11 @@
*/
package forge.game.spellability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityFactory;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import org.apache.commons.lang3.StringUtils;
import java.security.InvalidParameterException;
import java.util.List;
import java.util.Map;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.zone.ZoneType;
/**
* <p>
@@ -84,282 +64,6 @@ public class SpellPermanent extends Spell {
} // Spell_Permanent()
/** {@inheritDoc} */
@Override
public boolean canPlayAI(Player aiPlayer) {
final Card card = this.getHostCard();
ManaCost mana = this.getPayCosts().getTotalMana();
final Game game = aiPlayer.getGame();
if (mana.countX() > 0) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(this, aiPlayer);
if (xPay <= 0) {
return false;
}
card.setSVar("PayX", Integer.toString(xPay));
}
// Prevent the computer from summoning Ball Lightning type creatures after attacking
if (card.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")
&& (game.getPhaseHandler().isPlayerTurn(aiPlayer.getOpponent())
|| game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS))) {
return false;
}
// Prevent the computer from summoning Ball Lightning type creatures after attacking
if (card.hasStartOfKeyword("You may cast CARDNAME as though it had flash. If")
&& !card.getController().couldCastSorcery(this)) {
return false;
}
// Wait for Main2 if possible
if (game.getPhaseHandler().is(PhaseType.MAIN1)
&& game.getPhaseHandler().isPlayerTurn(aiPlayer)
&& aiPlayer.getManaPool().totalMana() <= 0
&& !ComputerUtil.castPermanentInMain1(aiPlayer, this)) {
return false;
}
// save cards with flash for surprise blocking
if (card.hasKeyword("Flash")
&& (aiPlayer.isUnlimitedHandSize() || aiPlayer.getCardsIn(ZoneType.Hand).size() <= aiPlayer.getMaxHandSize())
&& aiPlayer.getManaPool().totalMana() <= 0
&& (game.getPhaseHandler().isPlayerTurn(aiPlayer)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !card.hasETBTrigger())
&& !ComputerUtil.castPermanentInMain1(aiPlayer, this)) {
return false;
}
return canPlayFromEffectAI(aiPlayer, false, true);
} // canPlayAI()
/** {@inheritDoc} */
@Override
public boolean canPlayFromEffectAI(Player aiPlayer, final boolean mandatory, final boolean withOutManaCost) {
if (mandatory) {
return true;
}
final Card card = this.getHostCard();
ManaCost mana = this.getPayCosts().getTotalMana();
final Cost cost = this.getPayCosts();
if (cost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(aiPlayer, cost, card, 4, null)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(aiPlayer, cost, card)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(aiPlayer, cost, card)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, card)) {
return false;
}
}
// check on legendary
if (card.isType("Legendary")
&& !aiPlayer.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
final List<Card> list = aiPlayer.getCardsIn(ZoneType.Battlefield);
if (Iterables.any(list, CardPredicates.nameEquals(card.getName()))) {
return false;
}
}
if (card.isPlaneswalker()) {
List<Card> list = aiPlayer.getCardsIn(ZoneType.Battlefield);
list = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS);
for (int i = 0; i < list.size(); i++) {
List<String> type = card.getType();
final String subtype = type.get(type.size() - 1);
final List<Card> cl = CardLists.getType(list, subtype);
if (cl.size() > 0) {
return false;
}
}
}
if (card.isType("World")) {
List<Card> list = aiPlayer.getCardsIn(ZoneType.Battlefield);
list = CardLists.getType(list, "World");
if (list.size() > 0) {
return false;
}
}
if (card.isCreature() && (card.getNetDefense() <= 0) && !card.hasStartOfKeyword("etbCounter")
&& mana.countX() == 0 && !card.hasETBTrigger()
&& !card.hasETBReplacement()) {
return false;
}
if (!SpellPermanent.checkETBEffects(card, this, null, aiPlayer)) {
return false;
}
if (ComputerUtil.damageFromETB(aiPlayer, card) >= aiPlayer.getLife() && aiPlayer.canLoseLife()) {
return false;
}
return super.canPlayAI(aiPlayer);
}
public static boolean checkETBEffects(final Card card, final Player ai) {
return checkETBEffects(card, null, null, ai);
}
public static boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api, final Player ai) {
boolean rightapi = false;
final Game game = ai.getGame();
if (card.isCreature()
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) {
return api == null;
}
// Trigger play improvements
for (final Trigger tr : card.getTriggers()) {
// These triggers all care for ETB effects
final Map<String, String> params = tr.getMapParams();
if (tr.getMode() != TriggerType.ChangesZone) {
continue;
}
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
continue;
}
if (params.containsKey("ValidCard")) {
if (!params.get("ValidCard").contains("Self")) {
continue;
}
if (params.get("ValidCard").contains("notkicked")) {
if (sa.isKicked()) {
continue;
}
} else if (params.get("ValidCard").contains("kicked")) {
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
String s = params.get("ValidCard").split("kicked ")[1];
if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else if (!sa.isKicked()) {
continue;
}
}
}
if (!tr.requirementsCheck(game)) {
continue;
}
if (tr.getOverridingAbility() != null) {
// Abilities yet
continue;
}
// if trigger is not mandatory - no problem
if (params.get("OptionalDecider") != null) {
continue;
}
// Maybe better considerations
final String execute = params.get("Execute");
if (execute == null) {
continue;
}
final SpellAbility exSA = AbilityFactory.getAbility(card.getSVar(execute), card);
if (api != null) {
if (exSA.getApi() != api) {
continue;
} else {
rightapi = true;
}
}
if (sa != null) {
exSA.setActivatingPlayer(sa.getActivatingPlayer());
}
else {
exSA.setActivatingPlayer(ai);
}
exSA.setTrigger(true);
// Run non-mandatory trigger.
// These checks only work if the Executing SpellAbility is an Ability_Sub.
if ((exSA instanceof AbilitySub) && !exSA.doTrigger(false, ai)) {
// AI would not run this trigger if given the chance
return false;
}
}
if (api != null && !rightapi) {
return false;
}
// Replacement effects
for (final ReplacementEffect re : card.getReplacementEffects()) {
// These Replacements all care for ETB effects
final Map<String, String> params = re.getMapParams();
if (!(re instanceof ReplaceMoved)) {
continue;
}
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
continue;
}
if (params.containsKey("ValidCard")) {
if (!params.get("ValidCard").contains("Self")) {
continue;
}
if (params.get("ValidCard").contains("notkicked")) {
if (sa.isKicked()) {
continue;
}
} else if (params.get("ValidCard").contains("kicked")) {
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
String s = params.get("ValidCard").split("kicked ")[1];
if ( "1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
if ( "2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
} else if (!sa.isKicked()) { // otherwise just any must be present
continue;
}
}
}
if (!re.requirementsCheck(game)) {
continue;
}
final SpellAbility exSA = re.getOverridingAbility();
if (exSA != null) {
if (sa != null) {
exSA.setActivatingPlayer(sa.getActivatingPlayer());
}
else {
exSA.setActivatingPlayer(ai);
}
if (exSA.getActivatingPlayer() == null) {
throw new InvalidParameterException("Executing SpellAbility for Replacement Effect has no activating player");
}
}
// ETBReplacement uses overriding abilities.
// These checks only work if the Executing SpellAbility is an Ability_Sub.
if (exSA != null && (exSA instanceof AbilitySub) && !exSA.doTrigger(false, ai)) {
return false;
}
}
return true;
}
/** {@inheritDoc} */
@Override
public void resolve() {

View File

@@ -34,6 +34,10 @@ public class WrappedAbility extends Ability implements ISpellAbility {
decider = decider0;
}
public SpellAbility getWrappedAbility() {
return sa;
}
@Override
public boolean isWrapper() {
@@ -131,21 +135,11 @@ public class WrappedAbility extends Ability implements ISpellAbility {
return sa.canPlay();
}
@Override
public boolean canPlayAI(Player aiPlayer) {
return sa.canPlayAI(aiPlayer);
}
@Override
public SpellAbility copy() {
return sa.copy();
}
@Override
public boolean doTrigger(final boolean mandatory, Player ai) {
return sa.doTrigger(mandatory, ai);
}
@Override
public Player getActivatingPlayer() {
return sa.getActivatingPlayer();

View File

@@ -488,7 +488,8 @@ public class PlayerControllerForTests extends PlayerController {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
targetingPlayer.getController().chooseTargetsFor(sa);
} else {
sa.doTrigger(isMandatory, player);
// this code is no longer possible!
// sa.doTrigger(isMandatory, player);
}
}
@@ -505,7 +506,8 @@ public class PlayerControllerForTests extends PlayerController {
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
Spell spell = (Spell) tgtSA;
if (spell.canPlayFromEffectAI(player, !optional, noManaCost) || !optional) {
// if (spell.canPlayFromEffectAI(player, !optional, noManaCost) || !optional) { -- could not save this part
if (spell.canPlay() || !optional) {
if (noManaCost) {
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
} else {
@@ -526,7 +528,9 @@ public class PlayerControllerForTests extends PlayerController {
@Override
public boolean chooseTargetsFor(SpellAbility currentAbility) {
return currentAbility.doTrigger(true, player);
// no longer possible to run AI's methods on SpellAbility
// return currentAbility.doTrigger(true, player);
return false;
}
@Override