Fix AI not targeting triggered Random Charm (#8955)

This commit is contained in:
tool4ever
2025-10-19 20:57:11 +02:00
committed by GitHub
parent 80a4a42575
commit 6f38479190
10 changed files with 33 additions and 24 deletions

View File

@@ -377,7 +377,7 @@ public class AiController {
if (card.isSaga()) {
for (final Trigger tr : card.getTriggers()) {
if (tr.getMode() != TriggerType.CounterAdded || !tr.isChapter()) {
if (!tr.isChapter()) {
continue;
}
@@ -393,6 +393,7 @@ public class AiController {
return false;
}
// usually later chapters make use of an earlier one
break;
}
}

View File

@@ -1274,9 +1274,15 @@ public class PlayerControllerAi extends PlayerController {
}
}
private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory) {
private boolean prepareSingleSa(final Card host, SpellAbility sa, boolean isMandatory) {
if (sa.getApi() == ApiType.Charm) {
return CharmEffect.makeChoices(sa);
if (!CharmEffect.makeChoices(sa)) {
return false;
}
if (!sa.hasParam("Random")) {
return true;
}
sa = sa.getSubAbility();
}
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);

View File

@@ -634,6 +634,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
// not urgent, get the largest creature possible
// TODO checkETBEffects
return ComputerUtilCard.getBestCreatureAI(list);
}
@@ -1546,10 +1547,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (fetchList.isEmpty()) {
return null;
}
String type = sa.getParam("ChangeType");
if (type == null) {
type = "Card";
}
String type = sa.getParamOrDefault("ChangeType", "");
Card c = null;
final Player activator = sa.getActivatingPlayer();

View File

@@ -94,18 +94,21 @@ public class CharmAi extends SpellAbilityAi {
private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choices, final Player ai, boolean isTrigger, int num, int min) {
List<AbilitySub> chosenList = Lists.newArrayList();
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
boolean allowRepeat = sa.hasParam("CanRepeatModes"); // FIXME: unused for now, the AI doesn't know how to effectively handle repeated choices
// TODO unused for now, the AI doesn't know how to effectively handle repeated choices
boolean allowRepeat = sa.hasParam("CanRepeatModes");
// Pawprint
final int pawprintLimit = sa.hasParam("Pawprint") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Pawprint"), sa) : 0;
if (pawprintLimit > 0) {
Collections.reverse(choices); // try to pay for the more expensive subs first
// try to pay for the more expensive subs first
Collections.reverse(choices);
}
int pawprintAmount = 0;
// First pass using standard canPlayAi() for good choices
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
// TODO refactor to obtain the AiAbilityDecision instead, then we can check all to sort by value
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
if (pawprintLimit > 0) {
int curPawprintAmount = AbilityUtils.calculateAmount(sub.getHostCard(), sub.getParamOrDefault("Pawprint", "0"), sub);
@@ -116,7 +119,8 @@ public class CharmAi extends SpellAbilityAi {
}
chosenList.add(sub);
if (chosenList.size() == num) {
return chosenList; // maximum choices reached
// maximum choices reached
return chosenList;
}
}
}
@@ -145,7 +149,8 @@ public class CharmAi extends SpellAbilityAi {
}
}
if (chosenList.size() < min) {
chosenList.clear(); // not enough choices
// not enough choices
chosenList.clear();
}
return chosenList;
}

View File

@@ -72,16 +72,14 @@ public class ChooseCardNameAi extends SpellAbilityAi {
// 5 percent chance to cast per opposing card with a non mana ability
if (MyRandom.getRandom().nextFloat() <= .05 * oppPerms.size()) {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
} else {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
if (mandatory) {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
} else {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)

View File

@@ -1175,7 +1175,7 @@ public class CountersPutAi extends CountersAi {
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
if (sa.hasParam("ReadAhead")) {
if (sa.isKeyword(Keyword.READ_AHEAD)) {
return 1;
}
return max;

View File

@@ -982,6 +982,9 @@ public class AbilityUtils {
for (final Card c : getDefinedCards(card, "Targeted", sa)) {
players.add(c.getOwner());
}
for (final SpellAbility s : getDefinedSpellAbilities(card, "Targeted", sa)) {
players.add(s.getHostCard().getOwner());
}
} else if (defined.equals("TargetedAndYou") && sa instanceof SpellAbility) {
final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingPlayer();
if (saTargeting != null) {
@@ -1118,11 +1121,9 @@ public class AbilityUtils {
final String replacingType = defined.substring(8);
o = root.getReplacingObject(AbilityKey.fromString(replacingType));
}
if (o != null) {
if (o instanceof Player) {
players.add((Player) o);
}
}
} else if (defined.startsWith("Non")) {
players.addAll(game.getPlayersInTurnOrder());
players.removeAll(getDefinedPlayers(card, defined.substring(3), sa));

View File

@@ -2496,7 +2496,7 @@ public class CardFactoryUtil {
inst.addReplacement(re);
} else if (keyword.equals("Read ahead")) {
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | Secondary$ True | ReplacementResult$ Updated | Description$ Choose a chapter and start with that many lore counters.";
String effStr = "DB$ PutCounter | Defined$ Self | CounterType$ LORE | ETB$ True | UpTo$ True | UpToMin$ 1 | ReadAhead$ True | CounterNum$ FinalChapterNr";
String effStr = "DB$ PutCounter | Defined$ Self | CounterType$ LORE | ETB$ True | UpTo$ True | UpToMin$ 1 | CounterNum$ FinalChapterNr";
SpellAbility saCounter = AbilityFactory.getAbility(effStr, card);
saCounter.setSVar("FinalChapterNr", "Count$FinalChapterNr");

View File

@@ -4,7 +4,7 @@ Types:Artifact
K:Flash
K:ETBReplacement:Other:DBNameCard
SVar:DBNameCard:DB$ NameCard | Defined$ You | SpellDescription$ As CARDNAME enters, choose a card name.
S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | Type$ Spell | Activator$ Player | Amount$ 3 | Description$ Spells with the chosen name cost 3 more to cast.
S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | Type$ Spell | Activator$ Player | Amount$ 3 | Description$ Spells with the chosen name cost {3} more to cast.
S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.!ManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities.
AI:RemoveDeck:Random
Oracle:Flash\nAs Disruptor Flute enters, choose a card name.\nSpells with the chosen name cost 3 more to cast.\nActivated abilities of sources with the chosen name can't be activated unless they're mana abilities.
Oracle:Flash\nAs Disruptor Flute enters, choose a card name.\nSpells with the chosen name cost {3} more to cast.\nActivated abilities of sources with the chosen name can't be activated unless they're mana abilities.

View File

@@ -5,7 +5,7 @@ PT:2/2
K:Trample
K:etbCounter:P1P1:X:no Condition:NICKNAME enters with a +1/+1 counter on him for each land you control.
SVar:X:Count$Valid Land.YouCtrl
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME dies, return this card to your hand.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When NICKNAME dies, return this card to your hand.
SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand
DeckHas:Ability$Counters
Oracle:Trample (This creature can deal excess combat damage to the player it's attacking.)\nMichelangelo enters with a +1/+1 counter on him for each land you control.\nWhen Michelangelo dies, return this card to your hand.