mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 02:08:00 +00:00
Streamline logic so canPlay always before canPay (#8234)
This commit is contained in:
@@ -911,21 +911,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int oldCMC = -1;
|
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||||
boolean xCost = sa.costHasX() || host.hasKeyword(Keyword.STRIVE) || sa.getApi() == ApiType.Charm;
|
AiPlayDecision canPlay = canPlaySa(sa);
|
||||||
if (!xCost) {
|
|
||||||
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
|
|
||||||
// when the AI won't even be able to play the spell in the first place (even if it could afford it)
|
|
||||||
return AiPlayDecision.CantAfford;
|
|
||||||
}
|
|
||||||
// TODO check for Reduce too, e.g. Battlefield Thaumaturge could make it castable
|
|
||||||
if (!sa.getAllTargetChoices().isEmpty()) {
|
|
||||||
oldCMC = CostAdjustment.adjust(sa.getPayCosts(), sa, false).getTotalMana().getCMC();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
|
||||||
|
|
||||||
if (canPlay != AiPlayDecision.WillPlay) {
|
if (canPlay != AiPlayDecision.WillPlay) {
|
||||||
return canPlay;
|
return canPlay;
|
||||||
@@ -940,9 +927,6 @@ public class AiController {
|
|||||||
// 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(host.getController())) {
|
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
|
||||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||||
if (wardCost.hasManaCost()) {
|
|
||||||
xCost |= wardCost.getTotalMana().getCMC() > 0;
|
|
||||||
}
|
|
||||||
SpellAbilityAi topAI = new SpellAbilityAi() {};
|
SpellAbilityAi topAI = new SpellAbilityAi() {};
|
||||||
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
|
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
|
||||||
return AiPlayDecision.CostNotAcceptable;
|
return AiPlayDecision.CostNotAcceptable;
|
||||||
@@ -952,15 +936,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if some target raised cost
|
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
||||||
if (!xCost && oldCMC > -1) {
|
|
||||||
int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa, false).getTotalMana().getCMC();
|
|
||||||
if (finalCMC > oldCMC) {
|
|
||||||
xCost = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xCost && !ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
|
||||||
// for dependent costs with X, e.g. Repeal, which require a valid target to be specified before a decision can be made
|
// for dependent costs with X, e.g. Repeal, which require a valid target to be specified before a decision can be made
|
||||||
// on whether the cost can be paid, this can only be checked late after canPlaySa has been run (or the AI will misplay)
|
// on whether the cost can be paid, this can only be checked late after canPlaySa has been run (or the AI will misplay)
|
||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
@@ -973,8 +949,6 @@ public class AiController {
|
|||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we got here, looks like we can play the final cost and we could properly set up and target the API and
|
|
||||||
// are willing to play the SA
|
|
||||||
return AiPlayDecision.WillPlay;
|
return AiPlayDecision.WillPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -666,6 +666,7 @@ public class ComputerUtilMana {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int phyLifeToPay = 2;
|
||||||
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
||||||
boolean hasConverge = sa.getHostCard().hasConverge();
|
boolean hasConverge = sa.getHostCard().hasConverge();
|
||||||
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = getSourcesForShards(cost, sa, ai, test, checkPlayable, hasConverge);
|
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = getSourcesForShards(cost, sa, ai, test, checkPlayable, hasConverge);
|
||||||
@@ -693,13 +694,12 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourcesForShards == null && !purePhyrexian) {
|
if (sourcesForShards == null && !purePhyrexian) {
|
||||||
break; // no mana abilities to use for paying
|
// no mana abilities to use for paying
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
toPay = getNextShardToPay(cost, sourcesForShards);
|
toPay = getNextShardToPay(cost, sourcesForShards);
|
||||||
|
|
||||||
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
|
||||||
|
|
||||||
Collection<SpellAbility> saList = null;
|
Collection<SpellAbility> saList = null;
|
||||||
if (hasConverge &&
|
if (hasConverge &&
|
||||||
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
(toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
|
||||||
@@ -752,9 +752,14 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (saPayment == null) {
|
if (saPayment == null) {
|
||||||
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2, false, sa)
|
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
|
||||||
|| (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
|
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(phyLifeToPay, false, sa)
|
||||||
break; // cannot pay
|
|| (ai.getLife() <= phyLifeToPay && !ai.cantLoseForZeroOrLessLife())) {
|
||||||
|
// cannot pay
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (test) {
|
||||||
|
phyLifeToPay += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("AIPhyrexianPayment")) {
|
if (sa.hasParam("AIPhyrexianPayment")) {
|
||||||
|
|||||||
@@ -551,10 +551,10 @@ public class ManaCostBeingPaid {
|
|||||||
// The generic portion of a 2/Colored mana, should be lower priority than generic mana
|
// The generic portion of a 2/Colored mana, should be lower priority than generic mana
|
||||||
return !ColorSet.fromMask(bill.getColorMask() & paymentColor).isColorless() ? 9 : 1;
|
return !ColorSet.fromMask(bill.getColorMask() & paymentColor).isColorless() ? 9 : 1;
|
||||||
}
|
}
|
||||||
if (!bill.isPhyrexian()) {
|
if (bill.isPhyrexian()) {
|
||||||
return 10;
|
return 8;
|
||||||
}
|
}
|
||||||
return 8;
|
return 10;
|
||||||
}
|
}
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +47,9 @@ public class InputPayManaOfCostPayment extends InputPayMana {
|
|||||||
if (manaCost.payPhyrexian()) {
|
if (manaCost.payPhyrexian()) {
|
||||||
saPaidFor.setSpendPhyrexianMana(true);
|
saPaidFor.setSpendPhyrexianMana(true);
|
||||||
this.phyLifeToLose += 2;
|
this.phyLifeToLose += 2;
|
||||||
} else {
|
} else if (player.hasKeyword("PayLifeInsteadOf:B") && manaCost.hasAnyKind(ManaAtom.BLACK)) {
|
||||||
if (player.hasKeyword("PayLifeInsteadOf:B") && manaCost.hasAnyKind(ManaAtom.BLACK)) {
|
manaCost.decreaseShard(ManaCostShard.BLACK, 1);
|
||||||
manaCost.decreaseShard(ManaCostShard.BLACK, 1);
|
this.phyLifeToLose += 2;
|
||||||
this.phyLifeToLose += 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user