Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions forge-ai/src/main/java/forge/ai/ability/CharmAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CharmAi extends SpellAbilityAi {
@Override
Expand All @@ -32,6 +33,7 @@ protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {

boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
boolean choiceForOpp = !ai.equals(sa.getActivatingPlayer());
boolean eachModeMustTargetADifferentPlayer = Boolean.parseBoolean(sa.getParamOrDefault("EachModeMustTargetADifferentPlayer", "false"));

// Reset the chosen list otherwise it will be locked in forever by earlier calls
sa.setChosenList(null);
Expand All @@ -44,6 +46,12 @@ protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
chosenList = choices.subList(1, choices.size());
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
chosenList = chooseTriskaidekaphobia(choices, ai);
} else if (eachModeMustTargetADifferentPlayer && min > 1) {
if (min > ai.getGame().getPlayers().size() || min > choices.size()) {
// not enough different players or modes to choose from
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
chosenList = chooseEachModeMustTargetADifferentPlayer(choices, timingRight, min, num, sa, ai);
} else {
// only randomize if not all possible together
if (num < choices.size()) {
Expand Down Expand Up @@ -91,6 +99,57 @@ protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
return super.checkApiLogic(ai, sa);
}

private boolean addedTheLastChoice(List<AbilitySub> chosenList, List<AbilitySub> choices, AbilitySub chosenMode, Player player, int num) {
chosenMode.resetTargets();
chosenMode.getTargets().add(player);
chosenList.add(chosenMode);
choices.remove(chosenMode);
return chosenList.size() == num;
}

private List<AbilitySub> chooseEachModeMustTargetADifferentPlayer(List<AbilitySub> choices, boolean isTrigger, int min, int num, SpellAbility sa, Player ai) {
List<AbilitySub> chosenList = Lists.newArrayList();
List<AbilitySub> choicesForAi = chooseOptionsAi(sa, choices, ai, isTrigger, 1, 1);
if (choicesForAi.isEmpty()) {
// nothing found for AI, do not bother assigning other modes to other players
return chosenList;
} else if (addedTheLastChoice(chosenList, choices, choicesForAi.get(0), ai, num)) {
return chosenList;
}

Map<Boolean, List<Player>> playersByAi = ai.getGame().getPlayers().stream().collect(Collectors.partitioningBy(player -> player.getController().isAI()));

//try to find choices for other AI players first
for (Player otherAi : playersByAi.getOrDefault(Boolean.TRUE, List.of())) {
if (ai.equals(otherAi)) {
// already assigned the best choice
continue;
}
List<AbilitySub> choicesForOtherAi = chooseOptionsAi(sa, choices, otherAi, isTrigger, 1, 1);
if (!choicesForOtherAi.isEmpty() && addedTheLastChoice(chosenList, choices, choicesForOtherAi.get(0), otherAi, num)) {
return chosenList;
}
}

//assign the remaining choices to human players at random
List<Player> humanPlayers = playersByAi.getOrDefault(Boolean.FALSE, List.of());
int remainingChoices = min - chosenList.size();
if (remainingChoices > 0 && humanPlayers.size() >= remainingChoices && choices.size() >= remainingChoices) {
Collections.shuffle(choices);
for (int i = 0; i < remainingChoices; i++) {
if (addedTheLastChoice(chosenList, choices, choices.get(i), humanPlayers.get(i), num)) {
return chosenList;
}
}
}

if(chosenList.size() < min) {
// not enough choices
chosenList.clear();
}
return chosenList;
}

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ public static String makeFormatedDescription(SpellAbility sa, boolean includeCho
}
}

if (Boolean.parseBoolean(sa.getParamOrDefault("EachModeMustTargetADifferentPlayer", "false"))) {
sb.append(". Each mode must target a different player.");
}

if (!includeChosen) {
sb.append(num == 1 ? " mode." : " modes.");
} else if (!list.isEmpty()) {
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/b/balor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PT:5/5
K:Flying
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ Whenever CARDNAME attacks or dies, ABILITY
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChoose | Secondary$ True | TriggerDescription$ Whenever CARDNAME attacks or dies, ABILITY
SVar:TrigChoose:DB$ Charm | MinCharmNum$ 1 | CharmNum$ MaxUniqueOpponents | Choices$ TrigDrawDiscard,TrigSacrifice,TrigDmg | AdditionalDescription$ or more. Each mode must target a different player.
SVar:TrigChoose:DB$ Charm | MinCharmNum$ 1 | CharmNum$ MaxUniqueOpponents | Choices$ TrigDrawDiscard,TrigSacrifice,TrigDmg | EachModeMustTargetADifferentPlayer$ True | AdditionalDescription$ or more
SVar:TrigDrawDiscard:DB$ Draw | ValidTgts$ Opponent | TargetUnique$ True | NumCards$ 3 | SubAbility$ Discard3 | SpellDescription$ Target opponent draws three cards, then discards three cards at random.
SVar:Discard3:DB$ Discard | Defined$ ParentTarget | Mode$ Random | NumCards$ 3
SVar:TrigSacrifice:DB$ Sacrifice | ValidTgts$ Opponent | TargetUnique$ True | SacValid$ Artifact.!token | SpellDescription$ Target opponent sacrifices a nontoken artifact. | SacMessage$ nontoken artifact
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/c/casey_raph_hotheads.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ManaCost:4 R
Types:Legendary Creature Mutant Ninja Human Turtle
PT:4/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enter, ABILITY
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBExile,DBToken | AdditionalDescription$ . Each mode must target a different player.
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBExile,DBToken | EachModeMustTargetADifferentPlayer$ True
SVar:DBExile:DB$ Dig | ValidTgts$ Player | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Target player exiles the top card of their library. Until that player's next end step, they may play that card without paying its mana cost.
SVar:DBEffect:DB$ Effect | StaticAbilities$ CRPlay | Duration$ Permanent | ExileOnMoved$ Exile | RememberObjects$ Remembered & RememberedOwner | Triggers$ RemoveTrigger | SubAbility$ DBCleanup
SVar:CRPlay:Mode$ Continuous | Affected$ Card.IsRemembered | MayPlay$ True | MayPlayWithoutManaCost$ True | MayPlayPlayer$ CardOwner | AffectedZone$ Exile | Description$ Until that player's next end step, they may play that card without paying its mana cost.
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/c/chaos_balor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PT:4/5
K:Flying
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ Whenever CARDNAME attacks or dies, ABILITY
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigCharm | Secondary$ True | TriggerDescription$ Whenever CARDNAME attacks or dies, ABILITY
SVar:TrigCharm:DB$ Charm | CharmNum$ 2 | Choices$ DiscardSeek,DamageTreasures,DamagePump | AdditionalDescription$ . Each mode must target a different player.
SVar:TrigCharm:DB$ Charm | CharmNum$ 2 | Choices$ DiscardSeek,DamageTreasures,DamagePump | EachModeMustTargetADifferentPlayer$ True
SVar:DiscardSeek:DB$ Discard | ValidTgts$ Player | TargetUnique$ True | TgtPrompt$ Select target player to discard all the cards in their hand, then seek that many nonland cards | Mode$ Hand | RememberDiscarded$ True | SubAbility$ DBSeek | SpellDescription$ Target player discards all cards in their hand, then seeks that many nonland cards.
SVar:DBSeek:DB$ Seek | Defined$ ParentTarget | Num$ X | Type$ Card.nonLand | SubAbility$ DBCleanup
SVar:DamageTreasures:DB$ DealDamage | ValidTgts$ Player | TargetUnique$ True | TgtPrompt$ Select target player to deal 2 damage to and create two Treasures | NumDmg$ 2 | SubAbility$ DBToken | SpellDescription$ CARDNAME deals 2 damage to target player and they create two Treasure tokens.
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/d/donnie_april_adorkable_duo.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ManaCost:4 U
Types:Legendary Creature Mutant Ninja Human Turtle
PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enter, ABILITY
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBDraw,DBChangeZone | AdditionalDescription$ . Each mode must target a different player.
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBDraw,DBChangeZone | EachModeMustTargetADifferentPlayer$ True
SVar:DBDraw:DB$ Draw | NumCards$ 2 | ValidTgts$ Player | TargetUnique$ True | SpellDescription$ Target player draws two cards.
SVar:DBChangeZone:DB$ ChangeZone | ValidTgts$ Player | TargetUnique$ True | Hidden$ True | Mandatory$ True | ChangeType$ Artifact.TargetedPlayerOwn,Instant.TargetedPlayerOwn,Sorcery.TargetedPlayerOwn | ChangeTypeDesc$ artifact, instant or sorcery | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Target player returns an artifact, instant, or sorcery card from their graveyard to their hand.
Oracle:When Donnie & April enter, choose one or both. Each mode must target a different player.\n• Target player draws two cards.\n• Target player returns an artifact, instant, or sorcery card from their graveyard to their hand.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/m/mikey_mona_mutant_sitters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ManaCost:2 G
Types:Legendary Creature Mutant Ninja Turtle Lizard
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enter, ABILITY
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBPutCounter,DBReturn | AdditionalDescription$ . Each mode must target a different player.
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBPutCounter,DBReturn | EachModeMustTargetADifferentPlayer$ True
SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Player | TgtPrompt$ Select target player to put counters | Choices$ Creature.ControlledBy ThisTargetedPlayer | Chooser$ ThisTargetedPlayer | Placer$ ThisTargetedPlayer | ChoiceTitle$ Choose a creature you control | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Target player chooses a creature they control and puts two +1/+1 counters on it.
SVar:DBReturn:DB$ ChangeZone | ValidTgts$ Player | TgtPrompt$ Select target player to return cards | TargetUnique$ True | Hidden$ True | Mandatory$ True | ChangeType$ Creature.OwnedBy ThisTargetedPlayer,Land.OwnedBy ThisTargetedPlayer | ChangeTypeDesc$ creature or land | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Target player returns a creature or land card from their graveyard to their hand.
Oracle:When Mikey & Mona enter, choose one or both. Each mode must target a different player.\n• Target player chooses a creature they control and puts two +1/+1 counters on it.\n• Target player returns a creature or land card from their graveyard to their hand.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/s/shadrix_silverquill.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PT:2/5
K:Flying
K:Double Strike
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCharm | TriggerDescription$ At the beginning of combat on your turn, you may ABILITY
SVar:TrigCharm:DB$ Charm | CharmNum$ 2 | Optional$ True | Choices$ Token,Card,Counters | AdditionalDescription$ Each mode must target a different player.
SVar:TrigCharm:DB$ Charm | CharmNum$ 2 | Optional$ True | Choices$ Token,Card,Counters | EachModeMustTargetADifferentPlayer$ True
SVar:Token:DB$ Token | ValidTgts$ Player | TargetUnique$ True | TgtPrompt$ Select target player to create a 2/1 white and black inkling creature token with flying | TokenScript$ wb_2_1_inkling_flying | TokenOwner$ ThisTargetedPlayer | SpellDescription$ Target player creates a 2/1 white and black Inkling creature token with flying.
SVar:Card:DB$ Draw | ValidTgts$ Player | TargetUnique$ True | TgtPrompt$ Select target player to draw a card and lose 1 life | SubAbility$ DBLoseLife | SpellDescription$ Target player draws a card and loses 1 life.
SVar:DBLoseLife:DB$ LoseLife | Defined$ ParentTarget | LifeAmount$ 1
Expand Down
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/s/splinter_leo_father_son.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ManaCost:2 W
Types:Legendary Creature Mutant Ninja Rat Turtle
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enter, ABILITY
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBToken,DBPutCounterAll | AdditionalDescription$ . Each mode must target a different player.
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBToken,DBPutCounterAll | EachModeMustTargetADifferentPlayer$ True
SVar:DBToken:DB$ Token | ValidTgts$ Player | TokenOwner$ ThisTargetedPlayer | TokenScript$ r_2_2_mutant | SpellDescription$ Target player creates a 2/2 red Mutant creature token.
SVar:DBPutCounterAll:DB$ PutCounterAll | ValidTgts$ Player | TargetUnique$ True | ValidCards$ Creature.Other | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on each other creature target player controls.
Oracle:When Splinter & Leo enter, choose one or both. Each mode must target a different player.\n• Target player creates a 2/2 red Mutant creature token.\n• Put a +1/+1 counter on each other creature target player controls.
2 changes: 1 addition & 1 deletion forge-gui/res/cardsfolder/v/vindictive_lich.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ManaCost:3 B
Types:Creature Zombie Wizard
PT:4/1
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When CARDNAME dies, ABILITY
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ MaxUniqueOpponents | Choices$ SacCreature,DiscardCards,LoseLife | AdditionalDescription$ or more. Each mode must target a different player.
SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ MaxUniqueOpponents | Choices$ SacCreature,DiscardCards,LoseLife | EachModeMustTargetADifferentPlayer$ True | AdditionalDescription$ or more
SVar:SacCreature:DB$ Sacrifice | ValidTgts$ Opponent | TargetUnique$ True | SacValid$ Creature | SpellDescription$ Target opponent sacrifices a creature.
SVar:DiscardCards:DB$ Discard | ValidTgts$ Opponent | TargetUnique$ True | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Target opponent discards two cards.
SVar:LoseLife:DB$ LoseLife | ValidTgts$ Opponent | TargetUnique$ True | LifeAmount$ 5 | SpellDescription$ Target opponent loses 5 life.
Expand Down