mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +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/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/AiController.java svneol=native#text/plain
|
||||||
forge-game/src/main/java/forge/ai/AiCostDecision.java -text
|
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/AiProfileUtil.java -text
|
||||||
forge-game/src/main/java/forge/ai/AiProps.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
|
forge-game/src/main/java/forge/ai/ComputerUtil.java svneol=native#text/plain
|
||||||
|
|||||||
@@ -17,39 +17,69 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai;
|
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.esotericsoftware.minlog.Log;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
|
import forge.card.mana.ManaCost;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameActionUtil;
|
import forge.game.GameActionUtil;
|
||||||
import forge.game.GameEntity;
|
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.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.CardPredicates.Presets;
|
||||||
|
import forge.game.card.CounterType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostDiscard;
|
import forge.game.cost.CostDiscard;
|
||||||
import forge.game.cost.CostPart;
|
import forge.game.cost.CostPart;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.*;
|
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.game.zone.ZoneType;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.Expressions;
|
import forge.util.Expressions;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* ComputerAI_General class.
|
* ComputerAI_General class.
|
||||||
@@ -137,7 +167,7 @@ public class AiController {
|
|||||||
for (final SpellAbility sa : c.getNonManaSpellAbilities()) {
|
for (final SpellAbility sa : c.getNonManaSpellAbilities()) {
|
||||||
if (sa instanceof SpellPermanent) {
|
if (sa instanceof SpellPermanent) {
|
||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
if (SpellPermanent.checkETBEffects(c, sa, ApiType.Counter, player)) {
|
if (checkETBEffects(c, sa, ApiType.Counter)) {
|
||||||
spellAbilities.add(sa);
|
spellAbilities.add(sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +176,156 @@ public class AiController {
|
|||||||
return spellAbilities;
|
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)
|
private List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList)
|
||||||
{
|
{
|
||||||
final ArrayList<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
|
final ArrayList<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
|
||||||
@@ -266,36 +446,7 @@ public class AiController {
|
|||||||
landList = CardLists.filter(landList, new Predicate<Card>() {
|
landList = CardLists.filter(landList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (c.getSVar("NeedsToPlay").length() > 0) {
|
canPlaySpellBasic(c);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (c.isType("Legendary") && !c.getName().equals("Flagstones of Trokair")) {
|
if (c.isType("Legendary") && !c.getName().equals("Flagstones of Trokair")) {
|
||||||
final List<Card> list = player.getCardsIn(ZoneType.Battlefield);
|
final List<Card> list = player.getCardsIn(ZoneType.Battlefield);
|
||||||
if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) {
|
if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) {
|
||||||
@@ -420,7 +571,12 @@ public class AiController {
|
|||||||
SpellAbility currentSA = sa;
|
SpellAbility currentSA = sa;
|
||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
// check everything necessary
|
// 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) {
|
if (bestSA == null) {
|
||||||
bestSA = currentSA;
|
bestSA = currentSA;
|
||||||
bestRestriction = ComputerUtil.counterSpellRestriction(player, currentSA);
|
bestRestriction = ComputerUtil.counterSpellRestriction(player, currentSA);
|
||||||
@@ -441,51 +597,108 @@ public class AiController {
|
|||||||
return bestSA;
|
return bestSA;
|
||||||
} // playCounterSpell()
|
} // 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.)
|
// 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()) {
|
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)) {
|
AiPlayDecision op = canPlaySa(sa);
|
||||||
return false;
|
if (op != AiPlayDecision.WillPlay) {
|
||||||
|
return op;
|
||||||
}
|
}
|
||||||
//System.out.printf("wouldPlay: %s, canPay: %s%n", aiWouldPlay, canPay);
|
return ComputerUtilCost.canPayCost(sa, player) ? AiPlayDecision.WillPlay : AiPlayDecision.CantAfford;
|
||||||
return ComputerUtilCost.canPayCost(sa, player);
|
}
|
||||||
|
|
||||||
|
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?
|
// not sure "playing biggest spell" matters?
|
||||||
@@ -700,11 +913,11 @@ public class AiController {
|
|||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
//Spells
|
//Spells
|
||||||
if (sa instanceof Spell) {
|
if (sa instanceof Spell) {
|
||||||
if (!((Spell) sa).canPlayFromEffectAI(player, mandatory, withoutPayingManaCost)) {
|
if (AiPlayDecision.WillPlay != canPlayFromEffectAI((Spell) sa, mandatory, withoutPayingManaCost)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sa.canPlayAI(player)) {
|
if (AiPlayDecision.WillPlay == canPlaySa(sa)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -720,6 +933,91 @@ public class AiController {
|
|||||||
return null;
|
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() {
|
public SpellAbility choooseSpellAbilityToPlay() {
|
||||||
final PhaseType phase = game.getPhaseHandler().getPhase();
|
final PhaseType phase = game.getPhaseHandler().getPhase();
|
||||||
|
|
||||||
@@ -784,8 +1082,37 @@ public class AiController {
|
|||||||
return counterETB;
|
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) {
|
public List<Card> chooseCardsToDelve(int colorlessCost, List<Card> grave) {
|
||||||
List<Card> toExile = new ArrayList<Card>();
|
List<Card> toExile = new ArrayList<Card>();
|
||||||
@@ -818,13 +1145,74 @@ public class AiController {
|
|||||||
return toExile;
|
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) {
|
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
|
||||||
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
|
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
|
||||||
|
|
||||||
List<SpellAbility> result = new ArrayList<SpellAbility>();
|
List<SpellAbility> result = new ArrayList<SpellAbility>();
|
||||||
for(SpellAbility sa : usableFromOpeningHand) {
|
for(SpellAbility sa : usableFromOpeningHand) {
|
||||||
// Is there a better way for the AI to decide this?
|
// Is there a better way for the AI to decide this?
|
||||||
if (sa.doTrigger(false, player)) {
|
if (doTrigger(sa, false)) {
|
||||||
result.add(sa);
|
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
|
// don't use abilities with dangerous drawbacks
|
||||||
AbilitySub sub = m.getSubAbility();
|
AbilitySub sub = m.getSubAbility();
|
||||||
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
|
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
|
||||||
@@ -688,7 +688,7 @@ public class ComputerUtilMana {
|
|||||||
// don't use abilities with dangerous drawbacks
|
// don't use abilities with dangerous drawbacks
|
||||||
AbilitySub sub = m.getSubAbility();
|
AbilitySub sub = m.getSubAbility();
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
if (!sub.getAi().chkDrawbackWithSubs(ai, sub)) {
|
if (!sub.getApi().getAi().chkDrawbackWithSubs(ai, sub)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ public abstract class SpellAbilityAi extends SaTargetRoutines {
|
|||||||
*/
|
*/
|
||||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||||
final AbilitySub subAb = ab.getSubAbility();
|
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) {
|
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();
|
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();
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
chance &= subAb == null || subAb.getAi().chkDrawbackWithSubs(ai, subAb);
|
chance &= subAb == null || subAb.getApi().getAi().chkDrawbackWithSubs(ai, subAb);
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiController;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.effects.CharmEffect;
|
import forge.game.ability.effects.CharmEffect;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerControllerAi;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
@@ -45,18 +48,18 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
return choices.subList(1, choices.size());
|
return choices.subList(1, choices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
AbilitySub thisPick = null;
|
AbilitySub thisPick = null;
|
||||||
for (SpellAbility sub : choices) {
|
for (SpellAbility sub : choices) {
|
||||||
sub.setActivatingPlayer(ai);
|
sub.setActivatingPlayer(ai);
|
||||||
if (!playNow && sub.canPlayAI(ai)) {
|
if (!playNow && AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||||
thisPick = (AbilitySub) sub;
|
thisPick = (AbilitySub) sub;
|
||||||
choices.remove(sub);
|
choices.remove(sub);
|
||||||
playNow = true;
|
playNow = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((playNow || i < num - 1) && sub.doTrigger(false, ai)) {
|
if ((playNow || i < num - 1) && aic.doTrigger(sub, false)) {
|
||||||
thisPick = (AbilitySub) sub;
|
thisPick = (AbilitySub) sub;
|
||||||
choices.remove(sub);
|
choices.remove(sub);
|
||||||
break;
|
break;
|
||||||
@@ -71,7 +74,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
AbilitySub thisPick = null;
|
AbilitySub thisPick = null;
|
||||||
for (SpellAbility sub : choices) {
|
for (SpellAbility sub : choices) {
|
||||||
sub.setActivatingPlayer(ai);
|
sub.setActivatingPlayer(ai);
|
||||||
if (sub.doTrigger(true, ai)) {
|
if (aic.doTrigger(sub, true)) {
|
||||||
thisPick = (AbilitySub) sub;
|
thisPick = (AbilitySub) sub;
|
||||||
choices.remove(sub);
|
choices.remove(sub);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiController;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerControllerAi;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
@@ -15,9 +18,9 @@ public class DelayedTriggerAi extends SpellAbilityAi {
|
|||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (trigsa instanceof AbilitySub) {
|
if (trigsa instanceof AbilitySub) {
|
||||||
return ((AbilitySub) trigsa).getAi().chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
return ((AbilitySub) trigsa).getApi().getAi().chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||||
} else {
|
} 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) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final String svarName = sa.getParam("Execute");
|
final String svarName = sa.getParam("Execute");
|
||||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (!sa.hasParam("OptionalDecider")) {
|
if (!sa.hasParam("OptionalDecider")) {
|
||||||
return trigsa.doTrigger(true, ai);
|
return aic.doTrigger(trigsa, true);
|
||||||
} else {
|
} 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 String svarName = sa.getParam("Execute");
|
||||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||||
trigsa.setActivatingPlayer(ai);
|
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
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
AbilitySub subAb = sa.getSubAbility();
|
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;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -10,6 +12,7 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.player.PlayerControllerAi;
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.Spell;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
@@ -120,7 +123,9 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
Spell spell = (Spell) s;
|
Spell spell = (Spell) s;
|
||||||
s.setActivatingPlayer(ai);
|
s.setActivatingPlayer(ai);
|
||||||
// timing restrictions still apply
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.player.PlayerControllerAi;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
@@ -54,6 +56,7 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
repeat.setActivatingPlayer(sa.getActivatingPlayer());
|
repeat.setActivatingPlayer(sa.getActivatingPlayer());
|
||||||
((AbilitySub) repeat).setParent(sa);
|
((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;
|
package forge.game.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.effects.ChangeZoneAllEffect;
|
import forge.game.ability.effects.ChangeZoneAllEffect;
|
||||||
import forge.game.ability.effects.ChangeZoneEffect;
|
import forge.game.ability.effects.ChangeZoneEffect;
|
||||||
import forge.game.ability.effects.ManaEffect;
|
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.Card;
|
||||||
import forge.game.card.CardFactory;
|
import forge.game.card.CardFactory;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.AbilityActivated;
|
import forge.game.spellability.AbilityActivated;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
@@ -17,7 +15,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class AbilityApiBased extends AbilityActivated {
|
public class AbilityApiBased extends AbilityActivated {
|
||||||
private final SpellAbilityEffect effect;
|
private final SpellAbilityEffect effect;
|
||||||
private final SpellAbilityAi ai;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -4183793555528531978L;
|
private static final long serialVersionUID = -4183793555528531978L;
|
||||||
|
|
||||||
@@ -26,7 +23,6 @@ public class AbilityApiBased extends AbilityActivated {
|
|||||||
mapParams.putAll(params0);
|
mapParams.putAll(params0);
|
||||||
api = api0;
|
api = api0;
|
||||||
effect = api.getSpellEffect();
|
effect = api.getSpellEffect();
|
||||||
ai = api.getAi();
|
|
||||||
|
|
||||||
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
|
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
|
||||||
|
|
||||||
@@ -62,14 +58,4 @@ public class AbilityApiBased extends AbilityActivated {
|
|||||||
public void resolve() {
|
public void resolve() {
|
||||||
effect.resolve(this);
|
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;
|
package forge.game.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.effects.ChangeZoneAllEffect;
|
import forge.game.ability.effects.ChangeZoneAllEffect;
|
||||||
import forge.game.ability.effects.ChangeZoneEffect;
|
import forge.game.ability.effects.ChangeZoneEffect;
|
||||||
import forge.game.ability.effects.ManaEffect;
|
import forge.game.ability.effects.ManaEffect;
|
||||||
import forge.game.ability.effects.ManaReflectedEffect;
|
import forge.game.ability.effects.ManaReflectedEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.Spell;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
@@ -17,8 +15,7 @@ import java.util.Map;
|
|||||||
public class SpellApiBased extends Spell {
|
public class SpellApiBased extends Spell {
|
||||||
private static final long serialVersionUID = -6741797239508483250L;
|
private static final long serialVersionUID = -6741797239508483250L;
|
||||||
private final SpellAbilityEffect effect;
|
private final SpellAbilityEffect effect;
|
||||||
private final SpellAbilityAi ai;
|
|
||||||
|
|
||||||
public SpellApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
|
public SpellApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
|
||||||
super(sourceCard, abCost);
|
super(sourceCard, abCost);
|
||||||
this.setTargetRestrictions(tgt);
|
this.setTargetRestrictions(tgt);
|
||||||
@@ -26,7 +23,6 @@ public class SpellApiBased extends Spell {
|
|||||||
mapParams.putAll(params0);
|
mapParams.putAll(params0);
|
||||||
api = api0;
|
api = api0;
|
||||||
effect = api.getSpellEffect();
|
effect = api.getSpellEffect();
|
||||||
ai = api.getAi();
|
|
||||||
|
|
||||||
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
|
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
|
||||||
this.setManaPart(new AbilityManaPart(sourceCard, mapParams));
|
this.setManaPart(new AbilityManaPart(sourceCard, mapParams));
|
||||||
@@ -46,24 +42,8 @@ public class SpellApiBased extends Spell {
|
|||||||
* @see forge.card.spellability.SpellAbility#resolve()
|
* @see forge.card.spellability.SpellAbility#resolve()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canPlayAI(Player aiPlayer) {
|
|
||||||
return ai.canPlayAIWithSubs(aiPlayer, this) && super.canPlayAI(aiPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolve() {
|
public void resolve() {
|
||||||
effect.resolve(this);
|
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;
|
package forge.game.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.effects.ChangeZoneAllEffect;
|
import forge.game.ability.effects.ChangeZoneAllEffect;
|
||||||
import forge.game.ability.effects.ChangeZoneEffect;
|
import forge.game.ability.effects.ChangeZoneEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.AbilityStatic;
|
import forge.game.spellability.AbilityStatic;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
|
||||||
@@ -14,14 +12,12 @@ import java.util.Map;
|
|||||||
public class StaticAbilityApiBased extends AbilityStatic {
|
public class StaticAbilityApiBased extends AbilityStatic {
|
||||||
|
|
||||||
private final SpellAbilityEffect effect;
|
private final SpellAbilityEffect effect;
|
||||||
private final SpellAbilityAi ai;
|
|
||||||
|
|
||||||
public StaticAbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
|
public StaticAbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
|
||||||
super(sourceCard, abCost, tgt);
|
super(sourceCard, abCost, tgt);
|
||||||
mapParams.putAll(params0);
|
mapParams.putAll(params0);
|
||||||
api = api0;
|
api = api0;
|
||||||
effect = api.getSpellEffect();
|
effect = api.getSpellEffect();
|
||||||
ai = api.getAi();
|
|
||||||
|
|
||||||
if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
|
if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
|
||||||
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
|
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
|
||||||
@@ -41,14 +37,4 @@ public class StaticAbilityApiBased extends AbilityStatic {
|
|||||||
public void resolve() {
|
public void resolve() {
|
||||||
effect.resolve(this);
|
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.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.Command;
|
import forge.Command;
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.ComputerUtilCost;
|
|
||||||
import forge.card.CardCharacteristicName;
|
import forge.card.CardCharacteristicName;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
@@ -39,12 +37,19 @@ import forge.game.card.CardPredicates.Presets;
|
|||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.event.GameEventCardStatsChanged;
|
import forge.game.event.GameEventCardStatsChanged;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementHandler;
|
import forge.game.replacement.ReplacementHandler;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
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.Trigger;
|
||||||
import forge.game.trigger.TriggerHandler;
|
import forge.game.trigger.TriggerHandler;
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
@@ -107,15 +112,6 @@ public class CardFactoryUtil {
|
|||||||
card.addIntrinsicKeyword("Haste");
|
card.addIntrinsicKeyword("Haste");
|
||||||
card.setUnearthed(true);
|
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);
|
final AbilityActivated unearth = new AbilityUnearth(sourceCard, cost, null);
|
||||||
|
|
||||||
@@ -308,13 +304,6 @@ public class CardFactoryUtil {
|
|||||||
return sourceCard.getOwner().canCastSorcery();
|
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
|
@Override
|
||||||
public void resolve() {
|
public void resolve() {
|
||||||
final Game game = sourceCard.getGame();
|
final Game game = sourceCard.getGame();
|
||||||
@@ -2888,23 +2877,6 @@ public class CardFactoryUtil {
|
|||||||
card.setEvoked(true);
|
card.setEvoked(true);
|
||||||
game.getAction().moveToPlay(card);
|
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);
|
card.removeIntrinsicKeyword(evokeKeyword);
|
||||||
final StringBuilder desc = new StringBuilder();
|
final StringBuilder desc = new StringBuilder();
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ public class CostPayLife extends CostPart {
|
|||||||
return true;
|
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
|
@Override
|
||||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package forge.game.cost;
|
package forge.game.cost;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -128,9 +129,6 @@ public class CostRemoveAnyCounter extends CostPartWithList {
|
|||||||
return sb.toString();
|
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
|
@Override
|
||||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||||
final String amount = this.getAmount();
|
final String amount = this.getAmount();
|
||||||
|
|||||||
@@ -153,9 +153,6 @@ public class CostRemoveCounter extends CostPartWithList {
|
|||||||
return true;
|
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
|
@Override
|
||||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||||
Card source = ability.getHostCard();
|
Card source = ability.getHostCard();
|
||||||
|
|||||||
@@ -42,42 +42,23 @@ public class CostTap extends CostPart {
|
|||||||
@Override
|
@Override
|
||||||
public boolean isRenewable() { return true; }
|
public boolean isRenewable() { return true; }
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see forge.card.cost.CostPart#toString()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
return "{T}";
|
return "{T}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see forge.card.cost.CostPart#refund(forge.Card)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final void refund(final Card source) {
|
public final void refund(final Card source) {
|
||||||
source.setTapped(false);
|
source.setTapped(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
*
|
|
||||||
* @see
|
|
||||||
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
|
|
||||||
* forge.Card, forge.Player, forge.card.cost.Cost)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean canPay(final SpellAbility ability) {
|
public final boolean canPay(final SpellAbility ability) {
|
||||||
final Card source = ability.getHostCard();
|
final Card source = ability.getHostCard();
|
||||||
return source.isUntapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste."));
|
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
|
@Override
|
||||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||||
ability.getHostCard().tap();
|
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."));
|
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
|
@Override
|
||||||
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
|
||||||
ability.getHostCard().untap();
|
ability.getHostCard().untap();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.ai.ability.CharmAi;
|
import forge.ai.ability.CharmAi;
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
@@ -35,6 +36,7 @@ import forge.game.zone.ZoneType;
|
|||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
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
|
// 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
|
// 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;
|
ret = false;
|
||||||
}
|
}
|
||||||
if (storeChoices) {
|
if (storeChoices) {
|
||||||
@@ -265,12 +267,12 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (mayChooseNewTargets) {
|
if (mayChooseNewTargets) {
|
||||||
if (copySA instanceof Spell) {
|
if (copySA instanceof Spell) {
|
||||||
Spell spell = (Spell) copySA;
|
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?
|
return; // is this legal at all?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
copySA.canPlayAI(player);
|
getAi().canPlaySa(copySA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ComputerUtil.playSpellAbilityForFree(player, copySA);
|
ComputerUtil.playSpellAbilityForFree(player, copySA);
|
||||||
@@ -279,7 +281,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
@Override
|
@Override
|
||||||
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
|
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
|
||||||
if (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);
|
ComputerUtil.playNoStack(player, effectSA, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +346,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
||||||
return ReplacementEffect.aiShouldRun(replacementEffect, effectSA, player);
|
return brains.aiShouldRun(replacementEffect, effectSA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -629,7 +631,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
targetingPlayer.getController().chooseTargetsFor(sa);
|
targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
} else {
|
} else {
|
||||||
sa.doTrigger(isMandatory, player);
|
brains.doTrigger(sa, isMandatory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +647,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||||
Spell spell = (Spell) tgtSA;
|
Spell spell = (Spell) tgtSA;
|
||||||
if (spell.canPlayFromEffectAI(player, !optional, noManaCost) || !optional) {
|
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||||
if (noManaCost) {
|
if (noManaCost) {
|
||||||
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
||||||
} else {
|
} else {
|
||||||
@@ -664,7 +666,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
||||||
return currentAbility.doTrigger(true, player);
|
return brains.doTrigger(currentAbility, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,14 +19,9 @@ package forge.game.replacement;
|
|||||||
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.TriggerReplacementBase;
|
import forge.game.TriggerReplacementBase;
|
||||||
import forge.game.ability.AbilityUtils;
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.Expressions;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -50,7 +45,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
|||||||
public final boolean hasRun() {
|
public final boolean hasRun() {
|
||||||
return this.hasRun;
|
return this.hasRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new replacement effect.
|
* Instantiates a new replacement effect.
|
||||||
*
|
*
|
||||||
@@ -69,60 +64,11 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
|||||||
* Checks if is secondary.
|
* Checks if is secondary.
|
||||||
*
|
*
|
||||||
* @return true, if is secondary
|
* @return true, if is secondary
|
||||||
*/
|
*/
|
||||||
public final boolean isSecondary() {
|
public final boolean isSecondary() {
|
||||||
return this.getMapParams().containsKey("Secondary");
|
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.
|
* Sets the checks for run.
|
||||||
*
|
*
|
||||||
@@ -133,7 +79,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
|||||||
this.hasRun = hasRun;
|
this.hasRun = hasRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can replace.
|
* Can replace.
|
||||||
*
|
*
|
||||||
* @param runParams
|
* @param runParams
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package forge.game.spellability;
|
package forge.game.spellability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
@@ -28,7 +27,6 @@ import forge.game.ability.effects.ManaReflectedEffect;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardFactory;
|
import forge.game.card.CardFactory;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -81,14 +79,7 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
|
|||||||
|
|
||||||
|
|
||||||
private final SpellAbilityEffect effect;
|
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) {
|
public AbilitySub(ApiType api0, final Card ca, final TargetRestrictions tgt, Map<String, String> params0) {
|
||||||
super(ca, Cost.Zero);
|
super(ca, Cost.Zero);
|
||||||
@@ -96,7 +87,6 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
|
|||||||
|
|
||||||
api = api0;
|
api = api0;
|
||||||
mapParams.putAll(params0);
|
mapParams.putAll(params0);
|
||||||
ai = api.getAi();
|
|
||||||
effect = api.getSpellEffect();
|
effect = api.getSpellEffect();
|
||||||
|
|
||||||
if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
|
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);
|
return effect.getStackDescriptionWithSubs(mapParams, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canPlayAI(Player aiPlayer) {
|
|
||||||
return ai.canPlayAIWithSubs(aiPlayer, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolve() {
|
public void resolve() {
|
||||||
effect.resolve(this);
|
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.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPayment;
|
import forge.game.cost.CostPayment;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Expressions;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -124,46 +121,6 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
|||||||
return true;
|
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} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public final Object clone() {
|
public final Object clone() {
|
||||||
@@ -180,21 +137,6 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
|||||||
public boolean isAbility() { return false; }
|
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
|
* @return the castFaceDown
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -210,32 +210,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
*/
|
*/
|
||||||
public abstract void resolve();
|
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>
|
* <p>
|
||||||
* Getter for the field <code>multiKickerManaCost</code>.
|
* Getter for the field <code>multiKickerManaCost</code>.
|
||||||
|
|||||||
@@ -17,31 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
package forge.game.spellability;
|
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 org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.security.InvalidParameterException;
|
import forge.game.card.Card;
|
||||||
import java.util.List;
|
import forge.game.cost.Cost;
|
||||||
import java.util.Map;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -84,282 +64,6 @@ public class SpellPermanent extends Spell {
|
|||||||
|
|
||||||
} // Spell_Permanent()
|
} // 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} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public void resolve() {
|
public void resolve() {
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ public class WrappedAbility extends Ability implements ISpellAbility {
|
|||||||
sa = sa0;
|
sa = sa0;
|
||||||
decider = decider0;
|
decider = decider0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SpellAbility getWrappedAbility() {
|
||||||
|
return sa;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -131,21 +135,11 @@ public class WrappedAbility extends Ability implements ISpellAbility {
|
|||||||
return sa.canPlay();
|
return sa.canPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canPlayAI(Player aiPlayer) {
|
|
||||||
return sa.canPlayAI(aiPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpellAbility copy() {
|
public SpellAbility copy() {
|
||||||
return sa.copy();
|
return sa.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean doTrigger(final boolean mandatory, Player ai) {
|
|
||||||
return sa.doTrigger(mandatory, ai);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player getActivatingPlayer() {
|
public Player getActivatingPlayer() {
|
||||||
return sa.getActivatingPlayer();
|
return sa.getActivatingPlayer();
|
||||||
|
|||||||
@@ -488,7 +488,8 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
targetingPlayer.getController().chooseTargetsFor(sa);
|
targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
} else {
|
} 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");
|
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||||
Spell spell = (Spell) tgtSA;
|
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) {
|
if (noManaCost) {
|
||||||
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
||||||
} else {
|
} else {
|
||||||
@@ -526,7 +528,9 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
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
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user