mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 10:18:01 +00:00
Merge pull request #175 from tool4ever/unlessCost
willPayUnlessCost: teach AI to refuse if Uncounterable already prevents it
This commit is contained in:
@@ -572,8 +572,7 @@ public class AiAttackController {
|
|||||||
if (numExtraBlocks > 0) {
|
if (numExtraBlocks > 0) {
|
||||||
// TODO should be limited to how much getBlockCost the opp can pay
|
// TODO should be limited to how much getBlockCost the opp can pay
|
||||||
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
||||||
blockedAttackers.add(remainingAttackers.get(0));
|
blockedAttackers.add(remainingAttackers.remove(0));
|
||||||
remainingAttackers.remove(0);
|
|
||||||
maxBlockersAfterCrew--;
|
maxBlockersAfterCrew--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -581,8 +580,7 @@ public class AiAttackController {
|
|||||||
if (remainingAttackers.isEmpty()) {
|
if (remainingAttackers.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
blockedAttackers.add(remainingAttackers.get(0));
|
blockedAttackers.add(remainingAttackers.remove(0));
|
||||||
remainingAttackers.remove(0);
|
|
||||||
maxBlockersAfterCrew--;
|
maxBlockersAfterCrew--;
|
||||||
}
|
}
|
||||||
unblockedAttackers.addAll(remainingAttackers);
|
unblockedAttackers.addAll(remainingAttackers);
|
||||||
@@ -605,8 +603,8 @@ public class AiAttackController {
|
|||||||
final CardCollection unblockableCantPayFor = new CardCollection();
|
final CardCollection unblockableCantPayFor = new CardCollection();
|
||||||
final CardCollection unblockableWithoutCost = new CardCollection();
|
final CardCollection unblockableWithoutCost = new CardCollection();
|
||||||
// TODO also check poison
|
// TODO also check poison
|
||||||
for (Card attacker : CardLists.getKeyword(unblockedAttackers, Keyword.TRAMPLE)) {
|
for (Card attacker : unblockedAttackers) {
|
||||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
|
||||||
if (tax == null) {
|
if (tax == null) {
|
||||||
unblockableWithoutCost.add(attacker);
|
unblockableWithoutCost.add(attacker);
|
||||||
} else {
|
} else {
|
||||||
@@ -620,19 +618,17 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
int dmgUnblockableAfterPaying = ComputerUtilCombat.sumDamageIfUnblocked(unblockableWithPaying, defendingOpponent);
|
int dmgUnblockableAfterPaying = ComputerUtilCombat.sumDamageIfUnblocked(unblockableWithPaying, defendingOpponent);
|
||||||
|
unblockedAttackers = unblockableWithoutCost;
|
||||||
if (dmgUnblockableAfterPaying > trampleDamage) {
|
if (dmgUnblockableAfterPaying > trampleDamage) {
|
||||||
unblockedAttackers.removeAll(unblockableCantPayFor);
|
myFreeMana -= unblockableAttackTax;
|
||||||
unblockedAttackers.removeAll(unblockableWithPaying);
|
|
||||||
totalCombatDamage = dmgUnblockableAfterPaying;
|
totalCombatDamage = dmgUnblockableAfterPaying;
|
||||||
// recalculate the trampler damage with the reduced mana available now
|
// recalculate the trampler damage with the reduced mana available now
|
||||||
myFreeMana -= unblockableAttackTax;
|
|
||||||
trampleDamage = getDamageFromBlockingTramplers(blockedAttackers, remainingBlockers, myFreeMana).getLeft();
|
trampleDamage = getDamageFromBlockingTramplers(blockedAttackers, remainingBlockers, myFreeMana).getLeft();
|
||||||
} else {
|
} else {
|
||||||
unblockedAttackers = unblockableWithoutCost;
|
|
||||||
myFreeMana -= tramplerTaxPaid;
|
myFreeMana -= tramplerTaxPaid;
|
||||||
// find out if we can still pay for some left
|
// find out if we can still pay for some left
|
||||||
for (Card attacker : unblockableWithPaying) {
|
for (Card attacker : unblockableWithPaying) {
|
||||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
|
||||||
int taxCMC = tax.getCostMana().getMana().getCMC();
|
int taxCMC = tax.getCostMana().getMana().getCMC();
|
||||||
if (myFreeMana < unblockableAttackTax + taxCMC) {
|
if (myFreeMana < unblockableAttackTax + taxCMC) {
|
||||||
continue;
|
continue;
|
||||||
@@ -664,7 +660,7 @@ public class AiAttackController {
|
|||||||
CardCollection remainingBlockers = new CardCollection(blockers);
|
CardCollection remainingBlockers = new CardCollection(blockers);
|
||||||
for (Card attacker : CardLists.getKeyword(blockedAttackers, Keyword.TRAMPLE)) {
|
for (Card attacker : CardLists.getKeyword(blockedAttackers, Keyword.TRAMPLE)) {
|
||||||
// TODO might sort by quotient of dmg/cost for best combination
|
// TODO might sort by quotient of dmg/cost for best combination
|
||||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
|
||||||
int taxCMC = tax != null ? tax.getCostMana().getMana().getCMC() : 0;
|
int taxCMC = tax != null ? tax.getCostMana().getMana().getCMC() : 0;
|
||||||
if (myFreeMana < currentAttackTax + taxCMC) {
|
if (myFreeMana < currentAttackTax + taxCMC) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -709,9 +709,10 @@ public class AiController {
|
|||||||
return AiPlayDecision.CantPlaySa;
|
return AiPlayDecision.CantPlaySa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
|
||||||
// Check a predefined condition
|
// Check a predefined condition
|
||||||
if (sa.hasParam("AICheckSVar")) {
|
if (sa.hasParam("AICheckSVar")) {
|
||||||
final Card host = sa.getHostCard();
|
|
||||||
final String svarToCheck = sa.getParam("AICheckSVar");
|
final String svarToCheck = sa.getParam("AICheckSVar");
|
||||||
String comparator = "GE";
|
String comparator = "GE";
|
||||||
int compareTo = 1;
|
int compareTo = 1;
|
||||||
@@ -734,7 +735,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int oldCMC = -1;
|
int oldCMC = -1;
|
||||||
boolean xCost = sa.costHasX() || sa.getHostCard().hasStartOfKeyword("Strive");
|
boolean xCost = sa.costHasX() || host.hasStartOfKeyword("Strive");
|
||||||
if (!xCost) {
|
if (!xCost) {
|
||||||
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
||||||
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
|
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
|
||||||
@@ -748,15 +749,15 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// state needs to be switched here so API checks evaluate the right face
|
// state needs to be switched here so API checks evaluate the right face
|
||||||
CardStateName currentState = sa.getCardState() != null && sa.getHostCard().getCurrentStateName() != sa.getCardStateName() && !sa.getHostCard().isInPlay() ? sa.getHostCard().getCurrentStateName() : null;
|
CardStateName currentState = sa.getCardState() != null && host.getCurrentStateName() != sa.getCardStateName() && !host.isInPlay() ? host.getCurrentStateName() : null;
|
||||||
if (currentState != null) {
|
if (currentState != null) {
|
||||||
sa.getHostCard().setState(sa.getCardStateName(), false);
|
host.setState(sa.getCardStateName(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||||
|
|
||||||
if (currentState != null) {
|
if (currentState != null) {
|
||||||
sa.getHostCard().setState(currentState, false);
|
host.setState(currentState, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canPlay != AiPlayDecision.WillPlay) {
|
if (canPlay != AiPlayDecision.WillPlay) {
|
||||||
@@ -766,10 +767,10 @@ public class AiController {
|
|||||||
// Account for possible Ward after the spell is fully targeted
|
// Account for possible Ward after the spell is fully targeted
|
||||||
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
|
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
|
||||||
// one is warded and can't be paid for.
|
// one is warded and can't be paid for.
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting() && CardFactoryUtil.isCounterable(host)) {
|
||||||
for (Card tgt : sa.getTargets().getTargetCards()) {
|
for (Card tgt : sa.getTargets().getTargetCards()) {
|
||||||
// TODO some older cards don't use the keyword, so check for trigger instead
|
// TODO some older cards don't use the keyword, so check for trigger instead
|
||||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||||
if (wardCost.hasManaCost()) {
|
if (wardCost.hasManaCost()) {
|
||||||
|
|||||||
@@ -1268,21 +1268,17 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Card> list = Lists.newArrayList();
|
List<Card> list = Lists.newArrayList();
|
||||||
if (!sa.hasParam("ValidCards")) {
|
|
||||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
|
|
||||||
}
|
|
||||||
if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) {
|
|
||||||
list.add(attacker);
|
|
||||||
}
|
|
||||||
if (sa.hasParam("ValidCards")) {
|
if (sa.hasParam("ValidCards")) {
|
||||||
if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null)
|
if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null)
|
||||||
|| attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","),
|
|| attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","),
|
||||||
source.getController(), source, null)) {
|
source.getController(), source, null)) {
|
||||||
list.add(attacker);
|
list.add(attacker);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
|
||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) {
|
||||||
continue;
|
list.add(attacker);
|
||||||
}
|
}
|
||||||
if (!list.contains(attacker)) {
|
if (!list.contains(attacker)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1464,14 +1460,15 @@ public class ComputerUtilCombat {
|
|||||||
toughness -= predictDamageTo(attacker, damage, source, false);
|
toughness -= predictDamageTo(attacker, damage, source, false);
|
||||||
continue;
|
continue;
|
||||||
} else if (ApiType.Pump.equals(sa.getApi())) {
|
} else if (ApiType.Pump.equals(sa.getApi())) {
|
||||||
|
if (!sa.hasParam("NumDef")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (sa.hasParam("Cost")) {
|
if (sa.hasParam("Cost")) {
|
||||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sa.hasParam("NumDef")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final String defined = sa.getParam("Defined");
|
final String defined = sa.getParam("Defined");
|
||||||
CardCollection list = AbilityUtils.getDefinedCards(source, defined, sa);
|
CardCollection list = AbilityUtils.getDefinedCards(source, defined, sa);
|
||||||
if (defined != null && defined.startsWith("TriggeredAttacker")) {
|
if (defined != null && defined.startsWith("TriggeredAttacker")) {
|
||||||
@@ -1497,6 +1494,9 @@ public class ComputerUtilCombat {
|
|||||||
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
}
|
}
|
||||||
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
||||||
|
if (!sa.hasParam("NumDef")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (sa.hasParam("Cost")) {
|
if (sa.hasParam("Cost")) {
|
||||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1506,9 +1506,6 @@ public class ComputerUtilCombat {
|
|||||||
if (!sa.hasParam("ValidCards")) {
|
if (!sa.hasParam("ValidCards")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!sa.hasParam("NumDef")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, sa)) {
|
if (!attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, sa)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -528,10 +528,11 @@ public class ComputerUtilCost {
|
|||||||
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
|
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
|
||||||
|
|
||||||
// Check for stuff like Nether Void
|
// Check for stuff like Nether Void
|
||||||
int extraManaNeeded = 0;
|
int extraManaNeeded = 0;
|
||||||
if (sa instanceof Spell) {
|
if (sa instanceof Spell) {
|
||||||
final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
|
|
||||||
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||||
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
|
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
|
||||||
if (!StringUtils.isBlank(snem)) {
|
if (!StringUtils.isBlank(snem)) {
|
||||||
@@ -591,7 +592,7 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ward - will be accounted for when rechecking a targeted ability
|
// Ward - will be accounted for when rechecking a targeted ability
|
||||||
if (!(sa instanceof WrappedAbility) && sa.usesTargeting()) {
|
if (!(sa instanceof WrappedAbility) && sa.usesTargeting() && !cannotBeCountered) {
|
||||||
for (Card tgt : sa.getTargets().getTargetCards()) {
|
for (Card tgt : sa.getTargets().getTargetCards()) {
|
||||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||||
@@ -721,6 +722,17 @@ public class ComputerUtilCost {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ward or human misplay
|
||||||
|
if (ApiType.Counter.equals(sa.getApi())) {
|
||||||
|
List<SpellAbility> spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa);
|
||||||
|
for (SpellAbility toBeCountered : spells) {
|
||||||
|
if (!CardFactoryUtil.isCounterable(toBeCountered.getHostCard())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO check hasFizzled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AI was crashing because the blank ability used to pay costs
|
// AI was crashing because the blank ability used to pay costs
|
||||||
// Didn't have any of the data on the original SA to pay dependant costs
|
// Didn't have any of the data on the original SA to pay dependant costs
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user