Some fixes (#8816)

This commit is contained in:
tool4ever
2025-10-01 16:54:46 +02:00
committed by GitHub
parent c0fe93ee30
commit 31f8da5687
11 changed files with 43 additions and 77 deletions

View File

@@ -887,27 +887,8 @@ public class AiController {
private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) {
final Card host = sa.getHostCard();
// Check a predefined condition
if (sa.hasParam("AICheckSVar")) {
final String svarToCheck = sa.getParam("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
if (sa.hasParam("AISVarCompare")) {
final String fullCmp = sa.getParam("AISVarCompare");
comparator = fullCmp.substring(0, 2);
final String strCmpTo = fullCmp.substring(2);
try {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
}
}
int left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
if (!Expressions.compare(left, comparator, compareTo)) {
return AiPlayDecision.AnotherTime;
}
if (sa.hasParam("AICheckSVar") && !aiShouldRun(sa, sa, host, null)) {
return AiPlayDecision.AnotherTime;
}
// this is the "heaviest" check, which also sets up targets, defines X, etc.
@@ -1817,14 +1798,9 @@ public class AiController {
* @param sa the sa
* @return true, if successful
*/
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
Card hostCard = effect.getHostCard();
if (hostCard.hasAlternateState()) {
hostCard = game.getCardState(hostCard);
}
public final boolean aiShouldRun(final CardTraitBase effect, final SpellAbility sa, final Card host, final GameEntity affected) {
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
final Player controller = hostCard.getController();
final Player controller = host.getController();
if (affected instanceof Player) {
return !((Player) affected).isOpponentOf(controller);
}
@@ -1833,7 +1809,6 @@ public class AiController {
}
}
if (effect.hasParam("AICheckSVar")) {
System.out.println("aiShouldRun?" + sa);
final String svarToCheck = effect.getParam("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
@@ -1846,9 +1821,9 @@ public class AiController {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
if (sa == null) {
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), effect);
} else {
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
}
}
}
@@ -1856,13 +1831,12 @@ public class AiController {
int left = 0;
if (sa == null) {
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
left = AbilityUtils.calculateAmount(host, svarToCheck, effect);
} else {
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
}
System.out.println("aiShouldRun?" + left + comparator + compareTo);
return Expressions.compare(left, comparator, compareTo);
} else if (effect.hasParam("AICheckDredge")) {
} else if (effect.isKeyword(Keyword.DREDGE)) {
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
} else return sa != null && doTrigger(sa, false);
}

View File

@@ -460,7 +460,11 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
return brains.aiShouldRun(replacementEffect, effectSA, affected);
Card host = replacementEffect.getHostCard();
if (host.hasAlternateState()) {
host = host.getGame().getCardState(host);
}
return brains.aiShouldRun(replacementEffect, effectSA, host, affected);
}
@Override

View File

@@ -96,6 +96,10 @@ public class CloneAi extends SpellAbilityAi {
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
} else {
if (sa.isReplacementAbility() && host.isCloned()) {
// prevent StackOverflow from infinite loop copying another ETB RE
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
}
if (sa.hasParam("Choices")) {
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
sa.getParam("Choices"), host.getController(), host, sa);

View File

@@ -92,9 +92,8 @@ public class CountersPutAi extends CountersAi {
return false;
}
return chance > MyRandom.getRandom().nextFloat();
} else {
return false;
}
return false;
}
if (sa.isKeyword(Keyword.LEVEL_UP)) {
@@ -124,7 +123,6 @@ public class CountersPutAi extends CountersAi {
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
CardCollection list;
Card choice = null;
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final boolean divided = sa.isDividedAsYouChoose();
@@ -292,10 +290,8 @@ public class CountersPutAi extends CountersAi {
if (willActivate) {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
} else {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
} else if (logic.equals("ChargeToBestCMC")) {
return doChargeToCMCLogic(ai, sa);
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
@@ -348,7 +344,7 @@ public class CountersPutAi extends CountersAi {
if (type.equals("P1P1")) {
nPump = amount;
}
return FightAi.canFightAi(ai, sa, nPump, nPump);
return FightAi.canFight(ai, sa, nPump, nPump);
}
if (amountStr.equals("X")) {
@@ -451,6 +447,7 @@ public class CountersPutAi extends CountersAi {
sa.resetTargets();
CardCollection list;
if (sa.isCurse()) {
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
} else {
@@ -746,7 +743,7 @@ public class CountersPutAi extends CountersAi {
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility();
final Card source = sa.getHostCard();
final String aiLogic = sa.getParamOrDefault("AILogic", "");
final String aiLogic = sa.getParam("AILogic");
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final boolean divided = sa.isDividedAsYouChoose();
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
@@ -765,14 +762,10 @@ public class CountersPutAi extends CountersAi {
}
if ("ChargeToBestCMC".equals(aiLogic)) {
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
if (decision.willingToPlay()) {
return decision;
}
if (mandatory) {
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
return doChargeToCMCLogic(ai, sa);
}
if (!sa.usesTargeting()) {
@@ -796,7 +789,6 @@ public class CountersPutAi extends CountersAi {
// things like Powder Keg, which are way too complex for the AI
}
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
// can only target opponent
PlayerCollection playerList = new PlayerCollection(IterableUtil.filter(
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
@@ -811,13 +803,12 @@ public class CountersPutAi extends CountersAi {
sa.getTargets().add(choice);
}
} else {
String logic = sa.getParam("AILogic");
if ("Fight".equals(logic) || "PowerDmg".equals(logic)) {
if ("Fight".equals(aiLogic) || "PowerDmg".equals(aiLogic)) {
int nPump = 0;
if (type.equals("P1P1")) {
nPump = amount;
}
AiAbilityDecision decision = FightAi.canFightAi(ai, sa, nPump, nPump);
AiAbilityDecision decision = FightAi.canFight(ai, sa, nPump, nPump);
if (decision.willingToPlay()) {
return decision;
}
@@ -838,7 +829,6 @@ public class CountersPutAi extends CountersAi {
while (sa.canAddMoreTarget()) {
if (mandatory) {
// When things are mandatory, gotta handle a little differently
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
}
@@ -863,7 +853,7 @@ public class CountersPutAi extends CountersAi {
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
}
Card choice = null;
Card choice;
// Choose targets here:
if (sa.isCurse()) {
@@ -889,10 +879,10 @@ public class CountersPutAi extends CountersAi {
choice = Aggregates.random(list);
}
if (choice != null && divided) {
int alloc = Math.max(amount / totalTargets, 1);
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
sa.addDividedAllocation(choice, left);
} else {
int alloc = Math.max(amount / totalTargets, 1);
sa.addDividedAllocation(choice, alloc);
left -= alloc;
}
@@ -982,9 +972,7 @@ public class CountersPutAi extends CountersAi {
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
final boolean isCurse = sa.isCurse();
if (isCurse) {
if (sa.isCurse()) {
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
if (!opponents.isEmpty()) {
@@ -1210,9 +1198,8 @@ public class CountersPutAi extends CountersAi {
}
if (numCtrs < optimalCMC) {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
} else {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {

View File

@@ -270,7 +270,7 @@ public class EffectAi extends SpellAbilityAi {
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
} else if (logic.equals("Fight")) {
return FightAi.canFightAi(ai, sa, 0,0);
return FightAi.canFight(ai, sa, 0,0);
} else if (logic.equals("Pump")) {
sa.resetTargets();
List<Card> options = CardUtil.getValidCardsToTarget(sa);

View File

@@ -177,7 +177,7 @@ public class FightAi extends SpellAbilityAi {
* @param power bonus to power
* @return true if fight effect should be played, false otherwise
*/
public static AiAbilityDecision canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
public static AiAbilityDecision canFight(final Player ai, final SpellAbility sa, int power, int toughness) {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
AbilitySub tgtFight = sa.getSubAbility();

View File

@@ -453,7 +453,7 @@ public class PumpAi extends PumpAiBase {
}
if (isFight) {
return FightAi.canFightAi(ai, sa, attack, defense).willingToPlay();
return FightAi.canFight(ai, sa, attack, defense).willingToPlay();
}
}

View File

@@ -269,22 +269,22 @@ public class EffectEffect extends SpellAbilityEffect {
}
}
// Set Chosen Color(s)
if (hostCard.hasChosenColor()) {
eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors()));
}
// Set Chosen Cards
if (hostCard.hasChosenCard()) {
eff.setChosenCards(hostCard.getChosenCards());
}
// Set Chosen Player
if (hostCard.hasChosenPlayer()) {
eff.setChosenPlayer(hostCard.getChosenPlayer());
}
// Set Chosen Type
if (hostCard.getChosenDirection() != null) {
eff.setChosenDirection(hostCard.getChosenDirection());
}
if (hostCard.hasChosenType()) {
eff.setChosenType(hostCard.getChosenType());
}
@@ -292,12 +292,10 @@ public class EffectEffect extends SpellAbilityEffect {
eff.setChosenType2(hostCard.getChosenType2());
}
// Set Chosen name
if (hostCard.hasNamedCard()) {
eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards()));
}
// chosen number
if (sa.hasParam("SetChosenNumber")) {
eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa));
} else if (hostCard.hasChosenNumber()) {

View File

@@ -2238,7 +2238,7 @@ public class CardFactoryUtil {
final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | "
+ "Secondary$ True | Optional$ True | CheckSVar$ "
+ "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount
+ " | AICheckDredge$ True | Description$ CARDNAME - Dredge " + dredgeAmount;
+ " | Description$ CARDNAME - Dredge " + dredgeAmount;
final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount;

View File

@@ -3,11 +3,9 @@ ManaCost:1 W
Types:Instant
A:SP$ ChooseSource | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life.
SVar:DBEffect:DB$ Effect | ReplacementEffects$ RepDmg | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem | ConditionCompare$ GE1
SVar:RepDmg:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ReplaceWith$ DBStoreSVar | PreventionEffect$ True | Description$ Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life.
SVar:DBStoreSVar:DB$ StoreSVar | SVar$ Z | Type$ Calculate | Expression$ X | SubAbility$ DBTrigger
SVar:RepDmg:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ReplaceWith$ DBTrigger | PreventionEffect$ True | Description$ Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life.
SVar:DBTrigger:DB$ ImmediateTrigger | Execute$ GainLifeYou | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE1 | TriggerDescription$ Whenever damage from a black or red source is prevented this way this turn, you gain that much life.
SVar:GainLifeYou:DB$ GainLife | Defined$ You | LifeAmount$ Z
SVar:X:ReplaceCount$DamageAmount
SVar:GainLifeYou:DB$ GainLife | Defined$ You | LifeAmount$ X
SVar:X:Spawner>ReplaceCount$DamageAmount
SVar:Y:ReplacedSource$Valid Card.BlackSource,Card.RedSource
SVar:Z:Number$0
Oracle:Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life.

View File

@@ -4,7 +4,8 @@ Types:Legendary Creature God Archer
PT:2/4
K:Reach
K:Partner - Father & Son
A:AB$ Draw | Cost$ 3 T | NumCards$ X | SubAbility$ DBDamage | SpellDescription$ Draw a card for each experience counter you have, then discard a card.
A:AB$ Draw | Cost$ 3 T | NumCards$ X | SubAbility$ DBDiscard | SpellDescription$ Draw a card for each experience counter you have, then discard a card.
SVar:DBDiscard:DB$ Discard | Mode$ YouChoose | SubAbility$ DBDamage
SVar:DBDamage:DB$ DealDamage | NumDmg$ 2 | Defined$ Opponent | SpellDescription$ CARDNAME deals 2 damage to each opponent.
SVar:X:Count$YourCountersExperience
Oracle:Reach\n{3}, {T}: Draw a card for each experience counter you have, then discard a card. Atreus, Impulsive Son deals 2 damage to each opponent.\nPartner-Father & son