mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Merge branch 'ai-updates' into 'master'
Fixed an issue with the AI not playing spells like Repeal anymore, tweaked AI damage prediction to account for static effects a little better. See merge request core-developers/forge!1166
This commit is contained in:
@@ -670,7 +670,11 @@ public class AiController {
|
||||
|
||||
// This is for playing spells regularly (no Cascade/Ripple etc.)
|
||||
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
|
||||
if (!ComputerUtilCost.canPayCost(sa, player)) {
|
||||
boolean xCost = ComputerUtilMana.hasXInAnyCostPart(sa);
|
||||
|
||||
if (!xCost && !ComputerUtilCost.canPayCost(sa, player)) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -678,7 +682,20 @@ public class AiController {
|
||||
return AiPlayDecision.CantPlaySa;
|
||||
}
|
||||
|
||||
return canPlaySa(sa);
|
||||
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||
if (canPlay != AiPlayDecision.WillPlay) {
|
||||
return canPlay;
|
||||
}
|
||||
|
||||
if (xCost && !ComputerUtilCost.canPayCost(sa, player)) {
|
||||
// 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)
|
||||
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;
|
||||
}
|
||||
|
||||
public AiPlayDecision canPlaySa(SpellAbility sa) {
|
||||
|
||||
@@ -787,6 +787,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
if (sVar.equals("XChoice")) {
|
||||
c = AbilityUtils.calculateAmount(source, "ChosenX", ability);
|
||||
source.setSVar("ChosenX", "Number$" + String.valueOf(c));
|
||||
} else if (amount.equals("All")) {
|
||||
c = source.getCounters(cost.counter);
|
||||
} else if (sVar.equals("Targeted$CardManaCost")) {
|
||||
|
||||
@@ -2104,6 +2104,16 @@ public class ComputerUtilCombat {
|
||||
defenderDamage = predictDamageTo(attacker, defenderDamage, possibleAttackerPrevention, blocker, true);
|
||||
attackerDamage = predictDamageTo(blocker, attackerDamage, possibleDefenderPrevention, attacker, true);
|
||||
|
||||
// Damage prevention might come from a static effect
|
||||
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
|
||||
if (isCombatDamagePrevented(attacker, blocker, attackerDamage)) {
|
||||
attackerDamage = 0;
|
||||
}
|
||||
if (isCombatDamagePrevented(blocker, attacker, defenderDamage)) {
|
||||
defenderDamage = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (combat != null) {
|
||||
for (Card atkr : combat.getAttackersBlockedBy(blocker)) {
|
||||
if (!atkr.equals(attacker)) {
|
||||
|
||||
@@ -92,7 +92,7 @@ public class ComputerUtilCost {
|
||||
// value later as the AI decides what to do (in checkApiLogic / checkAiLogic)
|
||||
if (sa != null && sa.hasSVar(remCounter.getAmount())) {
|
||||
final String sVar = sa.getSVar(remCounter.getAmount());
|
||||
if (sVar.equals("XChoice")) {
|
||||
if (sVar.equals("XChoice") && !sa.hasSVar("ChosenX")) {
|
||||
sa.setSVar("ChosenX", String.valueOf(source.getCounters(type)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostAdjustment;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.cost.CostPayment;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.mana.ManaPool;
|
||||
@@ -1560,6 +1556,24 @@ public class ComputerUtilMana {
|
||||
return convoke;
|
||||
}
|
||||
|
||||
public static boolean hasXInAnyCostPart(SpellAbility sa) {
|
||||
boolean xCost = false;
|
||||
if (sa.getPayCosts() != null) {
|
||||
for (CostPart p : sa.getPayCosts().getCostParts()) {
|
||||
if (p instanceof CostPartMana) {
|
||||
if (((CostPartMana) p).getAmountOfX() > 0) {
|
||||
xCost = true;
|
||||
break;
|
||||
}
|
||||
} else if (p.getAmount().equals("X")) {
|
||||
xCost = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return xCost;
|
||||
}
|
||||
|
||||
public static int determineMaxAffordableX(Player ai, SpellAbility sa) {
|
||||
if (sa.getPayCosts() == null || sa.getPayCosts().getCostMana() == null) {
|
||||
return -1;
|
||||
|
||||
@@ -91,6 +91,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Don't try to attach an aura to a card which will have protection from the relevant color
|
||||
// TODO: Fix this not to be dependent on "Protection from Color" wording and to be flexible to account for
|
||||
// other possibilities like "protection from all colors" etc.
|
||||
Card targeted = sa.getTargets().getFirstTargetedCard();
|
||||
if (targeted != null && !targeted.getZone().is(ZoneType.Battlefield)) {
|
||||
byte color = sa.getTargets().getFirstTargetedCard().getCurrentState().getColor();
|
||||
|
||||
@@ -15,6 +15,7 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -29,7 +30,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
|
||||
if ("ManaRitual".equals(aiLogic)) {
|
||||
if (aiLogic.startsWith("ManaRitual")) {
|
||||
return doManaRitualLogic(ai, sa);
|
||||
} else if ("Always".equals(aiLogic)) {
|
||||
return true;
|
||||
@@ -117,7 +118,8 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
String produced = sa.getParam("Produced");
|
||||
byte producedColor = produced.equals("Any") ? MagicColor.ALL_COLORS : MagicColor.fromName(produced);
|
||||
|
||||
if ("ChosenX".equals(sa.getParam("Amount"))
|
||||
int numCounters = 0;
|
||||
if ("XChoice".equals(host.getSVar("X"))
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||
CounterType ctrType = CounterType.KI; // Petalmane Baku
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
@@ -126,7 +128,11 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
break;
|
||||
}
|
||||
}
|
||||
manaReceived = host.getCounters(ctrType);
|
||||
numCounters = host.getCounters(ctrType);
|
||||
manaReceived = numCounters;
|
||||
if ("ManaRitualBattery".equals(sa.getParam("AILogic"))) {
|
||||
manaReceived++; // adds an extra mana even if no counters removed
|
||||
}
|
||||
}
|
||||
|
||||
int searchCMC = numManaSrcs - selfCost + manaReceived;
|
||||
@@ -196,6 +202,12 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
CardPredicates.lessCMC(searchCMC),
|
||||
Predicates.or(CardPredicates.isColorless(), CardPredicates.isColor(producedColor))));
|
||||
|
||||
if ("ManaRitualBattery".equals(sa.getParam("AILogic"))) {
|
||||
// Don't remove more counters than would be needed to cast everything we want to cast
|
||||
int maxCtrs = Aggregates.sum(castableSpells, CardPredicates.Accessors.fnGetCmc);
|
||||
sa.setSVar("ChosenX", "Number$" + Math.min(numCounters, maxCtrs));
|
||||
}
|
||||
|
||||
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
|
||||
return castableSpells.size() > 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Black Mana Battery
|
||||
ManaCost:4
|
||||
Types:Artifact
|
||||
A:AB$ PutCounter | Cost$ 2 T | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | References$ X,Y | Produced$ B | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | SpellDescription$ Add {B}, then add an additional {B} for each charge counter removed this way.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | References$ X,Y | Produced$ B | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | AILogic$ ManaRitualBattery | AINoRecursiveCheck$ True | SpellDescription$ Add {B}, then add an additional {B} for each charge counter removed this way.
|
||||
SVar:Y:Number$1/Plus.ChosenX
|
||||
SVar:X:XChoice
|
||||
#ChosenX SVar created by Cost payment
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Blue Mana Battery
|
||||
ManaCost:4
|
||||
Types:Artifact
|
||||
A:AB$ PutCounter | Cost$ 2 T | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ U | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | References$ X,Y | SpellDescription$ Add {U}, then add an additional {U} for each charge counter removed this way.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ U | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | References$ X,Y | AILogic$ ManaRitualBattery | AINoRecursiveCheck$ True | SpellDescription$ Add {U}, then add an additional {U} for each charge counter removed this way.
|
||||
SVar:Y:Number$1/Plus.ChosenX
|
||||
SVar:X:XChoice
|
||||
#ChosenX SVar created by Cost payment
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Green Mana Battery
|
||||
ManaCost:4
|
||||
Types:Artifact
|
||||
A:AB$ PutCounter | Cost$ 2 T | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ G | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | References$ X,Y | SpellDescription$ Add {G}, then add an additional {G} for each charge counter removed this way.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ G | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | References$ X,Y | AILogic$ ManaRitualBattery | AINoRecursiveCheck$ True | SpellDescription$ Add {G}, then add an additional {G} for each charge counter removed this way.
|
||||
SVar:Y:Number$1/Plus.ChosenX
|
||||
SVar:X:XChoice
|
||||
#ChosenX SVar created by Cost payment
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Red Mana Battery
|
||||
ManaCost:4
|
||||
Types:Artifact
|
||||
A:AB$ PutCounter | Cost$ 2 T | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ R | Amount$ Y | References$ X,Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | SpellDescription$ Add {R}, then add an additional {R} for each charge counter removed this way.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ R | Amount$ Y | References$ X,Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | AILogic$ ManaRitualBattery | AINoRecursiveCheck$ True | SpellDescription$ Add {R}, then add an additional {R} for each charge counter removed this way.
|
||||
SVar:Y:Number$1/Plus.ChosenX
|
||||
SVar:X:XChoice
|
||||
#ChosenX SVar created by Cost payment
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:White Mana Battery
|
||||
ManaCost:4
|
||||
Types:Artifact
|
||||
A:AB$ PutCounter | Cost$ 2 T | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ W | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | SpellDescription$ Add {W}, then add an additional {W} for each charge counter removed this way.
|
||||
A:AB$ Mana | Cost$ T SubCounter<X/CHARGE> | Produced$ W | Amount$ Y | CostDesc$ {T}, Remove any number of charge counters from CARDNAME: | AILogic$ ManaRitualBattery | AINoRecursiveCheck$ True | SpellDescription$ Add {W}, then add an additional {W} for each charge counter removed this way.
|
||||
SVar:Y:Number$1/Plus.ChosenX
|
||||
SVar:X:XChoice
|
||||
#ChosenX SVar created by Cost payment
|
||||
|
||||
Reference in New Issue
Block a user