mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 04:08:01 +00:00
Separate AI decision making from SpellAbility classes
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -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
|
||||
|
||||
@@ -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,8 +1082,37 @@ 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) {
|
||||
List<Card> toExile = new ArrayList<Card>();
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
20
forge-game/src/main/java/forge/ai/AiPlayDecision.java
Normal file
20
forge-game/src/main/java/forge/ai/AiPlayDecision.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 +15,7 @@ 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);
|
||||
this.setTargetRestrictions(tgt);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -50,7 +45,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
||||
public final boolean hasRun() {
|
||||
return this.hasRun;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates a new replacement effect.
|
||||
*
|
||||
@@ -69,60 +64,11 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
||||
* Checks if is secondary.
|
||||
*
|
||||
* @return true, if is secondary
|
||||
*/
|
||||
*/
|
||||
public final boolean isSecondary() {
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -33,6 +33,10 @@ public class WrappedAbility extends Ability implements ISpellAbility {
|
||||
sa = sa0;
|
||||
decider = decider0;
|
||||
}
|
||||
|
||||
public SpellAbility getWrappedAbility() {
|
||||
return sa;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user