mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 10:18:01 +00:00
Convert checkApiLogic to AiAbilityDecision
This commit is contained in:
@@ -5,6 +5,8 @@ public enum AiPlayDecision {
|
|||||||
WillPlay,
|
WillPlay,
|
||||||
MandatoryPlay,
|
MandatoryPlay,
|
||||||
PlayToEmptyHand,
|
PlayToEmptyHand,
|
||||||
|
ImpactCombat,
|
||||||
|
ResponseToStackResolve,
|
||||||
AddBoardPresence,
|
AddBoardPresence,
|
||||||
Removal,
|
Removal,
|
||||||
Tempo,
|
Tempo,
|
||||||
@@ -22,14 +24,18 @@ public enum AiPlayDecision {
|
|||||||
CantPlayAi,
|
CantPlayAi,
|
||||||
CantAfford,
|
CantAfford,
|
||||||
CantAffordX,
|
CantAffordX,
|
||||||
|
DoesntImpactCombat,
|
||||||
|
DoesntImpactGame,
|
||||||
MissingLogic,
|
MissingLogic,
|
||||||
MissingNeededCards,
|
MissingNeededCards,
|
||||||
TimingRestrictions,
|
TimingRestrictions,
|
||||||
MissingPhaseRestrictions,
|
MissingPhaseRestrictions,
|
||||||
|
ConditionsNotMet,
|
||||||
NeedsToPlayCriteriaNotMet,
|
NeedsToPlayCriteriaNotMet,
|
||||||
StopRunawayActivations,
|
StopRunawayActivations,
|
||||||
TargetingFailed,
|
TargetingFailed,
|
||||||
CostNotAcceptable,
|
CostNotAcceptable,
|
||||||
|
LifeInDanger,
|
||||||
WouldDestroyLegend,
|
WouldDestroyLegend,
|
||||||
WouldDestroyOtherPlaneswalker,
|
WouldDestroyOtherPlaneswalker,
|
||||||
WouldBecomeZeroToughnessCreature,
|
WouldBecomeZeroToughnessCreature,
|
||||||
@@ -39,7 +45,7 @@ public enum AiPlayDecision {
|
|||||||
|
|
||||||
public boolean willingToPlay() {
|
public boolean willingToPlay() {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
case WillPlay, MandatoryPlay, PlayToEmptyHand, AddBoardPresence, Removal, Tempo, CardAdvantage -> true;
|
case WillPlay, MandatoryPlay, PlayToEmptyHand, AddBoardPresence, ImpactCombat, ResponseToStackResolve, Removal, Tempo, CardAdvantage -> true;
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1819,18 +1819,18 @@ public class ComputerUtilCard {
|
|||||||
* @param sa Pump* or CounterPut*
|
* @param sa Pump* or CounterPut*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) {
|
public static AiAbilityDecision canPumpAgainstRemoval(Player ai, SpellAbility sa) {
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
for (final Card card : cards) {
|
for (final Card card : cards) {
|
||||||
if (objects.contains(card)) {
|
if (objects.contains(card)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For pumps without targeting restrictions, just return immediately until this is fleshed out.
|
// For pumps without targeting restrictions, just return immediately until this is fleshed out.
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection threatenedTargets = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection threatenedTargets = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
@@ -1849,11 +1849,11 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
if (!sa.isTargetNumberValid()) {
|
if (!sa.isTargetNumberValid()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isUselessCreature(Player ai, Card c) {
|
public static boolean isUselessCreature(Player ai, Card c) {
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ public class SpecialAiLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
|
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
|
||||||
public static boolean doAristocratWithCountersLogic(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision doAristocratWithCountersLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
|
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
|
||||||
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||||
@@ -222,14 +222,14 @@ public class SpecialAiLogic {
|
|||||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||||
if (numOtherCreats == 0) {
|
if (numOtherCreats == 0) {
|
||||||
// Cut short if there's nothing to sac at all
|
// Cut short if there's nothing to sac at all
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
|
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
|
||||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||||
if (isDeclareBlockers || isThreatened) {
|
if (isDeclareBlockers || isThreatened) {
|
||||||
if (doAristocratLogic(ai, sa)) {
|
if (doAristocratLogic(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ public class SpecialAiLogic {
|
|||||||
if (countersSa == null) {
|
if (countersSa == null) {
|
||||||
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
|
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
|
||||||
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter SA in chain!");
|
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter SA in chain!");
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -263,7 +263,7 @@ public class SpecialAiLogic {
|
|||||||
relevantCreats.remove(source);
|
relevantCreats.remove(source);
|
||||||
if (relevantCreats.isEmpty()) {
|
if (relevantCreats.isEmpty()) {
|
||||||
// No relevant creatures to sac
|
// No relevant creatures to sac
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
int numCtrs = AbilityUtils.calculateAmount(source, countersSa.getParam("CounterNum"), countersSa);
|
int numCtrs = AbilityUtils.calculateAmount(source, countersSa.getParam("CounterNum"), countersSa);
|
||||||
@@ -287,16 +287,20 @@ public class SpecialAiLogic {
|
|||||||
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat))
|
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat))
|
||||||
);
|
);
|
||||||
if (!forcedSacTgts.isEmpty()) {
|
if (!forcedSacTgts.isEmpty()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
|
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
|
||||||
|
|
||||||
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
|
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
|
||||||
return source.getNetCombatDamage() < lethalDmg
|
if (source.getNetCombatDamage() < lethalDmg
|
||||||
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg;
|
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||||
@@ -309,7 +313,7 @@ public class SpecialAiLogic {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (sacTgts.isEmpty()) {
|
if (sacTgts.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean sourceCantDie = ComputerUtilCombat.combatantCantBeDestroyed(ai, source);
|
final boolean sourceCantDie = ComputerUtilCombat.combatantCantBeDestroyed(ai, source);
|
||||||
@@ -317,7 +321,10 @@ public class SpecialAiLogic {
|
|||||||
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
||||||
|
|
||||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
if (source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||||
@@ -329,7 +336,11 @@ public class SpecialAiLogic {
|
|||||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|
||||||
);
|
);
|
||||||
|
|
||||||
return !sacFodder.isEmpty();
|
if (sacFodder.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Donate
|
// Donate
|
||||||
public static class Donate {
|
public static class Donate {
|
||||||
public static boolean considerTargetingOpponent(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerTargetingOpponent(final Player ai, final SpellAbility sa) {
|
||||||
final Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(
|
final Card donateTarget = ComputerUtil.getCardPreference(ai, sa.getHostCard(), "DonateMe", CardLists.filter(
|
||||||
ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
ai.getCardsIn(ZoneType.Battlefield).threadSafeIterable(), CardPredicates.hasSVar("DonateMe")));
|
||||||
if (donateTarget != null) {
|
if (donateTarget != null) {
|
||||||
@@ -410,7 +410,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// All opponents have hexproof or something like that
|
// All opponents have hexproof or something like that
|
||||||
if (Iterables.isEmpty(oppList)) {
|
if (Iterables.isEmpty(oppList)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter for player who does not have donate target already
|
// filter for player who does not have donate target already
|
||||||
@@ -428,12 +428,11 @@ public class SpecialCardAi {
|
|||||||
if (opp != null) {
|
if (opp != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
// No targets found to donate, so do nothing.
|
// No targets found to donate, so do nothing.
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AiAbilityDecision considerDonatingPermanent(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision considerDonatingPermanent(final Player ai, final SpellAbility sa) {
|
||||||
@@ -452,7 +451,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Electrostatic Pummeler
|
// Electrostatic Pummeler
|
||||||
public static class ElectrostaticPummeler {
|
public static class ElectrostaticPummeler {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
@@ -465,13 +464,13 @@ public class SpecialCardAi {
|
|||||||
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
|
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
|
||||||
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop);
|
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop);
|
||||||
if (source.getNetToughness() - source.getDamage() <= dmg && predictedPT.getRight() - source.getDamage() > dmg)
|
if (source.getNetToughness() - source.getDamage() <= dmg && predictedPT.getRight() - source.getDamage() > dmg)
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not activate if damage will be prevented
|
// Do not activate if damage will be prevented
|
||||||
if (source.staticDamagePrevention(predictedPT.getLeft(), 0, source, true) == 0) {
|
if (source.staticDamagePrevention(predictedPT.getLeft(), 0, source, true) == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate Electrostatic Pummeler's pump only as a combat trick
|
// Activate Electrostatic Pummeler's pump only as a combat trick
|
||||||
@@ -480,14 +479,14 @@ public class SpecialCardAi {
|
|||||||
// We'll try to deal lethal trample/unblocked damage, so remember the card for attack
|
// We'll try to deal lethal trample/unblocked damage, so remember the card for attack
|
||||||
// and wait until declare blockers step.
|
// and wait until declare blockers step.
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (combat == null || !(combat.isAttacking(source) || combat.isBlocking(source))) {
|
if (combat == null || !(combat.isAttacking(source) || combat.isBlocking(source))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isBlocking = combat.isBlocking(source);
|
boolean isBlocking = combat.isBlocking(source);
|
||||||
@@ -512,11 +511,11 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
if (totalDamageToPW >= oppT + loyalty) {
|
if (totalDamageToPW >= oppT + loyalty) {
|
||||||
// Already enough damage to take care of the planeswalker
|
// Already enough damage to take care of the planeswalker
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if ((unblocked || canTrample) && predictedPT.getLeft() >= oppT + loyalty) {
|
if ((unblocked || canTrample) && predictedPT.getLeft() >= oppT + loyalty) {
|
||||||
// Can pump to kill the planeswalker, go for it
|
// Can pump to kill the planeswalker, go for it
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -537,31 +536,31 @@ public class SpecialCardAi {
|
|||||||
// We can deal a lot of damage (either a lot of damage directly to the opponent,
|
// We can deal a lot of damage (either a lot of damage directly to the opponent,
|
||||||
// or kill the blocker(s) and damage the opponent at the same time, so go for it
|
// or kill the blocker(s) and damage the opponent at the same time, so go for it
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (predictedPT.getRight() - source.getDamage() <= oppP && oppHasFirstStrike && !cantDie) {
|
if (predictedPT.getRight() - source.getDamage() <= oppP && oppHasFirstStrike && !cantDie) {
|
||||||
// Can't survive first strike or double strike, don't pump
|
// Can't survive first strike or double strike, don't pump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (predictedPT.getLeft() < oppT && (!cantDie || predictedPT.getRight() - source.getDamage() <= oppP)) {
|
if (predictedPT.getLeft() < oppT && (!cantDie || predictedPT.getRight() - source.getDamage() <= oppP)) {
|
||||||
// Can't pump enough to kill the blockers and survive, don't pump
|
// Can't pump enough to kill the blockers and survive, don't pump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (source.getNetCombatDamage() > oppT && source.getNetToughness() > oppP) {
|
if (source.getNetCombatDamage() > oppT && source.getNetToughness() > oppP) {
|
||||||
// Already enough to kill the blockers and survive, don't overpump
|
// Already enough to kill the blockers and survive, don't overpump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (oppCantDie && !source.hasKeyword(Keyword.TRAMPLE) && !source.isWitherDamage()
|
if (oppCantDie && !source.hasKeyword(Keyword.TRAMPLE) && !source.isWitherDamage()
|
||||||
&& predictedPT.getLeft() <= oppT) {
|
&& predictedPT.getLeft() <= oppT) {
|
||||||
// Can't kill or cripple anyone, as well as can't Trample over, so don't pump
|
// Can't kill or cripple anyone, as well as can't Trample over, so don't pump
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got here, it should be a favorable combat pump, resulting in at least one
|
// If we got here, it should be a favorable combat pump, resulting in at least one
|
||||||
// opposing creature dying, and hopefully with the Pummeler surviving combat.
|
// opposing creature dying, and hopefully with the Pummeler surviving combat.
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean predictOverwhelmingDamage(final Player ai, final SpellAbility sa) {
|
public static boolean predictOverwhelmingDamage(final Player ai, final SpellAbility sa) {
|
||||||
@@ -693,13 +692,13 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Gideon Blackblade
|
// Gideon Blackblade
|
||||||
public static class GideonBlackblade {
|
public static class GideonBlackblade {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
CardCollectionView otb = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(sa));
|
CardCollectionView otb = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isTargetableBy(sa));
|
||||||
if (!otb.isEmpty()) {
|
if (!otb.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestAI(otb));
|
sa.getTargets().add(ComputerUtilCard.getBestAI(otb));
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,12 +929,12 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Living Death (and other similar cards using AILogic LivingDeath or AILogic ReanimateAll)
|
// Living Death (and other similar cards using AILogic LivingDeath or AILogic ReanimateAll)
|
||||||
public static class LivingDeath {
|
public static class LivingDeath {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
||||||
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
||||||
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
||||||
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterEnumType.TIME) > 0) {
|
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterEnumType.TIME) > 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,7 +945,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
if (aiCreaturesInGY.isEmpty()) {
|
if (aiCreaturesInGY.isEmpty()) {
|
||||||
// nothing in graveyard, so cut short
|
// nothing in graveyard, so cut short
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card c : ai.getCreaturesInPlay()) {
|
for (Card c : ai.getCreaturesInPlay()) {
|
||||||
@@ -978,17 +977,30 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we get more value out of this than our opponent does (hopefully), go for it
|
// if we get more value out of this than our opponent does (hopefully), go for it
|
||||||
return (aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower + threshold);
|
if ((aiGraveyardPower - aiBattlefieldPower) > (oppGraveyardPower - oppBattlefieldPower + threshold)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maze's End
|
// Maze's End
|
||||||
public static class MazesEnd {
|
public static class MazesEnd {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
||||||
|
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && !availableGates.isEmpty();
|
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && !availableGates.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableGates.isEmpty()) {
|
||||||
|
// No gates available, so don't activate Maze's End
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Card considerCardToGet(final Player ai, final SpellAbility sa)
|
public static Card considerCardToGet(final Player ai, final SpellAbility sa)
|
||||||
@@ -1146,13 +1158,13 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Necropotence
|
// Necropotence
|
||||||
public static class Necropotence {
|
public static class Necropotence {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
int computerHandSize = ai.getZone(ZoneType.Hand).size();
|
int computerHandSize = ai.getZone(ZoneType.Hand).size();
|
||||||
int maxHandSize = ai.getMaxHandSize();
|
int maxHandSize = ai.getMaxHandSize();
|
||||||
|
|
||||||
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return false; // nothing to draw from the library
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.nameEquals("Yawgmoth's Bargain"))) {
|
if (ai.getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.nameEquals("Yawgmoth's Bargain"))) {
|
||||||
@@ -1160,7 +1172,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// TODO: in presence of bad effects which deal damage when a card is drawn, probably better to prefer Necropotence instead?
|
// TODO: in presence of bad effects which deal damage when a card is drawn, probably better to prefer Necropotence instead?
|
||||||
// (not sure how to detect the presence of such effects yet)
|
// (not sure how to detect the presence of such effects yet)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
PhaseHandler ph = game.getPhaseHandler();
|
PhaseHandler ph = game.getPhaseHandler();
|
||||||
@@ -1182,23 +1194,33 @@ public class SpecialCardAi {
|
|||||||
// We're in a situation when we have nothing castable in hand, something needs to be done
|
// We're in a situation when we have nothing castable in hand, something needs to be done
|
||||||
if (!blackViseOTB) {
|
if (!blackViseOTB) {
|
||||||
// exile-loot +1 card when at max hand size, hoping to get a workable spell or land
|
// exile-loot +1 card when at max hand size, hoping to get a workable spell or land
|
||||||
return computerHandSize + exiledWithNecro - 1 == maxHandSize;
|
if (computerHandSize + exiledWithNecro - 1 == maxHandSize) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Loot to 7 in presence of Black Vise, hoping to find what to do
|
// Loot to 7 in presence of Black Vise, hoping to find what to do
|
||||||
// NOTE: can still currently get theoretically locked with 7 uncastable spells. Loot to 8 instead?
|
// NOTE: can still currently get theoretically locked with 7 uncastable spells. Loot to 8 instead?
|
||||||
return computerHandSize + exiledWithNecro <= maxHandSize;
|
if (computerHandSize + exiledWithNecro <= maxHandSize) {
|
||||||
|
// Loot to 7, hoping to find something playable
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// Loot to 8, hoping to find something playable
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (blackViseOTB && computerHandSize + exiledWithNecro - 1 >= 4) {
|
} else if (blackViseOTB && computerHandSize + exiledWithNecro - 1 >= 4) {
|
||||||
// try not to overdraw in presence of Black Vise
|
// try not to overdraw in presence of Black Vise
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (computerHandSize + exiledWithNecro - 1 >= maxHandSize) {
|
} else if (computerHandSize + exiledWithNecro - 1 >= maxHandSize) {
|
||||||
// Only draw until we reach max hand size
|
// Only draw until we reach max hand size
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (!ph.isPlayerTurn(ai) || !ph.is(PhaseType.MAIN2)) {
|
} else if (!ph.isPlayerTurn(ai) || !ph.is(PhaseType.MAIN2)) {
|
||||||
// Only activate in AI's own turn (sans the exception above)
|
// Only activate in AI's own turn (sans the exception above)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1480,7 +1502,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// Sorin, Vengeful Bloodlord
|
// Sorin, Vengeful Bloodlord
|
||||||
public static class SorinVengefulBloodlord {
|
public static class SorinVengefulBloodlord {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
|
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
|
||||||
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
||||||
CardPredicates.CREATURES
|
CardPredicates.CREATURES
|
||||||
@@ -1494,7 +1516,7 @@ public class SpecialCardAi {
|
|||||||
CardLists.sortByCmcDesc(creaturesToGet);
|
CardLists.sortByCmcDesc(creaturesToGet);
|
||||||
|
|
||||||
if (creaturesToGet.isEmpty()) {
|
if (creaturesToGet.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pick the best creature that will stay on the battlefield
|
// pick the best creature that will stay on the battlefield
|
||||||
@@ -1510,10 +1532,10 @@ public class SpecialCardAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
sa.setXManaCostPaid(best.getCMC());
|
sa.setXManaCostPaid(best.getCMC());
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1627,23 +1649,27 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
// The One Ring
|
// The One Ring
|
||||||
public static class TheOneRing {
|
public static class TheOneRing {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
if (!ai.canLoseLife() || ai.cantLoseForZeroOrLessLife()) {
|
if (!ai.canLoseLife() || ai.cantLoseForZeroOrLessLife()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
int lifeInDanger = aic.getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD);
|
int lifeInDanger = aic.getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD);
|
||||||
int numCtrs = sa.getHostCard().getCounters(CounterEnumType.BURDEN);
|
int numCtrs = sa.getHostCard().getCounters(CounterEnumType.BURDEN);
|
||||||
|
|
||||||
return ai.getLife() > numCtrs + 1 && ai.getLife() > lifeInDanger
|
if (ai.getLife() > numCtrs + 1 && ai.getLife() > lifeInDanger
|
||||||
&& ai.getMaxHandSize() >= ai.getCardsIn(ZoneType.Hand).size() + numCtrs + 1;
|
&& ai.getMaxHandSize() >= ai.getCardsIn(ZoneType.Hand).size() + numCtrs + 1) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.LifeInDanger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Scarab God
|
// The Scarab God
|
||||||
public static class TheScarabGod {
|
public static class TheScarabGod {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
Card bestOppCreat = ComputerUtilCard.getBestAI(CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
Card bestOppCreat = ComputerUtilCard.getBestAI(CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
||||||
Card worstOwnCreat = ComputerUtilCard.getWorstAI(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
Card worstOwnCreat = ComputerUtilCard.getWorstAI(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES));
|
||||||
|
|
||||||
@@ -1654,13 +1680,19 @@ public class SpecialCardAi {
|
|||||||
sa.getTargets().add(worstOwnCreat);
|
sa.getTargets().add(worstOwnCreat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().size() > 0;
|
if (!sa.getTargets().isEmpty()) {
|
||||||
|
// If we have a target, we can play this ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// No valid targets, can't play this ability
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timetwister
|
// Timetwister
|
||||||
public static class Timetwister {
|
public static class Timetwister {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) {
|
||||||
final int aiHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
final int aiHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
int maxOppHandSize = 0;
|
int maxOppHandSize = 0;
|
||||||
|
|
||||||
@@ -1674,7 +1706,14 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use in case we're getting low on cards or if we're significantly behind our opponent in cards in hand
|
// use in case we're getting low on cards or if we're significantly behind our opponent in cards in hand
|
||||||
return aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD;
|
if (aiHandSize < HAND_SIZE_THRESHOLD || maxOppHandSize - aiHandSize > HAND_SIZE_THRESHOLD) {
|
||||||
|
// if the AI has less than 3 cards in hand or the opponent has more than 3 cards in hand than the AI
|
||||||
|
// then the AI is willing to play this ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// otherwise, don't play this ability
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,14 +95,15 @@ public abstract class SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingPhaseRestrictions);
|
return new AiAbilityDecision(0, AiPlayDecision.MissingPhaseRestrictions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkApiLogic(ai, sa)) {
|
AiAbilityDecision decision = checkApiLogic(ai, sa);
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
if (!decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
// needs to be after API logic because needs to check possible X Cost?
|
// needs to be after API logic because needs to check possible X Cost?
|
||||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||||
@@ -169,12 +170,18 @@ public abstract class SpellAbilityAi {
|
|||||||
/**
|
/**
|
||||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||||
*/
|
*/
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MyRandom.getRandom().nextFloat() < .8f;
|
if (MyRandom.getRandom().nextFloat() < .8f) {
|
||||||
|
// 80% chance to play the ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 20% chance to not play the ability
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -16,7 +18,7 @@ import java.util.Map;
|
|||||||
public class AlterAttributeAi extends SpellAbilityAi {
|
public class AlterAttributeAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
boolean activate = Boolean.parseBoolean(sa.getParamOrDefault("Activate", "true"));
|
boolean activate = Boolean.parseBoolean(sa.getParamOrDefault("Activate", "true"));
|
||||||
String[] attributes = sa.getParam("Attributes").split(",");
|
String[] attributes = sa.getParam("Attributes").split(",");
|
||||||
@@ -24,7 +26,7 @@ public class AlterAttributeAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
// TODO add targeting logic
|
// TODO add targeting logic
|
||||||
// needed for Suspected
|
// needed for Suspected
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
@@ -36,7 +38,7 @@ public class AlterAttributeAi extends SpellAbilityAi {
|
|||||||
case "Solved":
|
case "Solved":
|
||||||
// there is currently no effect that would un-solve something
|
// there is currently no effect that would un-solve something
|
||||||
if (!c.isSolved() && activate) {
|
if (!c.isSolved() && activate) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Suspect":
|
case "Suspect":
|
||||||
@@ -44,21 +46,21 @@ public class AlterAttributeAi extends SpellAbilityAi {
|
|||||||
// is Suspected good or bad?
|
// is Suspected good or bad?
|
||||||
// currently Suspected is better
|
// currently Suspected is better
|
||||||
if (!activate) {
|
if (!activate) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|
||||||
case "Saddle":
|
case "Saddle":
|
||||||
case "Saddled":
|
case "Saddled":
|
||||||
// AI should not try to Saddle again?
|
// AI should not try to Saddle again?
|
||||||
if (c.isSaddled()) {
|
if (c.isSaddled()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,13 +21,19 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class AmassAi extends SpellAbilityAi {
|
public class AmassAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, final SpellAbility sa) {
|
||||||
CardCollection aiArmies = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Army");
|
CardCollection aiArmies = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Army");
|
||||||
Card host = sa.getHostCard();
|
Card host = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
if (!aiArmies.isEmpty()) {
|
if (!aiArmies.isEmpty()) {
|
||||||
return aiArmies.anyMatch(CardPredicates.canReceiveCounters(CounterEnumType.P1P1));
|
if (aiArmies.anyMatch(CardPredicates.canReceiveCounters(CounterEnumType.P1P1))) {
|
||||||
|
// If AI has an Army that can receive counters, play the ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// AI has Armies but none can receive counters, so don't play
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactGame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final String type = sa.getParam("Type");
|
final String type = sa.getParam("Type");
|
||||||
final String tokenScript = "b_0_0_" + sa.getOriginalParam("Type").toLowerCase() + "_army";
|
final String tokenScript = "b_0_0_" + sa.getOriginalParam("Type").toLowerCase() + "_army";
|
||||||
@@ -36,7 +42,7 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false);
|
Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false);
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
token.setController(ai, 0);
|
token.setController(ai, 0);
|
||||||
@@ -63,7 +69,11 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
//reset static abilities
|
//reset static abilities
|
||||||
game.getAction().checkStaticAbilities(false);
|
game.getAction().checkStaticAbilities(false);
|
||||||
|
|
||||||
return result;
|
if (result) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -83,8 +93,11 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
boolean result = mandatory || checkApiLogic(ai, sa);
|
if (mandatory) {
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -142,21 +142,22 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||||
return false; // what is this for?
|
return new AiAbilityDecision(0, AiPlayDecision.ConditionsNotMet); // what is this for?
|
||||||
}
|
}
|
||||||
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) {
|
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) {
|
||||||
|
// Should I animate a card before i have to sacrifice something better?
|
||||||
if (!isAnimatedThisTurn(aiPlayer, source)) {
|
if (!isAnimatedThisTurn(aiPlayer, source)) {
|
||||||
rememberAnimatedThisTurn(aiPlayer, source);
|
rememberAnimatedThisTurn(aiPlayer, source);
|
||||||
return true; // interrupt sacrifice
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa, new CardCollection())) {
|
if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa, new CardCollection())) {
|
||||||
return false; // prevent crewing with equal or better creatures
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable); // prevent crewing with equal or better creatures
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.costHasManaX() && sa.getSVar("X").equals("Count$xPaid")) {
|
if (sa.costHasManaX() && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
@@ -202,16 +203,16 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
if (!isSorcerySpeed(sa, aiPlayer) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||||
if (sa.isCrew() && c.isCreature()) {
|
if (sa.isCrew() && c.isCreature()) {
|
||||||
// Do not try to crew a vehicle which is already a creature
|
// Do not try to crew a vehicle which is already a creature
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
Card animatedCopy = becomeAnimated(c, sa);
|
Card animatedCopy = becomeAnimated(c, sa);
|
||||||
if (ph.isPlayerTurn(aiPlayer)
|
if (ph.isPlayerTurn(aiPlayer)
|
||||||
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(aiPlayer, animatedCopy)) {
|
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(aiPlayer, animatedCopy)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
if (ph.getPlayerTurn().isOpponentOf(aiPlayer)
|
if (ph.getPlayerTurn().isOpponentOf(aiPlayer)
|
||||||
&& !ComputerUtilCard.doesSpecifiedCreatureBlock(aiPlayer, animatedCopy)) {
|
&& !ComputerUtilCard.doesSpecifiedCreatureBlock(aiPlayer, animatedCopy)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
// also check if maybe there are static effects applied to the animated copy that would matter
|
// also check if maybe there are static effects applied to the animated copy that would matter
|
||||||
// (e.g. Myth Realized)
|
// (e.g. Myth Realized)
|
||||||
@@ -227,11 +228,12 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (bFlag) {
|
if (bFlag) {
|
||||||
rememberAnimatedThisTurn(aiPlayer, sa.getHostCard());
|
rememberAnimatedThisTurn(aiPlayer, sa.getHostCard());
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return bFlag; // All of the defined stuff is animated, not very useful
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return animateTgtAI(sa).willingToPlay();
|
return animateTgtAI(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ public class AssembleContraptionAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if ("X".equals(sa.getParam("Amount")) && sa.getSVar("X").equals("Count$xPaid")) {
|
if ("X".equals(sa.getParam("Amount")) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
if (xPay == 0) {
|
if (xPay == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
sa.getRootAbility().setXManaCostPaid(xPay);
|
sa.getRootAbility().setXManaCostPaid(xPay);
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ public class AssembleContraptionAi extends SpellAbilityAi {
|
|||||||
if(target != null)
|
if(target != null)
|
||||||
sa.getTargets().add(target);
|
sa.getTargets().add(target);
|
||||||
else
|
else
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkApiLogic(ai, sa);
|
return super.checkApiLogic(ai, sa);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilAbility;
|
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -21,7 +18,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Game game = sa.getHostCard().getGame();
|
final Game game = sa.getHostCard().getGame();
|
||||||
final SpellAbility topSa = game.getStack().isEmpty() ? null
|
final SpellAbility topSa = game.getStack().isEmpty() ? null
|
||||||
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||||
@@ -32,47 +29,50 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// The AI can't otherwise play this ability, but should at least not
|
// The AI can't otherwise play this ability, but should at least not
|
||||||
// miss mandatory activations (e.g. triggers).
|
// miss mandatory activations (e.g. triggers).
|
||||||
return sa.isMandatory();
|
if (sa.isMandatory()) {
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
|
private AiAbilityDecision doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
|
||||||
// For cards like Spellskite that retarget spells to itself
|
// For cards like Spellskite that retarget spells to itself
|
||||||
if (topSa == null) {
|
if (topSa == null) {
|
||||||
// nothing on stack, so nothing to target
|
// nothing on stack, so nothing to target
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
final TargetChoices topTargets = topSa.getTargets();
|
final TargetChoices topTargets = topSa.getTargets();
|
||||||
final Card topHost = topSa.getHostCard();
|
final Card topHost = topSa.getHostCard();
|
||||||
|
|
||||||
if (sa.getTargets().size() != 0 && sa.isTrigger()) {
|
if (!sa.getTargets().isEmpty() && sa.isTrigger()) {
|
||||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
|
if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
|
||||||
// if this does not target at all or already targets host, no need to redirect it again
|
// if this does not target at all or already targets host, no need to redirect it again
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card tgt : topTargets.getTargetCards()) {
|
for (Card tgt : topTargets.getTargetCards()) {
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
||||||
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
||||||
// no need to retarget again to another one
|
// no need to retarget again to another one
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
|
if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
|
||||||
// make sure not to redirect our own abilities
|
// make sure not to redirect our own abilities
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (!topSa.canTarget(sa.getHostCard())) {
|
if (!topSa.canTarget(sa.getHostCard())) {
|
||||||
// don't try targeting it if we can't legally target the host card with it in the first place
|
// don't try targeting it if we can't legally target the host card with it in the first place
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if (!sa.canTarget(topSa)) {
|
if (!sa.canTarget(topSa)) {
|
||||||
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
|
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
||||||
@@ -85,22 +85,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||||
&& topTargets.contains(aiPlayer)) {
|
&& topTargets.contains(aiPlayer)) {
|
||||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Card firstCard = topTargets.getFirstTargetedCard();
|
Card firstCard = topTargets.getFirstTargetedCard();
|
||||||
// if we're not the target don't intervene unless we can steal a buff
|
// if we're not the target don't intervene unless we can steal a buff
|
||||||
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
|
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
Player firstPlayer = topTargets.getFirstTargetedPlayer();
|
Player firstPlayer = topTargets.getFirstTargetedPlayer();
|
||||||
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
|
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(topSa);
|
sa.getTargets().add(topSa);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,14 +129,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
// Checks for "return true" unlike checkAiLogic()
|
// Checks for "return true" unlike checkAiLogic()
|
||||||
|
|
||||||
multipleCardsToChoose.clear();
|
multipleCardsToChoose.clear();
|
||||||
String aiLogic = sa.getParam("AILogic");
|
String aiLogic = sa.getParam("AILogic");
|
||||||
if (aiLogic != null) {
|
if (aiLogic != null) {
|
||||||
if (aiLogic.equals("Always")) {
|
if (aiLogic.equals("Always")) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (aiLogic.startsWith("SacAndUpgrade")) { // Birthing Pod, Natural Order, etc.
|
} else if (aiLogic.startsWith("SacAndUpgrade")) { // Birthing Pod, Natural Order, etc.
|
||||||
return doSacAndUpgradeLogic(aiPlayer, sa);
|
return doSacAndUpgradeLogic(aiPlayer, sa);
|
||||||
} else if (aiLogic.startsWith("SacAndRetFromGrave")) { // Recurring Nightmare, etc.
|
} else if (aiLogic.startsWith("SacAndRetFromGrave")) { // Recurring Nightmare, etc.
|
||||||
@@ -156,10 +156,18 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} else if (aiLogic.equals("MazesEnd")) {
|
} else if (aiLogic.equals("MazesEnd")) {
|
||||||
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("Pongify")) {
|
} else if (aiLogic.equals("Pongify")) {
|
||||||
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
if (sa.isTargetNumberValid()) {
|
||||||
|
// Pre-targeted in checkAiLogic
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
} else if (aiLogic.equals("ReturnCastable")) {
|
} else if (aiLogic.equals("ReturnCastable")) {
|
||||||
return !sa.getHostCard().getExiledCards().isEmpty()
|
if (!sa.getHostCard().getExiledCards().isEmpty()
|
||||||
&& ComputerUtilMana.canPayManaCost(sa.getHostCard().getExiledCards().getFirst().getFirstSpellAbility(), aiPlayer, 0, false);
|
&& ComputerUtilMana.canPayManaCost(sa.getHostCard().getExiledCards().getFirst().getFirstSpellAbility(), aiPlayer, 0, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
@@ -258,7 +266,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean hiddenOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
private static AiAbilityDecision hiddenOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
// Fetching should occur fairly often as it helps cast more spells, and
|
// Fetching should occur fairly often as it helps cast more spells, and
|
||||||
// have access to more mana
|
// have access to more mana
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
@@ -275,7 +283,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
// This happens when Origin is something like
|
// This happens when Origin is something like
|
||||||
// "Graveyard,Library" (Doomsday)
|
// "Graveyard,Library" (Doomsday)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
@@ -284,11 +292,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// AI currently disabled for these costs
|
// AI currently disabled for these costs
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)
|
||||||
&& !(destination.equals("Battlefield") && !source.isLand())) {
|
&& !(destination.equals("Battlefield") && !source.isLand())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
|
||||||
@@ -297,7 +305,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
CostDiscard cd = (CostDiscard) part;
|
CostDiscard cd = (CostDiscard) part;
|
||||||
// this is mainly for typecycling
|
// this is mainly for typecycling
|
||||||
if (!cd.payCostFromSource() || !ComputerUtil.isWorseThanDraw(ai, source)) {
|
if (!cd.payCostFromSource() || !ComputerUtil.isWorseThanDraw(ai, source)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,14 +313,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (sa.isNinjutsu()) {
|
if (sa.isNinjutsu()) {
|
||||||
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
}
|
}
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getGame().getCombat() == null) {
|
if (ai.getGame().getCombat() == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
List<Card> attackers = ai.getGame().getCombat().getUnblockedAttackers();
|
List<Card> attackers = ai.getGame().getCombat().getUnblockedAttackers();
|
||||||
boolean lowerCMC = false;
|
boolean lowerCMC = false;
|
||||||
@@ -323,7 +331,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!lowerCMC) {
|
if (!lowerCMC) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,15 +341,15 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final AbilitySub abSub = sa.getSubAbility();
|
final AbilitySub abSub = sa.getSubAbility();
|
||||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||||
if (!abSub.metConditions()) {
|
if (!abSub.metConditions()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.ConditionsNotMet);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.ConditionsNotMet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Player> pDefined = Lists.newArrayList(source.getController());
|
Iterable<Player> pDefined = Lists.newArrayList(source.getController());
|
||||||
@@ -355,7 +363,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
if (!sa.isTargetNumberValid()) {
|
if (!sa.isTargetNumberValid()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
pDefined = sa.getTargets().getTargetPlayers();
|
pDefined = sa.getTargets().getTargetPlayers();
|
||||||
} else {
|
} else {
|
||||||
@@ -399,12 +407,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!activateForCost && list.isEmpty()) {
|
if (!activateForCost && list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
if ("Atarka's Command".equals(sourceName)
|
if ("Atarka's Command".equals(sourceName)
|
||||||
&& (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
|
&& (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
|
||||||
// be strict on playing lands off charms
|
// be strict on playing lands off charms
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
String num = sa.getParamOrDefault("ChangeNum", "1");
|
String num = sa.getParamOrDefault("ChangeNum", "1");
|
||||||
@@ -412,55 +420,65 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (sa.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
if (xPay == 0) return false;
|
if (xPay == 0) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
|
}
|
||||||
xPay = Math.min(xPay, list.size());
|
xPay = Math.min(xPay, list.size());
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
} else {
|
} else {
|
||||||
// Figure out the X amount, bail if it's zero (nothing will change zone).
|
// Figure out the X amount, bail if it's zero (nothing will change zone).
|
||||||
int xValue = AbilityUtils.calculateAmount(source, "X", sa);
|
int xValue = AbilityUtils.calculateAmount(source, "X", sa);
|
||||||
if (xValue == 0) {
|
if (xValue == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Temur Sabertooth")) {
|
if (sourceName.equals("Temur Sabertooth")) {
|
||||||
// activated bounce + pump
|
// activated bounce + pump
|
||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa.getSubAbility(), source, 0, 0, Arrays.asList("Indestructible")) ||
|
boolean pumpDecision = ComputerUtilCard.shouldPumpCard(ai, sa.getSubAbility(), source, 0, 0, Arrays.asList("Indestructible"));
|
||||||
ComputerUtilCard.canPumpAgainstRemoval(ai, sa.getSubAbility())) {
|
AiAbilityDecision saveDecision = ComputerUtilCard.canPumpAgainstRemoval(ai, sa.getSubAbility());
|
||||||
|
if (pumpDecision || saveDecision.willingToPlay()) {
|
||||||
for (Card c : list) {
|
for (Card c : list) {
|
||||||
if (ComputerUtilCard.evaluateCreature(c) < ComputerUtilCard.evaluateCreature(source)) {
|
if (ComputerUtilCard.evaluateCreature(c) < ComputerUtilCard.evaluateCreature(source)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ResponseToStackResolve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return canBouncePermanent(ai, sa, list) != null;
|
if (canBouncePermanent(ai, sa, list) != null) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't use fetching to top of library/graveyard before main2
|
// don't use fetching to top of library/graveyard before main2
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||||
&& !sa.hasParam("ActivationPhases")) {
|
&& !sa.hasParam("ActivationPhases")) {
|
||||||
if (!destination.equals("Battlefield") && !destination.equals("Hand")) {
|
if (!destination.equals("Battlefield") && !destination.equals("Hand")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// Only tutor something in main1 if hand is almost empty
|
// Only tutor something in main1 if hand is almost empty
|
||||||
if (ai.getCardsIn(ZoneType.Hand).size() > 1 && destination.equals("Hand")
|
if (ai.getCardsIn(ZoneType.Hand).size() > 1 && destination.equals("Hand")
|
||||||
&& !aiLogic.equals("AnyMainPhase")) {
|
&& !aiLogic.equals("AnyMainPhase")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
return subAb == null || SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb).willingToPlay();
|
if (subAb == null) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -681,7 +699,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private static boolean knownOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
private static AiAbilityDecision knownOriginCanPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
// Retrieve either this card, or target Cards in Graveyard
|
// Retrieve either this card, or target Cards in Graveyard
|
||||||
|
|
||||||
final List<ZoneType> origin = Lists.newArrayList();
|
final List<ZoneType> origin = Lists.newArrayList();
|
||||||
@@ -694,19 +712,19 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!isPreferredTarget(ai, sa, false, false)) {
|
if (!isPreferredTarget(ai, sa, false, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// non-targeted retrieval
|
// non-targeted retrieval
|
||||||
final List<Card> retrieval = sa.knownDetermineDefined(sa.getParam("Defined"));
|
final List<Card> retrieval = sa.knownDetermineDefined(sa.getParam("Defined"));
|
||||||
|
|
||||||
if (retrieval == null || retrieval.isEmpty()) {
|
if (retrieval == null || retrieval.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return this card from graveyard: cards like Hammer of Bogardan
|
// return this card from graveyard: cards like Hammer of Bogardan
|
||||||
@@ -717,7 +735,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// (dying or losing control of)
|
// (dying or losing control of)
|
||||||
if (origin.contains(ZoneType.Battlefield)) {
|
if (origin.contains(ZoneType.Battlefield)) {
|
||||||
if (ai.getGame().getStack().isEmpty()) {
|
if (ai.getGame().getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbilitySub abSub = sa.getSubAbility();
|
final AbilitySub abSub = sa.getSubAbility();
|
||||||
@@ -730,7 +748,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (!(destination.equals(ZoneType.Exile)
|
if (!(destination.equals(ZoneType.Exile)
|
||||||
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
|
&& (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
|
||||||
&& !destination.equals(ZoneType.Hand)) {
|
&& !destination.equals(ZoneType.Hand)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
|
||||||
@@ -742,13 +760,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!contains) {
|
if (!contains) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destination == ZoneType.Battlefield) {
|
if (destination == ZoneType.Battlefield) {
|
||||||
if (ComputerUtil.isETBprevented(retrieval.get(0))) {
|
if (ComputerUtil.isETBprevented(retrieval.get(0))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// predict whether something may put a ETBing creature below zero toughness
|
// predict whether something may put a ETBing creature below zero toughness
|
||||||
@@ -758,7 +776,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final Card copy = CardCopyService.getLKICopy(c);
|
final Card copy = CardCopyService.getLKICopy(c);
|
||||||
ComputerUtilCard.applyStaticContPT(c.getGame(), copy, null);
|
ComputerUtilCard.applyStaticContPT(c.getGame(), copy, null);
|
||||||
if (copy.getNetToughness() <= 0) {
|
if (copy.getNetToughness() <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,13 +790,17 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nothingWillReturn) {
|
if (nothingWillReturn) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
return subAb == null || SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb).willingToPlay();
|
if (subAb == null) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpellApiToAi.Converter.get(subAb).chkDrawbackWithSubs(ai, subAb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1816,7 +1838,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return super.chooseSingleAttackableEntity(ai, sa, options, params);
|
return super.chooseSingleAttackableEntity(ai, sa, options, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) {
|
private AiAbilityDecision doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
||||||
|
|
||||||
@@ -1835,14 +1857,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(bestRet);
|
sa.getTargets().add(bestRet);
|
||||||
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + worstSac.getCMC());
|
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + worstSac.getCMC());
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSacAndUpgradeLogic(final Player ai, final SpellAbility sa) {
|
private AiAbilityDecision doSacAndUpgradeLogic(final Player ai, final SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
@@ -1850,7 +1872,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!ph.is(PhaseType.MAIN2)) {
|
if (!ph.is(PhaseType.MAIN2)) {
|
||||||
// Should be given a chance to cast other spells as well as to use a previously upgraded creature
|
// Should be given a chance to cast other spells as well as to use a previously upgraded creature
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
|
|
||||||
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
String definedSac = StringUtils.split(source.getSVar("AIPreference"), "$")[1];
|
||||||
@@ -1889,12 +1911,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (!listGoal.isEmpty()) {
|
if (!listGoal.isEmpty()) {
|
||||||
// make sure we're upgrading sacCMC->goalCMC
|
// make sure we're upgrading sacCMC->goalCMC
|
||||||
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + sacCMC);
|
source.setSVar("AIPreferenceOverride", "Creature.cmcEQ" + sacCMC);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no candidates to upgrade
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AiAbilityDecision doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
||||||
|
|||||||
@@ -69,11 +69,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||||
|
|
||||||
if ("LivingDeath".equals(aiLogic)) {
|
if ("LivingDeath".equals(aiLogic)) {
|
||||||
boolean result = SpecialCardAi.LivingDeath.consider(ai, sa);
|
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
} else if ("Timetwister".equals(aiLogic)) {
|
} else if ("Timetwister".equals(aiLogic)) {
|
||||||
boolean result = SpecialCardAi.Timetwister.consider(ai, sa);
|
return SpecialCardAi.Timetwister.consider(ai, sa);
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
} else if ("RetDiscardedThisTurn".equals(aiLogic)) {
|
} else if ("RetDiscardedThisTurn".equals(aiLogic)) {
|
||||||
boolean result = !ai.getDiscardedThisTurn().isEmpty() && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
boolean result = !ai.getDiscardedThisTurn().isEmpty() && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class CharmAi extends SpellAbilityAi {
|
public class CharmAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||||
|
|
||||||
@@ -70,10 +70,10 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
||||||
chosenList = chooseOptionsAi(sa, choices, ai, true, num, min);
|
chosenList = chooseOptionsAi(sa, choices, ai, true, num, min);
|
||||||
if (chosenList.isEmpty() && min != 0) {
|
if (chosenList.isEmpty() && min != 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
sa.setChosenList(chosenList);
|
sa.setChosenList(chosenList);
|
||||||
|
|
||||||
if (choiceForOpp) {
|
if (choiceForOpp) {
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
@@ -90,7 +90,11 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
if (MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn())) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choices, final Player ai, boolean isTrigger, int num, int min) {
|
private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choices, final Player ai, boolean isTrigger, int num, int min) {
|
||||||
|
|||||||
@@ -26,19 +26,19 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
// search targetable Opponents
|
// search targetable Opponents
|
||||||
final List<Player> oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
final List<Player> oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,11 +140,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkApiLogic(ai, sa)) {
|
return checkApiLogic(ai, sa);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||||
|
|||||||
@@ -43,8 +43,13 @@ public class ChooseGenericAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
return sa.hasParam("AILogic");
|
if (sa.hasParam("AILogic")) {
|
||||||
|
// This is equivilant to what was here before but feels bad
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -52,10 +57,14 @@ public class ChooseGenericAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AiAbilityDecision chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public AiAbilityDecision chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
boolean result = sa.isTrigger()
|
AiAbilityDecision decision;
|
||||||
? doTriggerAINoCost(aiPlayer, sa, sa.isMandatory()).willingToPlay()
|
if (sa.isTrigger()) {
|
||||||
: checkApiLogic(aiPlayer, sa);
|
decision = doTriggerAINoCost(aiPlayer, sa, sa.isMandatory());
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
} else {
|
||||||
|
decision = checkApiLogic(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -73,8 +82,7 @@ public class ChooseGenericAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
AiAbilityDecision superDecision = super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||||
return superDecision;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -42,14 +42,17 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
boolean legalAction = true;
|
boolean legalAction = true;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
legalAction = selectTarget(ai, sa);
|
legalAction = selectTarget(ai, sa);
|
||||||
|
if (!legalAction) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return legalAction;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -19,20 +19,25 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class CountersMoveAi extends SpellAbilityAi {
|
public class CountersMoveAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
AiAbilityDecision decision = new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
AiAbilityDecision decision = moveTgtAI(ai, sa);
|
decision = moveTgtAI(ai, sa);
|
||||||
if (!decision.willingToPlay()) {
|
if (!decision.willingToPlay()) {
|
||||||
return false;
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!playReusable(ai, sa)) {
|
if (!playReusable(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
if (MyRandom.getRandom().nextFloat() < .8f) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import java.util.Map;
|
|||||||
public class CountersMultiplyAi extends SpellAbilityAi {
|
public class CountersMultiplyAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final CounterType counterType = getCounterType(sa);
|
final CounterType counterType = getCounterType(sa);
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
@@ -51,7 +51,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return setTargets(ai, sa);
|
return setTargets(ai, sa);
|
||||||
@@ -87,8 +87,10 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
if (setTargets(ai, sa)) {
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
AiAbilityDecision decision = setTargets(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
} else if (mandatory) {
|
} else if (mandatory) {
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
@@ -98,7 +100,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
.filter(CardPredicates.hasCounters().negate())
|
.filter(CardPredicates.hasCounters().negate())
|
||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
sa.getTargets().add(safeMatch == null ? list.getFirst() : safeMatch);
|
sa.getTargets().add(safeMatch == null ? list.getFirst() : safeMatch);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
@@ -116,7 +118,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setTargets(Player ai, SpellAbility sa) {
|
private AiAbilityDecision setTargets(Player ai, SpellAbility sa) {
|
||||||
final CounterType counterType = getCounterType(sa);
|
final CounterType counterType = getCounterType(sa);
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -172,10 +174,10 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
// targeting does failed
|
// targeting does failed
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list,
|
private void addTargetsByCounterType(final Player ai, final SpellAbility sa, final CardCollection list,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.Map;
|
|||||||
public class CountersProliferateAi extends SpellAbilityAi {
|
public class CountersProliferateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final List<Card> cperms = Lists.newArrayList();
|
final List<Card> cperms = Lists.newArrayList();
|
||||||
boolean allyExpOrEnergy = false;
|
boolean allyExpOrEnergy = false;
|
||||||
|
|
||||||
@@ -68,7 +68,13 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return !cperms.isEmpty() || !hperms.isEmpty() || opponentPoison || allyExpOrEnergy;
|
if (!cperms.isEmpty() || !hperms.isEmpty() || opponentPoison || allyExpOrEnergy) {
|
||||||
|
// AI will play it if there are any counters to proliferate
|
||||||
|
// or if there are no counters, but AI has experience or energy counters
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -92,11 +98,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkApiLogic(ai, sa)) {
|
return checkApiLogic(ai, sa);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, final SpellAbility sa) {
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
// based on what the expected targets could be
|
// based on what the expected targets could be
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
@@ -159,7 +159,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
|
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
|
||||||
if (!poisonList.isEmpty()) {
|
if (!poisonList.isEmpty()) {
|
||||||
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
||||||
return true;
|
return new AiAbilityDecision(1000, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
Card best = ComputerUtilCard.getBestAI(oppCreatM1);
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection aiCreat = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
CardCollection aiCreat = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
||||||
@@ -195,7 +195,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
best = ComputerUtilCard.getBestAI(aiCreat);
|
best = ComputerUtilCard.getBestAI(aiCreat);
|
||||||
if (best != null) {
|
if (best != null) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,28 +204,28 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (!ai.getCounters().isEmpty()) {
|
if (!ai.getCounters().isEmpty()) {
|
||||||
if (!eachExisting || ai.getPoisonCounters() < 5) {
|
if (!eachExisting || ai.getPoisonCounters() < 5) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Never".equals(logic)) {
|
if ("Never".equals(logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if ("AlwaysWithNoTgt".equals(logic)) {
|
} else if ("AlwaysWithNoTgt".equals(logic)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if ("AristocratCounters".equals(logic)) {
|
} else if ("AristocratCounters".equals(logic)) {
|
||||||
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
||||||
} else if ("PayEnergy".equals(logic)) {
|
} else if ("PayEnergy".equals(logic)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if ("PayEnergyConservatively".equals(logic)) {
|
} else if ("PayEnergyConservatively".equals(logic)) {
|
||||||
boolean onlyInCombat = ai.getController().isAI()
|
boolean onlyInCombat = ai.getController().isAI()
|
||||||
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
|
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
|
||||||
@@ -234,10 +234,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
if (playAggro) {
|
if (playAggro) {
|
||||||
// aggro profiles ignore conservative play for this AI logic
|
// aggro profiles ignore conservative play for this AI logic
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (ph.inCombat() && source != null) {
|
} else if (ph.inCombat() && source != null) {
|
||||||
if (ai.getGame().getCombat().isAttacking(source) && !onlyDefensive) {
|
if (ai.getGame().getCombat().isAttacking(source) && !onlyDefensive) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
} else if (ai.getGame().getCombat().isBlocking(source)) {
|
} else if (ai.getGame().getCombat().isBlocking(source)) {
|
||||||
// when blocking, consider this if it's possible to save the blocker and/or kill at least one attacker
|
// when blocking, consider this if it's possible to save the blocker and/or kill at least one attacker
|
||||||
CardCollection blocked = ai.getGame().getCombat().getAttackersBlockedBy(source);
|
CardCollection blocked = ai.getGame().getCombat().getAttackersBlockedBy(source);
|
||||||
@@ -247,7 +247,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
||||||
if (source.getNetToughness() + numActivations > totBlkPower
|
if (source.getNetToughness() + numActivations > totBlkPower
|
||||||
|| source.getNetPower() + numActivations >= totBlkToughness) {
|
|| source.getNetPower() + numActivations >= totBlkToughness) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (sa.getSubAbility() != null
|
} else if (sa.getSubAbility() != null
|
||||||
@@ -257,18 +257,18 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// Bristling Hydra: save from death using a ping activation
|
// Bristling Hydra: save from death using a ping activation
|
||||||
if (ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(source)) {
|
if (ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(source)) {
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
} else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
||||||
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
||||||
// and if there is enough energy saved
|
// and if there is enough energy saved
|
||||||
if (!onlyInCombat) {
|
if (!onlyInCombat) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (logic.equals("MarkOppCreature")) {
|
} else if (logic.equals("MarkOppCreature")) {
|
||||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate<Card> predicate = CardPredicates.hasCounter(CounterType.getType(type));
|
Predicate<Card> predicate = CardPredicates.hasCounter(CounterType.getType(type));
|
||||||
@@ -280,12 +280,12 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Card bestCreat = ComputerUtilCard.getBestCreatureAI(oppCreats);
|
Card bestCreat = ComputerUtilCard.getBestCreatureAI(oppCreats);
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(bestCreat);
|
sa.getTargets().add(bestCreat);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (logic.equals("CheckDFC")) {
|
} else if (logic.equals("CheckDFC")) {
|
||||||
// for cards like Ludevic's Test Subject
|
// for cards like Ludevic's Test Subject
|
||||||
if (!source.canTransform(null)) {
|
if (!source.canTransform(null)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if (logic.startsWith("MoveCounter")) {
|
} else if (logic.startsWith("MoveCounter")) {
|
||||||
return doMoveCounterLogic(ai, sa, ph);
|
return doMoveCounterLogic(ai, sa, ph);
|
||||||
@@ -294,8 +294,15 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
// don't use this for mana until after combat
|
// don't use this for mana until after combat
|
||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||||
|
return new AiAbilityDecision(25, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
return willActivate;
|
|
||||||
|
if (willActivate) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (logic.equals("ChargeToBestCMC")) {
|
} else if (logic.equals("ChargeToBestCMC")) {
|
||||||
return doChargeToCMCLogic(ai, sa);
|
return doChargeToCMCLogic(ai, sa);
|
||||||
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
||||||
@@ -305,14 +312,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||||
return false;
|
return new AiAbilityDecision(100, AiPlayDecision.ConditionsNotMet);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Feat of Resistance")) { // sub-ability should take precedence
|
if (sourceName.equals("Feat of Resistance")) { // sub-ability should take precedence
|
||||||
CardCollection prot = ProtectAi.getProtectCreatures(ai, sa.getSubAbility());
|
CardCollection prot = ProtectAi.getProtectCreatures(ai, sa.getSubAbility());
|
||||||
if (!prot.isEmpty()) {
|
if (!prot.isEmpty()) {
|
||||||
sa.getTargets().add(prot.get(0));
|
sa.getTargets().add(prot.get(0));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,13 +327,13 @@ public class CountersPutAi extends CountersAi {
|
|||||||
CardCollection creatsYouCtrl = ai.getCreaturesInPlay();
|
CardCollection creatsYouCtrl = ai.getCreaturesInPlay();
|
||||||
List<Card> leastToughness = Aggregates.listWithMin(creatsYouCtrl, Card::getNetToughness);
|
List<Card> leastToughness = Aggregates.listWithMin(creatsYouCtrl, Card::getNetToughness);
|
||||||
if (leastToughness.isEmpty()) {
|
if (leastToughness.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
// TODO If Creature that would be Bolstered for some reason is useless, also return False
|
// TODO If Creature that would be Bolstered for some reason is useless, also return False
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
|
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost
|
// TODO handle proper calculation of X values based on Cost
|
||||||
@@ -341,7 +348,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
|
|
||||||
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return doCombatAdaptLogic(source, amount, combat);
|
return doCombatAdaptLogic(source, amount, combat);
|
||||||
}
|
}
|
||||||
@@ -368,12 +375,12 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
||||||
if (curCtrs > Math.ceil(maxCtrs / 2.0)) {
|
if (curCtrs > Math.ceil(maxCtrs / 2.0)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
amount = Math.min(amount, maxCtrs - curCtrs);
|
amount = Math.min(amount, maxCtrs - curCtrs);
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,14 +392,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
.mapToInt(Card::getCMC)
|
.mapToInt(Card::getCMC)
|
||||||
.max().orElse(0);
|
.max().orElse(0);
|
||||||
if (amount > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) {
|
if (amount > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't use it if no counters to add
|
// don't use it if no counters to add
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Polukranos".equals(logic)) {
|
if ("Polukranos".equals(logic)) {
|
||||||
@@ -419,20 +426,20 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canSurvive) {
|
if (!canSurvive) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("AtOppEOT".equals(logic)) {
|
if ("AtOppEOT".equals(logic)) {
|
||||||
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai)) {
|
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,17 +450,19 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
if (!ai.getGame().getStack().isEmpty() && !isSorcerySpeed(sa, ai)) {
|
||||||
// only evaluates case where all tokens are placed on a single target
|
// only evaluates case where all tokens are placed on a single target
|
||||||
if (sa.getMinTargets() < 2) {
|
if (sa.getMinTargets() < 2) {
|
||||||
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
AiAbilityDecision decision = ComputerUtilCard.canPumpAgainstRemoval(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
Card c = sa.getTargetCard();
|
Card c = sa.getTargetCard();
|
||||||
if (sa.getTargets().size() > 1) {
|
if (sa.getTargets().size() > 1) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
sa.addDividedAllocation(c, amount);
|
sa.addDividedAllocation(c, amount);
|
||||||
return true;
|
return decision;
|
||||||
} else {
|
} else {
|
||||||
if (!hasSacCost) { // for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
if (!hasSacCost) {
|
||||||
return false;
|
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,7 +506,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list.size() < sa.getTargetRestrictions().getMinTargets(source, sa)) {
|
if (list.size() < sa.getTargetRestrictions().getMinTargets(source, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate +Loyalty planeswalker abilities even if they have no target (e.g. Vivien of the Arkbow),
|
// Activate +Loyalty planeswalker abilities even if they have no target (e.g. Vivien of the Arkbow),
|
||||||
@@ -506,9 +515,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
&& sa.isPwAbility()
|
&& sa.isPwAbility()
|
||||||
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
||||||
&& sa.isTargetNumberValid()
|
&& sa.isTargetNumberValid()
|
||||||
&& sa.getTargets().size() == 0
|
&& sa.getTargets().isEmpty()
|
||||||
&& ai.getGame().getPhaseHandler().is(PhaseType.MAIN2, ai)) {
|
&& ai.getGame().getPhaseHandler().is(PhaseType.MAIN2, ai)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Abzan Charm")) {
|
if (sourceName.equals("Abzan Charm")) {
|
||||||
@@ -530,11 +539,11 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (left == 0) {
|
if (left == 0) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
@@ -542,7 +551,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -574,10 +583,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// check if other choice will already be played
|
// check if other choice will already be played
|
||||||
increasesCharmOutcome = !choices.get(0).getTargets().isEmpty();
|
increasesCharmOutcome = !choices.get(0).getTargets().isEmpty();
|
||||||
}
|
}
|
||||||
if (!source.isSpell() || increasesCharmOutcome // does not cost a card or can buff charm for no expense
|
if (source != null && !source.isSpell() || increasesCharmOutcome // does not cost a card or can buff charm for no expense
|
||||||
|| ph.getTurn() - source.getTurnInZone() >= source.getGame().getPlayers().size() * 2) {
|
|| ph.getTurn() - source.getTurnInZone() >= source.getGame().getPlayers().size() * 2) {
|
||||||
if (abCost == null || abCost == Cost.Zero
|
if (abCost == Cost.Zero || ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
|| (ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn().isOpponentOf(ai))) {
|
|
||||||
// only use at opponent EOT unless it is free
|
// only use at opponent EOT unless it is free
|
||||||
choice = chooseBoonTarget(list, type);
|
choice = chooseBoonTarget(list, type);
|
||||||
}
|
}
|
||||||
@@ -591,7 +599,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -607,14 +615,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
choice = null;
|
choice = null;
|
||||||
}
|
}
|
||||||
if (sa.getTargets().isEmpty()) {
|
if (sa.getTargets().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
// Don't activate Curse abilities on my cards and non-curse abilities
|
// Don't activate Curse abilities on my cards and non-curse abilities
|
||||||
// on my opponents
|
// on my opponents
|
||||||
if (cards.isEmpty() || (cards.get(0).getController().isOpponentOf(ai) && !sa.isCurse())) {
|
if (cards.isEmpty() || (cards.get(0).getController().isOpponentOf(ai) && !sa.isCurse())) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
||||||
@@ -622,46 +630,46 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// activating this ability.
|
// activating this ability.
|
||||||
|
|
||||||
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (MyRandom.getRandom().nextFloat() < (.1 * currCounters))) {
|
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (MyRandom.getRandom().nextFloat() < (.1 * currCounters))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// Instant +1/+1
|
// Instant +1/+1
|
||||||
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
if (type.equals("P1P1") && !isSorcerySpeed(sa, ai)) {
|
||||||
if (!hasSacCost && !(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
if (!hasSacCost && !(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) {
|
||||||
return false; // only if next turn and cost is reusable
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useless since the card already has the keyword (or for another reason)
|
// Useless since the card already has the keyword (or for another reason)
|
||||||
if (ComputerUtil.isUselessCounter(CounterType.get(type), cards.get(0))) {
|
if (ComputerUtil.isUselessCounter(CounterType.get(type), cards.get(0))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean immediately = ComputerUtil.playImmediately(ai, sa);
|
boolean immediately = ComputerUtil.playImmediately(ai, sa);
|
||||||
|
|
||||||
if (abCost != null && !ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa, immediately)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa, immediately)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediately) {
|
if (immediately) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!type.equals("P1P1") && !type.equals("M1M1") && !sa.hasParam("ActivationPhases")) {
|
if (!type.equals("P1P1") && !type.equals("M1M1") && !sa.hasParam("ActivationPhases")) {
|
||||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
if (ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)) {
|
if (ph.isPlayerTurn(ai) && !isSorcerySpeed(sa, ai)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -772,17 +780,25 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("ChargeToBestCMC".equals(aiLogic)) {
|
if ("ChargeToBestCMC".equals(aiLogic)) {
|
||||||
if (doChargeToCMCLogic(ai, sa) || mandatory) {
|
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
// If the AI logic is to charge to best CMC, we can return true
|
// If the AI logic is to charge to best CMC, we can return true
|
||||||
// if the logic was successfully applied or if it's mandatory.
|
// if the logic was successfully applied or if it's mandatory.
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return decision;
|
||||||
|
} else if (mandatory) {
|
||||||
|
// If the logic was not applied and it's mandatory, we return false.
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
} else {
|
} else {
|
||||||
// If the logic was not applied and it's not mandatory, we return false.
|
// If the logic was not applied and it's not mandatory, we return false.
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else if ("ChargeToBestOppControlledCMC".equals(aiLogic)) {
|
} else if ("ChargeToBestOppControlledCMC".equals(aiLogic)) {
|
||||||
if (doChargeToOppCtrlCMCLogic(ai, sa) || mandatory) {
|
AiAbilityDecision decision = doChargeToOppCtrlCMCLogic(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else if (mandatory) {
|
||||||
|
// If the logic was not applied and it's mandatory, we return false.
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
} else {
|
} else {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
@@ -830,8 +846,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
if (FightAi.canFightAi(ai, sa, nPump, nPump)) {
|
AiAbilityDecision decision = FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1135,7 +1152,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doMoveCounterLogic(final Player ai, SpellAbility sa, PhaseHandler ph) {
|
private AiAbilityDecision doMoveCounterLogic(final Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||||
// Spikes (Tempest)
|
// Spikes (Tempest)
|
||||||
|
|
||||||
// Try not to do it unless at the end of opponent's turn or the creature is threatened
|
// Try not to do it unless at the end of opponent's turn or the creature is threatened
|
||||||
@@ -1148,7 +1165,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|| (combat.isBlocking(source) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, source, combat) && !ComputerUtilCombat.willKillAtLeastOne(ai, source, combat))));
|
|| (combat.isBlocking(source) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, source, combat) && !ComputerUtilCombat.willKillAtLeastOne(ai, source, combat))));
|
||||||
|
|
||||||
if (!(threatened || (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai))) {
|
if (!(threatened || (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai))) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.AnotherTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection targets = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
CardCollection targets = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa);
|
||||||
@@ -1166,45 +1183,45 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
if (bestTgt != null) {
|
if (bestTgt != null) {
|
||||||
sa.getTargets().add(bestTgt);
|
sa.getTargets().add(bestTgt);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doCombatAdaptLogic(Card source, int amount, Combat combat) {
|
private AiAbilityDecision doCombatAdaptLogic(Card source, int amount, Combat combat) {
|
||||||
if (combat.isAttacking(source)) {
|
if (combat.isAttacking(source)) {
|
||||||
if (!combat.isBlocked(source)) {
|
if (!combat.isBlocked(source)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
for (Card blockedBy : combat.getBlockers(source)) {
|
for (Card blockedBy : combat.getBlockers(source)) {
|
||||||
if (blockedBy.getNetToughness() > source.getNetPower()
|
if (blockedBy.getNetToughness() > source.getNetPower()
|
||||||
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
|
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int totBlkPower = Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
int totBlkPower = Aggregates.sum(combat.getBlockers(source), Card::getNetPower);
|
||||||
if (source.getNetToughness() <= totBlkPower
|
if (source.getNetToughness() <= totBlkPower
|
||||||
&& source.getNetToughness() + amount > totBlkPower) {
|
&& source.getNetToughness() + amount > totBlkPower) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (combat.isBlocking(source)) {
|
} else if (combat.isBlocking(source)) {
|
||||||
for (Card blocked : combat.getAttackersBlockedBy(source)) {
|
for (Card blocked : combat.getAttackersBlockedBy(source)) {
|
||||||
if (blocked.getNetToughness() > source.getNetPower()
|
if (blocked.getNetToughness() > source.getNetPower()
|
||||||
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
|
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.ImpactCombat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), Card::getNetPower);
|
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), Card::getNetPower);
|
||||||
if (source.getNetToughness() <= totAtkPower
|
if (source.getNetToughness() <= totAtkPower
|
||||||
&& source.getNetToughness() + amount > totAtkPower) {
|
&& source.getNetToughness() + amount > totAtkPower) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1215,7 +1232,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doChargeToCMCLogic(Player ai, SpellAbility sa) {
|
private AiAbilityDecision doChargeToCMCLogic(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
CardCollectionView ownLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.CREATURES);
|
CardCollectionView ownLib = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.CREATURES);
|
||||||
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
||||||
@@ -1230,10 +1247,16 @@ public class CountersPutAi extends CountersAi {
|
|||||||
optimalCMC = cmc;
|
optimalCMC = cmc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return numCtrs < optimalCMC;
|
if (numCtrs < optimalCMC) {
|
||||||
|
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
CardCollectionView oppInPlay = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.NONLAND_PERMANENTS);
|
CardCollectionView oppInPlay = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.NONLAND_PERMANENTS);
|
||||||
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
int numCtrs = source.getCounters(CounterEnumType.CHARGE);
|
||||||
@@ -1247,6 +1270,12 @@ public class CountersPutAi extends CountersAi {
|
|||||||
optimalCMC = cmc;
|
optimalCMC = cmc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return numCtrs < optimalCMC;
|
if (numCtrs < optimalCMC) {
|
||||||
|
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,13 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return doTgt(ai, sa, false);
|
if (doTgt(ai, sa, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.checkApiLogic(ai, sa);
|
return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
@@ -85,14 +85,14 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (!type.matches("Any") && !type.matches("All")) {
|
if (!type.matches("Any") && !type.matches("All")) {
|
||||||
final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
|
final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
|
||||||
if (currCounters < 1) {
|
if (currCounters < 1) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkApiLogic(ai, sa);
|
return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
private AiAbilityDecision doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// Filter AI-specific targets if provided
|
||||||
@@ -123,7 +123,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
||||||
if (!depthsList.isEmpty()) {
|
if (!depthsList.isEmpty()) {
|
||||||
sa.getTargets().add(depthsList.getFirst());
|
sa.getTargets().add(depthsList.getFirst());
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!planeswalkerList.isEmpty()) {
|
if (!planeswalkerList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (type.matches("Any")) {
|
} else if (type.matches("Any")) {
|
||||||
// variable amount for Hex Parasite
|
// variable amount for Hex Parasite
|
||||||
@@ -146,7 +146,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
|
|
||||||
if (manaLeft == 0) {
|
if (manaLeft == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
amount = manaLeft;
|
amount = manaLeft;
|
||||||
xPay = true;
|
xPay = true;
|
||||||
@@ -168,7 +168,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (xPay) {
|
if (xPay) {
|
||||||
sa.setXManaCostPaid(ice);
|
sa.setXManaCostPaid(ice);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (xPay) {
|
if (xPay) {
|
||||||
sa.setXManaCostPaid(best.getCurrentLoyalty());
|
sa.setXManaCostPaid(best.getCurrentLoyalty());
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// some rules only for amount = 1
|
// some rules only for amount = 1
|
||||||
@@ -204,7 +204,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!aiM1M1List.isEmpty()) {
|
if (!aiM1M1List.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do as P1P1 part
|
// do as P1P1 part
|
||||||
@@ -213,7 +213,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!aiUndyingList.isEmpty()) {
|
if (!aiUndyingList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiUndyingList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO stun counters with canRemoveCounters check
|
// TODO stun counters with canRemoveCounters check
|
||||||
@@ -224,7 +224,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardPredicates.hasCounter(CounterEnumType.P1P1));
|
CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||||
if (!oppP1P1List.isEmpty()) {
|
if (!oppP1P1List.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback to remove any counter from opponent
|
// fallback to remove any counter from opponent
|
||||||
@@ -236,7 +236,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
for (final CounterType aType : best.getCounters().keySet()) {
|
for (final CounterType aType : best.getCounters().keySet()) {
|
||||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (type.equals("P1P1")) {
|
} else if (type.equals("P1P1")) {
|
||||||
// no special amount for that one yet
|
// no special amount for that one yet
|
||||||
@@ -275,7 +275,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type.equals("TIME")) {
|
} else if (type.equals("TIME")) {
|
||||||
@@ -300,7 +300,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
|
|
||||||
if (manaLeft == 0) {
|
if (manaLeft == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
amount = manaLeft;
|
amount = manaLeft;
|
||||||
xPay = true;
|
xPay = true;
|
||||||
@@ -318,7 +318,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (xPay) {
|
if (xPay) {
|
||||||
sa.setXManaCostPaid(timeCount);
|
sa.setXManaCostPaid(timeCount);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
@@ -327,7 +327,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardCollection adaptCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.ADAPT));
|
CardCollection adaptCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.ADAPT));
|
||||||
if (!adaptCreats.isEmpty()) {
|
if (!adaptCreats.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(adaptCreats));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(adaptCreats));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outlast nice target
|
// Outlast nice target
|
||||||
@@ -338,29 +338,27 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!betterTargets.isEmpty()) {
|
if (!betterTargets.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(outlastCreats));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(outlastCreats));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
boolean canTarget = doTgt(aiPlayer, sa, mandatory);
|
return doTgt(aiPlayer, sa, mandatory);
|
||||||
return canTarget ? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
|
||||||
: new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
|
||||||
}
|
}
|
||||||
return mandatory ? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
return mandatory ? new AiAbilityDecision(100, AiPlayDecision.WillPlay)
|
||||||
: new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
: new AiAbilityDecision(0, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -374,8 +372,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
GameEntity target = (GameEntity) params.get("Target");
|
GameEntity target = (GameEntity) params.get("Target");
|
||||||
CounterType type = (CounterType) params.get("CounterType");
|
CounterType type = (CounterType) params.get("CounterType");
|
||||||
|
|
||||||
if (target instanceof Card) {
|
if (target instanceof Card targetCard) {
|
||||||
Card targetCard = (Card) target;
|
|
||||||
if (targetCard.getController().isOpponentOf(player)) {
|
if (targetCard.getController().isOpponentOf(player)) {
|
||||||
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||||
} else {
|
} else {
|
||||||
@@ -386,8 +383,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||||
}
|
}
|
||||||
} else if (target instanceof Player) {
|
} else if (target instanceof Player targetPlayer) {
|
||||||
Player targetPlayer = (Player) target;
|
|
||||||
if (targetPlayer.isOpponentOf(player)) {
|
if (targetPlayer.isOpponentOf(player)) {
|
||||||
return !type.is(CounterEnumType.POISON) ? max : min;
|
return !type.is(CounterEnumType.POISON) ? max : min;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ import forge.util.collect.FCollectionView;
|
|||||||
public class DestroyAi extends SpellAbilityAi {
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
public AiAbilityDecision chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
if (checkApiLogic(ai, sa)) {
|
return checkApiLogic(ai, sa);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -107,7 +103,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
@@ -115,7 +111,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
CardCollection list;
|
CardCollection list;
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
@@ -129,7 +125,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// Assume there where already enough targets chosen by AI Logic Above
|
// Assume there where already enough targets chosen by AI Logic Above
|
||||||
if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset targets before AI Logic part
|
// reset targets before AI Logic part
|
||||||
@@ -149,13 +145,17 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (maxTargets == 0) {
|
if (maxTargets == 0) {
|
||||||
// can't afford X or otherwise target anything
|
// can't afford X or otherwise target anything
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("TargetingPlayer")) {
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
if (targetingPlayer.getController().chooseTargetsFor(sa)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI doesn't destroy own cards if it isn't defined in AI logic
|
// AI doesn't destroy own cards if it isn't defined in AI logic
|
||||||
@@ -210,7 +210,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
// Try to avoid targeting creatures that are dead on board
|
// Try to avoid targeting creatures that are dead on board
|
||||||
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
@@ -225,7 +225,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -239,7 +239,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
if ("OppDestroyYours".equals(logic)) {
|
if ("OppDestroyYours".equals(logic)) {
|
||||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||||
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||||
@@ -248,7 +248,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
||||||
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
||||||
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -258,14 +258,14 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
//option to hold removal instead only applies for single targeted removal
|
//option to hold removal instead only applies for single targeted removal
|
||||||
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
||||||
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -302,18 +302,18 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||||
|| ai.getLife() <= 5)) {
|
|| ai.getLife() <= 5)) {
|
||||||
// Basic ai logic for Lethal Vapors
|
// Basic ai logic for Lethal Vapors
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if ("Always".equals(logic)) {
|
} else if ("Always".equals(logic)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()
|
if (list.isEmpty()
|
||||||
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||||
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import java.util.Map;
|
|||||||
public class DiscoverAi extends SpellAbilityAi {
|
public class DiscoverAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false; // prevent infinite loop
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,11 +38,7 @@ public class DiscoverAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkApiLogic(ai, sa)) {
|
return checkApiLogic(ai, sa);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -42,43 +42,46 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, forge.game.spellability.SpellAbility)
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player, forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if (!targetAI(ai, sa, false)) {
|
if (!targetAI(ai, sa, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final Player player = sa.getTargets().getFirstTargetedPlayer();
|
final Player player = sa.getTargets().getFirstTargetedPlayer();
|
||||||
if (player != null && player.isOpponentOf(ai)) {
|
if (player != null && player.isOpponentOf(ai)) {
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't tap creatures that may be able to block
|
// Don't tap creatures that may be able to block
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canLoot(ai, sa)) {
|
if (!canLoot(ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCost.isSacrificeSelfCost(sa.getPayCosts())) {
|
if (ComputerUtilCost.isSacrificeSelfCost(sa.getPayCosts())) {
|
||||||
// Canopy lands and other cards that sacrifice themselves to draw cards
|
// Canopy lands and other cards that sacrifice themselves to draw cards
|
||||||
return ai.getCardsIn(ZoneType.Hand).isEmpty()
|
if (ai.getCardsIn(ZoneType.Hand).isEmpty()
|
||||||
|| (sa.getHostCard().isLand() && ai.getLandsInPlay().size() >= 5); // TODO: make this configurable in the AI profile
|
|| (sa.getHostCard().isLand() && ai.getLandsInPlay().size() >= 5)) {
|
||||||
|
// TODO: make this configurable in the AI profile
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("Fight")) {
|
} else if (logic.equals("Fight")) {
|
||||||
return FightAi.canFightAi(ai, sa, 0, 0) ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return FightAi.canFightAi(ai, sa, 0,0);
|
||||||
} else if (logic.equals("Pump")) {
|
} else if (logic.equals("Pump")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
// everything is defined or targeted above, can't do anything there unless a specific logic is set
|
// everything is defined or targeted above, can't do anything there unless a specific logic is set
|
||||||
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get creature lists
|
// Get creature lists
|
||||||
@@ -42,8 +42,10 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
// Filter MustTarget requirements
|
// Filter MustTarget requirements
|
||||||
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
||||||
|
|
||||||
if (humCreatures.isEmpty())
|
//prevent IndexOutOfBoundsException on MOJHOSTO variant
|
||||||
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
if (humCreatures.isEmpty()) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
|
|
||||||
// assumes the triggered card belongs to the ai
|
// assumes the triggered card belongs to the ai
|
||||||
if (sa.hasParam("Defined")) {
|
if (sa.hasParam("Defined")) {
|
||||||
@@ -54,7 +56,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fighter1List.isEmpty()) {
|
if (fighter1List.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
Card fighter1 = fighter1List.get(0);
|
Card fighter1 = fighter1List.get(0);
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
@@ -62,10 +64,11 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
&& !canKill(humanCreature, fighter1, 0)) {
|
&& !canKill(humanCreature, fighter1, 0)) {
|
||||||
// todo: check min/max targets; see if we picked the best matchup
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false; // bail at this point, otherwise the AI will overtarget and waste the activation
|
// bail at this point, otherwise the AI will overtarget and waste the activation
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("TargetsFromDifferentZone")) {
|
if (sa.hasParam("TargetsFromDifferentZone")) {
|
||||||
@@ -77,12 +80,12 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
// todo: check min/max targets; see if we picked the best matchup
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
sa.getTargets().add(aiCreature);
|
sa.getTargets().add(aiCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
for (Card creature1 : humCreatures) {
|
for (Card creature1 : humCreatures) {
|
||||||
for (Card creature2 : humCreatures) {
|
for (Card creature2 : humCreatures) {
|
||||||
@@ -97,11 +100,11 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
// todo: check min/max targets; see if we picked the best matchup
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
sa.getTargets().add(creature1);
|
sa.getTargets().add(creature1);
|
||||||
sa.getTargets().add(creature2);
|
sa.getTargets().add(creature2);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -110,11 +113,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay); // e.g. Hunt the Weak, the AI logic was already checked through canFightAi
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay); // e.g. Hunt the Weak, the AI logic was already checked through canFightAi
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkApiLogic(aiPlayer, sa)) {
|
return checkApiLogic(aiPlayer, sa);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,12 +131,14 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkApiLogic(ai, sa)) {
|
AiAbilityDecision decision = checkApiLogic(ai, sa);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return decision;
|
||||||
}
|
}
|
||||||
|
// if mandatory, we have to play it, so we will try to make a good trade or no trade
|
||||||
|
|
||||||
//try to make a good trade or no trade
|
//try to make a good trade or no trade
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -153,19 +154,19 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
if (canKill(aiCreature, humanCreature, 0)
|
if (canKill(aiCreature, humanCreature, 0)
|
||||||
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
if (!canKill(humanCreature, aiCreature, 0)) {
|
if (!canKill(humanCreature, aiCreature, 0)) {
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sa.getTargets().add(humCreatures.get(0));
|
sa.getTargets().add(humCreatures.get(0));
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,7 +177,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
* @param power bonus to power
|
* @param power bonus to power
|
||||||
* @return true if fight effect should be played, false otherwise
|
* @return true if fight effect should be played, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
public static AiAbilityDecision canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
AbilitySub tgtFight = sa.getSubAbility();
|
AbilitySub tgtFight = sa.getSubAbility();
|
||||||
@@ -208,7 +209,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
||||||
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||||
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
// Evaluate creature pairs
|
// Evaluate creature pairs
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
@@ -238,7 +239,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
tgtFight.resetTargets();
|
tgtFight.resetTargets();
|
||||||
tgtFight.getTargets().add(humanCreature);
|
tgtFight.getTargets().add(humanCreature);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Other cards that use AILogic PowerDmg and a single target
|
// Other cards that use AILogic PowerDmg and a single target
|
||||||
@@ -248,7 +249,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
tgtFight.resetTargets();
|
tgtFight.resetTargets();
|
||||||
tgtFight.getTargets().add(humanCreature);
|
tgtFight.getTargets().add(humanCreature);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -261,12 +262,12 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(aiCreature);
|
sa.getTargets().add(aiCreature);
|
||||||
tgtFight.resetTargets();
|
tgtFight.resetTargets();
|
||||||
tgtFight.getTargets().add(humanCreature);
|
tgtFight.getTargets().add(humanCreature);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.util.List;
|
|||||||
public class GoadAi extends SpellAbilityAi {
|
public class GoadAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ public class GoadAi extends SpellAbilityAi {
|
|||||||
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
if (list.isEmpty())
|
if (list.isEmpty())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
|
||||||
if (game.getPlayers().size() > 2) {
|
if (game.getPlayers().size() > 2) {
|
||||||
// use this part only in multiplayer
|
// use this part only in multiplayer
|
||||||
@@ -50,7 +50,7 @@ public class GoadAi extends SpellAbilityAi {
|
|||||||
if (!betterList.isEmpty()) {
|
if (!betterList.isEmpty()) {
|
||||||
list = betterList;
|
list = betterList;
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// single Player, goaded creature would attack ai
|
// single Player, goaded creature would attack ai
|
||||||
@@ -71,37 +71,40 @@ public class GoadAi extends SpellAbilityAi {
|
|||||||
if (!betterList.isEmpty()) {
|
if (!betterList.isEmpty()) {
|
||||||
list = betterList;
|
list = betterList;
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI does not find a good creature to goad.
|
// AI does not find a good creature to goad.
|
||||||
// because if it would goad a creature it would attack AI.
|
// because if it would goad a creature it would attack AI.
|
||||||
// AI might not have enough information to block it
|
// AI might not have enough information to block it
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (checkApiLogic(ai, sa)) {
|
AiAbilityDecision decision = checkApiLogic(ai, sa);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
// mandatory play, so we have to play it
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
||||||
for (Player opp : ai.getOpponents()) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.canTarget(ai)) {
|
if (sa.canTarget(ai)) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<Card> list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
@@ -110,7 +113,7 @@ public class GoadAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(list));
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(30, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
@@ -148,12 +148,12 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
// Ugin AI: always use ultimate
|
// Ugin AI: always use ultimate
|
||||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||||
// TODO: somehow link with DamageDealAi for cases where +1 = win
|
// TODO: somehow link with DamageDealAi for cases where +1 = win
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't use it if no life to gain
|
// don't use it if no life to gain
|
||||||
if (!activateForCost && lifeAmount <= 0) {
|
if (!activateForCost && lifeAmount <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// don't play if the conditions aren't met, unless it would trigger a
|
// don't play if the conditions aren't met, unless it would trigger a
|
||||||
// beneficial sub-condition
|
// beneficial sub-condition
|
||||||
@@ -161,47 +161,52 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
final AbilitySub abSub = sa.getSubAbility();
|
final AbilitySub abSub = sa.getSubAbility();
|
||||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||||
if (!abSub.getConditions().areMet(abSub)) {
|
if (!abSub.getConditions().areMet(abSub)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.ConditionsNotMet);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.ConditionsNotMet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activateForCost && !ai.canGainLife()) {
|
if (!activateForCost && !ai.canGainLife()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!target(ai, sa, true)) {
|
if (!target(ai, sa, true)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSorcerySpeed(sa, ai)
|
if (isSorcerySpeed(sa, ai)
|
||||||
|| sa.getSubAbility() != null || playReusable(ai, sa)) {
|
|| sa.getSubAbility() != null || playReusable(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||||
return true; // sac costs should be performed at Instant speed when able
|
// sac costs should be performed at Instant speed when able
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save instant-speed life-gain unless it is really worth it
|
// Save instant-speed life-gain unless it is really worth it
|
||||||
final float value = 0.9f * lifeAmount / life;
|
final float value = 0.9f * lifeAmount / life;
|
||||||
if (value < 0.2f) {
|
if (value < 0.2f) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
if (MyRandom.getRandom().nextFloat() < value) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return MyRandom.getRandom().nextFloat() < value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
@@ -102,7 +102,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!doTgt(ai, sa, false)) {
|
if (!doTgt(ai, sa, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,15 +115,15 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||||
@@ -132,7 +132,7 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
.filter(PlayerPredicates.isOpponentOf(ai).and(PlayerPredicates.lifeLessOrEqualTo(amount)));
|
.filter(PlayerPredicates.isOpponentOf(ai).and(PlayerPredicates.lifeLessOrEqualTo(amount)));
|
||||||
// killing opponents asap
|
// killing opponents asap
|
||||||
if (!filteredPlayer.isEmpty()) {
|
if (!filteredPlayer.isEmpty()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sacrificing a creature in response to something dangerous is generally good in any phase
|
// Sacrificing a creature in response to something dangerous is generally good in any phase
|
||||||
@@ -144,20 +144,20 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
// Don't use loselife before main 2 if possible
|
// Don't use loselife before main 2 if possible
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
&& !ComputerUtil.castSpellInMain1(ai, sa) && !aiLogic.contains("AnyPhase") && !isSacCost) {
|
&& !ComputerUtil.castSpellInMain1(ai, sa) && !aiLogic.contains("AnyPhase") && !isSacCost) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForMain2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't tap creatures that may be able to block
|
// Don't tap creatures that may be able to block
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || playReusable(ai, sa)
|
if (isSorcerySpeed(sa, ai) || sa.hasParam("ActivationPhases") || playReusable(ai, sa)
|
||||||
|| ComputerUtil.activateForCost(sa, ai)) {
|
|| ComputerUtil.activateForCost(sa, ai)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -87,18 +87,22 @@ public class ManaAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
return true; // handled elsewhere, does not meet the standard requirements
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay); // handled elsewhere, does not meet the standard requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check if it would be worth it to keep mana open for opponents turn anyway
|
// TODO check if it would be worth it to keep mana open for opponents turn anyway
|
||||||
if (ComputerUtil.activateForCost(sa, ai)) {
|
if (ComputerUtil.activateForCost(sa, ai)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
if (sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
||||||
&& sa.getSubAbility() == null && (improvesPosition(ai, sa) || ComputerUtil.playImmediately(ai, sa));
|
&& sa.getSubAbility() == null && (improvesPosition(ai, sa) || ComputerUtil.playImmediately(ai, sa))) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,7 +280,6 @@ public class ManaAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision canPlayAI(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
boolean result = checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,11 +81,11 @@ public abstract class ManifestBaseAi extends SpellAbilityAi {
|
|||||||
abstract protected boolean shouldApply(final Card card, final Player ai, final SpellAbility sa);
|
abstract protected boolean shouldApply(final Card card, final Player ai, final SpellAbility sa);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Choices") || sa.hasParam("ChoiceZone")) {
|
if (sa.hasParam("Choices") || sa.hasParam("ChoiceZone")) {
|
||||||
@@ -98,36 +98,42 @@ public abstract class ManifestBaseAi extends SpellAbilityAi {
|
|||||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), ai, host, sa);
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), ai, host, sa);
|
||||||
}
|
}
|
||||||
if (choices.isEmpty()) {
|
if (choices.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlaySa);
|
||||||
}
|
}
|
||||||
} else if ("TopOfLibrary".equals(sa.getParamOrDefault("Defined", "TopOfLibrary"))) {
|
} else if ("TopOfLibrary".equals(sa.getParamOrDefault("Defined", "TopOfLibrary"))) {
|
||||||
// Library is empty, no Manifest
|
// Library is empty, no Manifest
|
||||||
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
|
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
|
||||||
if (library.isEmpty())
|
if (library.isEmpty())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
// try not to mill himself with Manifest
|
// try not to mill himself with Manifest
|
||||||
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
|
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldApply(library.getFirst(), ai, sa)) {
|
if (!shouldApply(library.getFirst(), ai, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Probably should be a little more discerning on playing during OPPs turn
|
// TODO Probably should be a little more discerning on playing during OPPs turn
|
||||||
if (playReusable(ai, sa)) {
|
if (playReusable(ai, sa)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
// Add blockers?
|
// Add blockers?
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.AddBoardPresence);
|
||||||
}
|
}
|
||||||
if (sa.isAbility()) {
|
if (sa.isAbility()) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MyRandom.getRandom().nextFloat() < .8;
|
if ( MyRandom.getRandom().nextFloat() < .8) {
|
||||||
|
// 80% chance to play a Manifest spell
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// 20% chance to not play a Manifest spell
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -11,19 +11,24 @@ import forge.game.zone.ZoneType;
|
|||||||
|
|
||||||
public class MeldAi extends SpellAbilityAi {
|
public class MeldAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
String primaryMeld = sa.getParam("Primary");
|
String primaryMeld = sa.getParam("Primary");
|
||||||
String secondaryMeld = sa.getParam("Secondary");
|
String secondaryMeld = sa.getParam("Secondary");
|
||||||
|
|
||||||
CardCollectionView cardsOTB = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView cardsOTB = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||||
if (cardsOTB.isEmpty()) {
|
if (cardsOTB.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasPrimaryMeld = cardsOTB.anyMatch(CardPredicates.nameEquals(primaryMeld).and(CardPredicates.isOwner(aiPlayer)));
|
boolean hasPrimaryMeld = cardsOTB.anyMatch(CardPredicates.nameEquals(primaryMeld).and(CardPredicates.isOwner(aiPlayer)));
|
||||||
boolean hasSecondaryMeld = cardsOTB.anyMatch(CardPredicates.nameEquals(secondaryMeld).and(CardPredicates.isOwner(aiPlayer)));
|
boolean hasSecondaryMeld = cardsOTB.anyMatch(CardPredicates.nameEquals(secondaryMeld).and(CardPredicates.isOwner(aiPlayer)));
|
||||||
|
if (hasPrimaryMeld && hasSecondaryMeld && sa.getHostCard().getName().equals(primaryMeld)) {
|
||||||
return hasPrimaryMeld && hasSecondaryMeld && sa.getHostCard().getName().equals(primaryMeld);
|
// If the primary meld card is on the battlefield and both meld cards are owned by the AI, play the ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// If the secondary meld card is on the battlefield and it is the one being activated, play the ability
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
/*
|
/*
|
||||||
* TODO:
|
* TODO:
|
||||||
* - logic in targetAI looks dodgy
|
* - logic in targetAI looks dodgy
|
||||||
@@ -70,16 +70,17 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
* effect due to possibility of "lose abilities" effect)
|
* effect due to possibility of "lose abilities" effect)
|
||||||
*/
|
*/
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false; // prevents mill 0 infinite loop?
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (("You".equals(sa.getParam("Defined")) || "Player".equals(sa.getParam("Defined")))
|
if (("You".equals(sa.getParam("Defined")) || "Player".equals(sa.getParam("Defined")))
|
||||||
&& ai.getCardsIn(ZoneType.Library).size() < 10) {
|
&& ai.getCardsIn(ZoneType.Library).size() < 10) {
|
||||||
return false; // prevent self and each player mill when library is small
|
// prevent self and each player mill when library is small
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting() && !targetAI(ai, sa, false)) {
|
if (sa.usesTargeting() && !targetAI(ai, sa, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("NumCards") && (sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
|
if (sa.hasParam("NumCards") && (sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
|
||||||
@@ -87,9 +88,11 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
final int cardsToDiscard = getNumToDiscard(ai, sa);
|
||||||
sa.setXManaCostPaid(cardsToDiscard);
|
sa.setXManaCostPaid(cardsToDiscard);
|
||||||
return cardsToDiscard > 0;
|
if (cardsToDiscard <= 0) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
|||||||
@@ -43,27 +43,27 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
* here
|
* here
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
// check on legendary
|
// check on legendary
|
||||||
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||||
// TODO check the risk we'd lose the effect with bad timing
|
// TODO check the risk we'd lose the effect with bad timing
|
||||||
|
// TODO Technically we're not checking if same card in play is also legendary, but this is a good enough approximation
|
||||||
if (!source.hasSVar("AILegendaryException")) {
|
if (!source.hasSVar("AILegendaryException")) {
|
||||||
// AiPlayDecision.WouldDestroyLegend
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
String specialRule = source.getSVar("AILegendaryException");
|
String specialRule = source.getSVar("AILegendaryException");
|
||||||
if ("TwoCopiesAllowed".equals(specialRule)) {
|
if ("TwoCopiesAllowed".equals(specialRule)) {
|
||||||
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
|
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
|
||||||
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(source.getName())) > 1) {
|
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(source.getName())) > 1) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
}
|
}
|
||||||
} else if ("AlwaysAllowed".equals(specialRule)) {
|
} else if ("AlwaysAllowed".equals(specialRule)) {
|
||||||
// Nothing to do here, check for Legendary is disabled
|
// Nothing to do here, check for Legendary is disabled
|
||||||
} else {
|
} else {
|
||||||
// Unknown hint, assume two copies not allowed
|
// Unknown hint, assume two copies not allowed
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,8 +71,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
if (source.getType().hasSupertype(Supertype.World)) {
|
if (source.getType().hasSupertype(Supertype.World)) {
|
||||||
CardCollection list = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "World");
|
CardCollection list = CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "World");
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
// AiPlayDecision.WouldDestroyWorldEnchantment
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyWorldEnchantment);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +92,8 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// AiPlayDecision.CantAffordX
|
|
||||||
if (xPay <= 0) {
|
if (xPay <= 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
}
|
}
|
||||||
@@ -103,8 +101,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
// if mana is zero, but card mana cost does have X, then something is wrong
|
// if mana is zero, but card mana cost does have X, then something is wrong
|
||||||
ManaCost cardCost = source.getManaCost();
|
ManaCost cardCost = source.getManaCost();
|
||||||
if (cardCost != null && cardCost.countX() > 0) {
|
if (cardCost != null && cardCost.countX() > 0) {
|
||||||
// AiPlayDecision.CantPlayAi
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,10 +144,10 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (oppCards.size() > 3 && oppCards.size() >= aiCards.size() * 2) {
|
if (oppCards.size() > 3 && oppCards.size() >= aiCards.size() * 2) {
|
||||||
sa.setXManaCostPaid(manaValue);
|
sa.setXManaCostPaid(manaValue);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (KeywordInterface ki : source.getKeywords(Keyword.MULTIKICKER)) {
|
for (KeywordInterface ki : source.getKeywords(Keyword.MULTIKICKER)) {
|
||||||
@@ -177,7 +174,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
sa.clearOptionalKeywordAmount();
|
sa.clearOptionalKeywordAmount();
|
||||||
// Bail if the card cost was {0} and no multikicker was paid (e.g. Everflowing Chalice).
|
// Bail if the card cost was {0} and no multikicker was paid (e.g. Everflowing Chalice).
|
||||||
// TODO: update this if there's ever a card where it makes sense to play it for {0} with no multikicker
|
// TODO: update this if there's ever a card where it makes sense to play it for {0} with no multikicker
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,8 +210,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
emptyAbility.setActivatingPlayer(ai);
|
emptyAbility.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (!ComputerUtilCost.canPayCost(emptyAbility, ai, true)) {
|
if (!ComputerUtilCost.canPayCost(emptyAbility, ai, true)) {
|
||||||
// AiPlayDecision.AnotherTime
|
return new AiAbilityDecision(0, AiPlayDecision.CantAfford);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,10 +306,12 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !dontCast;
|
if (dontCast) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -333,8 +331,13 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
boolean result = checkApiLogic(ai, sa);
|
AiAbilityDecision decision = checkApiLogic(ai, sa);
|
||||||
return (result || mandatory) ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
if (decision.willingToPlay()) {
|
||||||
}
|
return decision;
|
||||||
|
} else if (mandatory) {
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
} else {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,9 +195,10 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
if (!super.checkApiLogic(ai, sa)) {
|
AiAbilityDecision decision = super.checkApiLogic(ai, sa);
|
||||||
return false;
|
if (!decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
@@ -220,16 +221,15 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
// AiPlayDecision.WouldBecomeZeroToughnessCreature
|
||||||
if (card.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|
if (card.hasStartOfKeyword("etbCounter") || mana.countX() != 0
|
||||||
|| card.hasETBTrigger(false) || card.hasETBReplacement() || card.hasSVar("NoZeroToughnessAI")) {
|
|| card.hasETBTrigger(false) || card.hasETBReplacement() || card.hasSVar("NoZeroToughnessAI")) {
|
||||||
return true;
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card copy = CardCopyService.getLKICopy(card);
|
final Card copy = CardCopyService.getLKICopy(card);
|
||||||
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
ComputerUtilCard.applyStaticContPT(game, copy, null);
|
||||||
if (copy.getNetToughness() > 0) {
|
if (copy.getNetToughness() > 0) {
|
||||||
return true;
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WouldBecomeZeroToughnessCreature);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAbilityDecision;
|
||||||
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
@@ -21,9 +23,11 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
* here
|
* here
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (!super.checkApiLogic(ai, sa))
|
AiAbilityDecision decision = super.checkApiLogic(ai, sa);
|
||||||
return false;
|
if (!decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
@@ -41,9 +45,10 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
|
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
|
||||||
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
|
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
|
||||||
}
|
}
|
||||||
// AiPlayDecision.AnotherTime
|
if (targets.isEmpty()) {
|
||||||
return !targets.isEmpty();
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return decision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
|
|||||||
public class PlayAi extends SpellAbilityAi {
|
public class PlayAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -34,11 +34,11 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
|
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
|
||||||
// while the trigger is on stack)
|
// while the trigger is on stack)
|
||||||
if (!game.getStack().isEmpty() && !"ReplaySpell".equals(logic)) {
|
if (!game.getStack().isEmpty() && !"ReplaySpell".equals(logic)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false; // prevent infinite loop
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) {
|
if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) {
|
||||||
@@ -48,38 +48,46 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
int numLandsForJhoira = aic.getIntProperty(AiProps.MOJHOSTO_NUM_LANDS_TO_ACTIVATE_JHOIRA);
|
int numLandsForJhoira = aic.getIntProperty(AiProps.MOJHOSTO_NUM_LANDS_TO_ACTIVATE_JHOIRA);
|
||||||
int chanceToActivateInst = 100 - aic.getIntProperty(AiProps.MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT);
|
int chanceToActivateInst = 100 - aic.getIntProperty(AiProps.MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT);
|
||||||
if (ai.getLandsInPlay().size() < numLandsForJhoira) {
|
if (ai.getLandsInPlay().size() < numLandsForJhoira) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
// Don't spam activate the Instant copying ability all the time to give the AI a chance to use other abilities
|
// Don't spam activate the Instant copying ability all the time to give the AI a chance to use other abilities
|
||||||
// Can probably be improved, but as random as MoJhoSto already is, probably not a huge deal for now
|
// Can probably be improved, but as random as MoJhoSto already is, probably not a huge deal for now
|
||||||
if ("Instant".equals(sa.getParam("AnySupportedCard")) && MyRandom.percentTrue(chanceToActivateInst)) {
|
if ("Instant".equals(sa.getParam("AnySupportedCard")) && MyRandom.percentTrue(chanceToActivateInst)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Card> cards = getPlayableCards(sa, ai);
|
List<Card> cards = getPlayableCards(sa, ai);
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("ReplaySpell".equals(logic)) {
|
if ("ReplaySpell".equals(logic)) {
|
||||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"), false);
|
if (ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"), false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
} else if (logic.startsWith("NeedsChosenCard")) {
|
} else if (logic.startsWith("NeedsChosenCard")) {
|
||||||
int minCMC = 0;
|
int minCMC = 0;
|
||||||
if (sa.getPayCosts().getCostMana() != null) {
|
if (sa.getPayCosts().getCostMana() != null) {
|
||||||
minCMC = sa.getPayCosts().getTotalMana().getCMC();
|
minCMC = sa.getPayCosts().getTotalMana().getCMC();
|
||||||
}
|
}
|
||||||
cards = CardLists.filter(cards, CardPredicates.greaterCMC(minCMC));
|
cards = CardLists.filter(cards, CardPredicates.greaterCMC(minCMC));
|
||||||
return chooseSingleCard(ai, sa, cards, sa.hasParam("Optional"), null, null) != null;
|
if (chooseSingleCard(ai, sa, cards, sa.hasParam("Optional"), null, null) != null) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
} else if ("WithTotalCMC".equals(logic)) {
|
} else if ("WithTotalCMC".equals(logic)) {
|
||||||
// Try to play only when there are more than three playable cards.
|
// Try to play only when there are more than three playable cards.
|
||||||
if (cards.size() < 3)
|
if (cards.size() < 3)
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
if (sa.costHasManaX()) {
|
if (sa.costHasManaX()) {
|
||||||
int amount = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int amount = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
if (amount < ComputerUtilCard.getBestAI(cards).getCMC())
|
if (amount < ComputerUtilCard.getBestAI(cards).getCMC())
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
int totalCMC = 0;
|
int totalCMC = 0;
|
||||||
for (Card c : cards) {
|
for (Card c : cards) {
|
||||||
totalCMC += c.getCMC();
|
totalCMC += c.getCMC();
|
||||||
@@ -97,10 +105,14 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
Card rem = source.getExiledCards().getFirst();
|
Card rem = source.getExiledCards().getFirst();
|
||||||
CardTypeView t = rem.getState(CardStateName.Original).getType();
|
CardTypeView t = rem.getState(CardStateName.Original).getType();
|
||||||
|
|
||||||
return t.isPermanent() && !t.isLand();
|
if (t.isPermanent() && !t.isLand()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,8 +138,7 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean result = checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|||||||
@@ -36,18 +36,22 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
// Don't tap creatures that may be able to block
|
// Don't tap creatures that may be able to block
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return tgtPlayer(ai, sa, true);
|
if (tgtPlayer(ai, sa, true)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -157,17 +157,21 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return protectTgtAI(ai, sa, false);
|
return protectTgtAI(ai, sa, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
if (cards.size() == 0) {
|
if (cards.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
} else if (cards.size() == 1) {
|
} else if (cards.size() == 1) {
|
||||||
// Affecting single card
|
// Affecting single card
|
||||||
return getProtectCreatures(ai, sa).contains(cards.get(0));
|
if (getProtectCreatures(ai, sa).contains(cards.get(0))) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* when this happens we need to expand AI to consider if its ok
|
* when this happens we need to expand AI to consider if its ok
|
||||||
@@ -175,14 +179,14 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
* control Card and Pump is a Curse, than maybe use?
|
* control Card and Pump is a Curse, than maybe use?
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private AiAbilityDecision protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& game.getStack().isEmpty()) {
|
&& game.getStack().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -216,7 +220,12 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return mandatory && protectMandatoryTarget(ai, sa);
|
if (mandatory && protectMandatoryTarget(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
} else {
|
||||||
|
sa.resetTargets();
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
@@ -226,11 +235,13 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().size() < tgt.getMinTargets(source, sa)) || sa.getTargets().size() == 0) {
|
if ((sa.getTargets().size() < tgt.getMinTargets(source, sa)) || sa.getTargets().size() == 0) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return protectMandatoryTarget(ai, sa);
|
if (protectMandatoryTarget(ai, sa)) {
|
||||||
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
@@ -242,7 +253,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
list.remove(t);
|
list.remove(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} // protectTgtAI()
|
} // protectTgtAI()
|
||||||
|
|
||||||
private static boolean protectMandatoryTarget(final Player ai, final SpellAbility sa) {
|
private static boolean protectMandatoryTarget(final Player ai, final SpellAbility sa) {
|
||||||
@@ -311,11 +322,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (protectTgtAI(ai, sa, mandatory)) {
|
return protectTgtAI(ai, sa, mandatory);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
@@ -324,11 +331,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public AiAbilityDecision chkAIDrawback(SpellAbility sa, Player ai) {
|
public AiAbilityDecision chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (protectTgtAI(ai, sa, false)) {
|
return protectTgtAI(ai, sa, false);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
} else {
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
} else if ("Aristocrat".equals(aiLogic)) {
|
} else if ("Aristocrat".equals(aiLogic)) {
|
||||||
return SpecialAiLogic.doAristocratLogic(ai, sa);
|
return SpecialAiLogic.doAristocratLogic(ai, sa);
|
||||||
} else if (aiLogic.startsWith("AristocratCounters")) {
|
} else if (aiLogic.startsWith("AristocratCounters")) {
|
||||||
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa).willingToPlay();
|
||||||
} else if (aiLogic.equals("SwitchPT")) {
|
} else if (aiLogic.equals("SwitchPT")) {
|
||||||
// Some more AI would be even better, but this is a good start to prevent spamming
|
// Some more AI would be even better, but this is a good start to prevent spamming
|
||||||
if (sa.isActivatedAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
|
if (sa.isActivatedAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
|
||||||
@@ -114,7 +114,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
@@ -131,14 +131,15 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if ("Pummeler".equals(aiLogic)) {
|
if ("Pummeler".equals(aiLogic)) {
|
||||||
return SpecialCardAi.ElectrostaticPummeler.consider(ai, sa);
|
return SpecialCardAi.ElectrostaticPummeler.consider(ai, sa);
|
||||||
} else if (aiLogic.startsWith("AristocratCounters")) {
|
} else if (aiLogic.startsWith("AristocratCounters")) {
|
||||||
return true; // the preconditions to this are already tested in checkAiLogic
|
// the preconditions to this are already tested in checkAiLogic
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if ("GideonBlackblade".equals(aiLogic)) {
|
} else if ("GideonBlackblade".equals(aiLogic)) {
|
||||||
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
||||||
} else if ("MoveCounter".equals(aiLogic)) {
|
} else if ("MoveCounter".equals(aiLogic)) {
|
||||||
final SpellAbility moveSA = sa.findSubAbilityByType(ApiType.MoveCounter);
|
final SpellAbility moveSA = sa.findSubAbilityByType(ApiType.MoveCounter);
|
||||||
|
|
||||||
if (moveSA == null) {
|
if (moveSA == null) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String counterType = moveSA.getParam("CounterType");
|
final String counterType = moveSA.getParam("CounterType");
|
||||||
@@ -153,7 +154,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (cType != null) {
|
if (cType != null) {
|
||||||
attr = CardLists.filter(attr, CardPredicates.hasCounter(cType));
|
attr = CardLists.filter(attr, CardPredicates.hasCounter(cType));
|
||||||
if (attr.isEmpty()) {
|
if (attr.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0,AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
CardCollection best = CardLists.filter(attr, card -> {
|
CardCollection best = CardLists.filter(attr, card -> {
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
@@ -187,7 +188,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
|
|
||||||
final Card card = ComputerUtilCard.getBestCreatureAI(best);
|
final Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||||
sa.getTargets().add(card);
|
sa.getTargets().add(card);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final boolean sameCtrl = moveSA.getTargetRestrictions().isSameController();
|
final boolean sameCtrl = moveSA.getTargetRestrictions().isSameController();
|
||||||
@@ -196,7 +197,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (cType != null) {
|
if (cType != null) {
|
||||||
list = CardLists.filter(list, CardPredicates.hasCounter(cType));
|
list = CardLists.filter(list, CardPredicates.hasCounter(cType));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
List<Card> oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
List<Card> oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||||
if (!oppList.isEmpty() && !sameCtrl) {
|
if (!oppList.isEmpty() && !sameCtrl) {
|
||||||
@@ -232,7 +233,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
|
|
||||||
final Card card = ComputerUtilCard.getBestCreatureAI(best);
|
final Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||||
sa.getTargets().add(card);
|
sa.getTargets().add(card);
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,12 +245,12 @@ public class PumpAi extends PumpAiBase {
|
|||||||
int numRedMana = ComputerUtilMana.determineLeftoverMana(new SpellAbility.EmptySa(source), ai, "R", false);
|
int numRedMana = ComputerUtilMana.determineLeftoverMana(new SpellAbility.EmptySa(source), ai, "R", false);
|
||||||
int currentPower = source.getNetPower();
|
int currentPower = source.getNetPower();
|
||||||
if (currentPower < 20 && currentPower + numRedMana >= 20) {
|
if (currentPower < 20 && currentPower + numRedMana >= 20) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game.getStack().isEmpty() && !sa.isCurse() && !isFight) {
|
if (!game.getStack().isEmpty() && !sa.isCurse() && !isFight) {
|
||||||
@@ -261,7 +262,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
final int activations = sa.getActivationsThisTurn();
|
final int activations = sa.getActivationsThisTurn();
|
||||||
// don't risk sacrificing a creature just to pump it
|
// don't risk sacrificing a creature just to pump it
|
||||||
if (activations >= sacActivations - 1) {
|
if (activations >= sacActivations - 1) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.ConditionsNotMet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +305,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((numDefense.contains("X") && defense == 0) || (numAttack.contains("X") && attack == 0 && !isBerserk)) {
|
if ((numDefense.contains("X") && defense == 0) || (numAttack.contains("X") && attack == 0 && !isBerserk)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Untargeted
|
//Untargeted
|
||||||
@@ -312,47 +313,51 @@ public class PumpAi extends PumpAiBase {
|
|||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
// when this happens we need to expand AI to consider if its ok for everything?
|
// when this happens we need to expand AI to consider if its ok for everything?
|
||||||
for (final Card card : cards) {
|
for (final Card card : cards) {
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
if (!card.getController().isOpponentOf(ai)) {
|
if (!card.getController().isOpponentOf(ai)) {
|
||||||
return false;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
if (!containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
if (!card.getController().isOpponentOf(ai)) {
|
if (!card.getController().isOpponentOf(ai)) {
|
||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||||
if (game.getPhaseHandler().is(PhaseType.MAIN1) && isSorcerySpeed(sa, ai) ||
|
if (game.getPhaseHandler().is(PhaseType.MAIN1) && isSorcerySpeed(sa, ai) ||
|
||||||
game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) ||
|
game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai) ||
|
||||||
game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
||||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||||
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped);
|
if (ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, pumped)) {
|
||||||
|
// If the AI can attack with the pumped creature, then it is worth playing
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (grantsUsefulExtraBlockOpts(ai, sa, card, keywords)) {
|
} else if (grantsUsefulExtraBlockOpts(ai, sa, card, keywords)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
//Targeted
|
//Targeted
|
||||||
if (!pumpTgtAI(ai, sa, defense, attack, false, false)) {
|
if (!pumpTgtAI(ai, sa, defense, attack, false, false)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} // pumpPlayAI()
|
} // pumpPlayAI()
|
||||||
|
|
||||||
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory,
|
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory,
|
||||||
@@ -462,7 +467,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
return FightAi.canFightAi(ai, sa, attack, defense);
|
return FightAi.canFightAi(ai, sa, attack, defense).willingToPlay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import java.util.List;
|
|||||||
public class RegenerateAi extends SpellAbilityAi {
|
public class RegenerateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
@@ -54,7 +54,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
List<Card> targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
if (targetables.isEmpty()) {
|
if (targetables.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
@@ -87,12 +87,12 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.getTargets().isEmpty()) {
|
if (sa.getTargets().isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
// when regenerating more than one is possible try for slightly more value
|
// when regenerating more than one is possible try for slightly more value
|
||||||
int numToSave = Math.min(2, list.size());
|
int numToSave = Math.min(2, list.size());
|
||||||
@@ -116,7 +116,11 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
chance = saved >= numToSave;
|
chance = saved >= numToSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
if (chance) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,20 +15,27 @@ import forge.util.MyRandom;
|
|||||||
public class RevealAi extends RevealAiBase {
|
public class RevealAi extends RevealAiBase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
// we can reuse this function here...
|
// we can reuse this function here...
|
||||||
final boolean bFlag = revealHandTargetAI(ai, sa, false);
|
final boolean bFlag = revealHandTargetAI(ai, sa, false);
|
||||||
|
|
||||||
if (!bFlag) {
|
if (!bFlag) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
||||||
|
|
||||||
|
// Are we checking for runaway activations?
|
||||||
if (playReusable(ai, sa)) {
|
if (playReusable(ai, sa)) {
|
||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
}
|
}
|
||||||
return randomReturn;
|
|
||||||
|
if (randomReturn) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ public class RevealHandAi extends RevealAiBase {
|
|||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final boolean bFlag = revealHandTargetAI(ai, sa, false);
|
final boolean bFlag = revealHandTargetAI(ai, sa, false);
|
||||||
|
|
||||||
if (!bFlag) {
|
if (!bFlag) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.667, sa.getActivationsThisTurn() + 1);
|
||||||
@@ -25,7 +25,11 @@ public class RevealHandAi extends RevealAiBase {
|
|||||||
randomReturn = true;
|
randomReturn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
if (randomReturn) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -147,10 +147,10 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
// does Scry make sense with no Library cards?
|
// does Scry make sense with no Library cards?
|
||||||
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
||||||
@@ -181,12 +181,15 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
if ("X".equals(sa.getParam("ScryNum")) && sa.getSVar("X").equals("Count$xPaid")) {
|
if ("X".equals(sa.getParam("ScryNum")) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
if (xPay == 0) {
|
if (xPay == 0) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantAffordX);
|
||||||
}
|
}
|
||||||
sa.getRootAbility().setXManaCostPaid(xPay);
|
sa.getRootAbility().setXManaCostPaid(xPay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
if (randomReturn) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -17,19 +17,19 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class SetStateAi extends SpellAbilityAi {
|
public class SetStateAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player aiPlayer, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player aiPlayer, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String mode = sa.getParam("Mode");
|
final String mode = sa.getParam("Mode");
|
||||||
|
|
||||||
// turning face is most likely okay
|
// turning face is most likely okay
|
||||||
// TODO only do this at beneficial moment (e.g. surprise during combat or morph trigger), might want to reserve mana to protect them from easy removal
|
// TODO only do this at beneficial moment (e.g. surprise during combat or morph trigger), might want to reserve mana to protect them from easy removal
|
||||||
if ("TurnFaceUp".equals(mode) || "TurnFaceDown".equals(mode)) {
|
if ("TurnFaceUp".equals(mode) || "TurnFaceDown".equals(mode)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent transform into legendary creature if copy already exists
|
// Prevent transform into legendary creature if copy already exists
|
||||||
if (!isSafeToTransformIntoLegendary(aiPlayer, source)) {
|
if (!isSafeToTransformIntoLegendary(aiPlayer, source)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getSVar("X").equals("Count$xPaid")) {
|
if (sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
@@ -38,9 +38,9 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Transform".equals(mode) || "Flip".equals(mode)) {
|
if ("Transform".equals(mode) || "Flip".equals(mode)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -84,10 +84,10 @@ public class SurveilAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
// Makes no sense to do Surveil when there's nothing in the library
|
// Makes no sense to do Surveil when there's nothing in the library
|
||||||
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only Surveil for life when at decent amount of life remaining
|
// Only Surveil for life when at decent amount of life remaining
|
||||||
@@ -95,10 +95,11 @@ public class SurveilAi extends SpellAbilityAi {
|
|||||||
if (cost != null && cost.hasSpecificCostType(CostPayLife.class)) {
|
if (cost != null && cost.hasSpecificCostType(CostPayLife.class)) {
|
||||||
final int maxLife = ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SURVEIL_LIFEPERC_AFTER_PAYING_LIFE);
|
final int maxLife = ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SURVEIL_LIFEPERC_AFTER_PAYING_LIFE);
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, sa.getHostCard(), ai.getStartingLife() * maxLife / 100, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, sa.getHostCard(), ai.getStartingLife() * maxLife / 100, sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO If EOT and I'm the next turn, the percent should probably be higher
|
||||||
double chance = .4; // 40 percent chance for instant speed
|
double chance = .4; // 40 percent chance for instant speed
|
||||||
if (isSorcerySpeed(sa, ai)) {
|
if (isSorcerySpeed(sa, ai)) {
|
||||||
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||||
@@ -111,9 +112,10 @@ public class SurveilAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (randomReturn) {
|
if (randomReturn) {
|
||||||
AiCardMemory.rememberCard(ai, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
AiCardMemory.rememberCard(ai, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
/*
|
/*
|
||||||
* readParameters() is called in checkPhaseRestrictions
|
* readParameters() is called in checkPhaseRestrictions
|
||||||
*/
|
*/
|
||||||
@@ -142,14 +142,14 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false; // prevent infinite tokens?
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
Card actualToken = spawnToken(ai, sa);
|
Card actualToken = spawnToken(ai, sa);
|
||||||
|
|
||||||
// Don't kill AIs Legendary tokens
|
// Don't kill AIs Legendary tokens
|
||||||
if (actualToken.getType().isLegendary() && ai.isCardInPlay(actualToken.getName())) {
|
if (actualToken.getType().isLegendary() && ai.isCardInPlay(actualToken.getName())) {
|
||||||
// TODO Check if Token is useless due to an aura or counters?
|
// TODO Check if Token is useless due to an aura or counters?
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.WouldDestroyLegend);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -157,14 +157,18 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
if (actualToken.getType().hasSubtype("Role")) {
|
if (actualToken.getType().hasSubtype("Role")) {
|
||||||
return tgtRoleAura(ai, sa, actualToken, false);
|
if (tgtRoleAura(ai, sa, actualToken, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt.canOnlyTgtOpponent() || "Opponent".equals(sa.getParam("AITgts"))) {
|
if (tgt.canOnlyTgtOpponent() || "Opponent".equals(sa.getParam("AITgts"))) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sa.canTarget(ai)) {
|
if (sa.canTarget(ai)) {
|
||||||
@@ -183,7 +187,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,7 +205,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isPwAbility() && alwaysFromPW) {
|
if (sa.isPwAbility() && alwaysFromPW) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
} else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)
|
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)
|
||||||
&& game.getCombat() != null
|
&& game.getCombat() != null
|
||||||
@@ -210,14 +214,18 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
&& actualToken.isCreature()) {
|
&& actualToken.isCreature()) {
|
||||||
for (Card attacker : game.getCombat().getAttackers()) {
|
for (Card attacker : game.getCombat().getAttackers()) {
|
||||||
if (CombatUtil.canBlock(attacker, actualToken)) {
|
if (CombatUtil.canBlock(attacker, actualToken)) {
|
||||||
return true;
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if the token can't block, then what's the point?
|
// if the token can't block, then what's the point?
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.DoesntImpactCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MyRandom.getRandom().nextFloat() <= chance;
|
if (MyRandom.getRandom().nextFloat() <= chance) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -54,19 +54,29 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return untapPrefTargeting(ai, sa, false);
|
if (untapPrefTargeting(ai, sa, false)) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
return pDefined.isEmpty() || (pDefined.get(0).isTapped() && pDefined.get(0).getController() == ai);
|
if (pDefined.isEmpty() || (pDefined.get(0).isTapped() && pDefined.get(0).getController() == ai)) {
|
||||||
|
// If the defined card is tapped, or if there are no defined cards, we can play this ability
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
// Otherwise, we can't play this ability
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ public class CardStorageReader {
|
|||||||
sw.start();
|
sw.start();
|
||||||
executeLoadTask(result, taskFiles, cdlFiles);
|
executeLoadTask(result, taskFiles, cdlFiles);
|
||||||
sw.stop();
|
sw.stop();
|
||||||
final long timeOnParse = sw.getTime();
|
final long timeOnParse = sw.getTime(TimeUnit.SECONDS);
|
||||||
System.out.printf("Read cards: %s files in %d ms (%d parts) %s%n", allFiles.size(), timeOnParse, taskFiles.size(), useThreadPool ? "using thread pool" : "in same thread");
|
System.out.printf("Read cards: %s files in %d ms (%d parts) %s%n", allFiles.size(), timeOnParse, taskFiles.size(), useThreadPool ? "using thread pool" : "in same thread");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ public class CardStorageReader {
|
|||||||
sw.start();
|
sw.start();
|
||||||
executeLoadTask(result, taskZip, cdlZip);
|
executeLoadTask(result, taskZip, cdlZip);
|
||||||
sw.stop();
|
sw.stop();
|
||||||
final long timeOnParse = sw.getTime();
|
final long timeOnParse = sw.getTime(TimeUnit.SECONDS);
|
||||||
System.out.printf("Read cards: %s archived files in %d ms (%d parts) %s%n", this.zip.size(), timeOnParse, taskZip.size(), useThreadPool ? "using thread pool" : "in same thread");
|
System.out.printf("Read cards: %s archived files in %d ms (%d parts) %s%n", this.zip.size(), timeOnParse, taskZip.size(), useThreadPool ? "using thread pool" : "in same thread");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user