mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Fix remembering players leading to crash for AI with Shifting Shadow (#2587)
Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
This commit is contained in:
@@ -2492,12 +2492,12 @@ public class ComputerUtilCombat {
|
||||
return poison;
|
||||
}
|
||||
|
||||
public static GameEntity addAttackerToCombat(SpellAbility sa, Card attacker, FCollection<GameEntity> defenders) {
|
||||
public static GameEntity addAttackerToCombat(SpellAbility sa, Card attacker, Iterable<? extends GameEntity> defenders) {
|
||||
Combat combat = sa.getHostCard().getGame().getCombat();
|
||||
if (combat != null) {
|
||||
// 1. If the card that spawned the attacker was sent at a planeswalker, attack the same. Consider improving.
|
||||
GameEntity def = combat.getDefenderByAttacker(sa.getHostCard());
|
||||
if (def instanceof Card && ((Card)def).isPlaneswalker() && defenders.contains(def)) {
|
||||
if (def instanceof Card && ((Card)def).isPlaneswalker() && Iterables.contains(defenders, def)) {
|
||||
return def;
|
||||
}
|
||||
// 2. Otherwise, go through the list of options one by one, choose the first one that can't be blocked profitably.
|
||||
|
||||
@@ -33,7 +33,6 @@ import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
@@ -1759,7 +1758,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
// Called when attaching Aura to player or adding creature to combat
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
return AttachAi.attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options);
|
||||
}
|
||||
@@ -1767,7 +1766,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
// should not be reached
|
||||
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
|
||||
|
||||
@@ -34,7 +34,6 @@ import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
public class CopyPermanentAi extends SpellAbilityAi {
|
||||
@Override
|
||||
@@ -254,7 +253,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
@@ -264,7 +263,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
// should not be reached
|
||||
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
|
||||
|
||||
@@ -27,7 +27,6 @@ import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
|
||||
public class DigAi extends SpellAbilityAi {
|
||||
@@ -190,7 +189,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
// an opponent choose a card from
|
||||
return Iterables.getFirst(options, null);
|
||||
@@ -199,7 +198,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
// should not be reached
|
||||
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
|
||||
|
||||
@@ -40,7 +40,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -321,7 +320,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
@@ -332,7 +331,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
if (params != null && params.containsKey("Attacker")) {
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), options);
|
||||
}
|
||||
// should not be reached
|
||||
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
|
||||
|
||||
@@ -288,12 +288,12 @@ public class AbilityUtils {
|
||||
} else {
|
||||
System.err.println("Warning: couldn't find trigger SA in the chain of SpellAbility " + sa);
|
||||
}
|
||||
} else if (defined.equals("FirstRemembered")) {
|
||||
} else if (defined.equals("RememberedFirst")) {
|
||||
Object o = hostCard.getFirstRemembered();
|
||||
if (o instanceof Card) {
|
||||
cards.add(game.getCardState((Card) o));
|
||||
}
|
||||
} else if (defined.equals("LastRemembered")) {
|
||||
} else if (defined.equals("RememberedLast")) {
|
||||
Object o = Iterables.getLast(hostCard.getRemembered(), null);
|
||||
if (o instanceof Card) {
|
||||
cards.add(game.getCardState((Card) o));
|
||||
|
||||
@@ -2,12 +2,12 @@ Name:Mangara's Tome
|
||||
ManaCost:5
|
||||
Types:Artifact
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When CARDNAME enters the battlefield, search your library for five cards, exile them in a face-down pile, and shuffle that pile. Then shuffle your library.
|
||||
SVar:TrigSearch:DB$ ChangeZone | ChangeNum$ 5 | ChangeType$ Card | Origin$ Library | Destination$ Exile | ShuffleChangedPile$ True | ExileFaceDown$ True | RememberChanged$ True
|
||||
SVar:TrigSearch:DB$ ChangeZone | ChangeNum$ 5 | Mandatory$ True | ChangeType$ Card | Origin$ Library | Destination$ Exile | ShuffleChangedPile$ True | ExileFaceDown$ True | RememberChanged$ True
|
||||
T:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | Static$ True | Execute$ TrigForget
|
||||
SVar:TrigForget:DB$ Pump | ForgetObjects$ TriggeredCard
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReset | Static$ True
|
||||
SVar:TrigReset:DB$ Cleanup | ClearRemembered$ True
|
||||
A:AB$ Effect | Cost$ 2 | RememberObjects$ FirstRemembered | Triggers$ TrigLeavePlay | ReplacementEffects$ DrawReplace | ImprintCards$ Self | SpellDescription$ The next time you would draw a card this turn, instead put the top card of the exiled pile into its owner's hand.
|
||||
A:AB$ Effect | Cost$ 2 | RememberObjects$ RememberedFirst | Triggers$ TrigLeavePlay | ReplacementEffects$ DrawReplace | ImprintCards$ Self | SpellDescription$ The next time you would draw a card this turn, instead put the top card of the exiled pile into its owner's hand.
|
||||
SVar:DrawReplace:Event$ Draw | ValidPlayer$ You | ReplaceWith$ RepMangarasTome | Description$ The next time you would draw a card this turn, instead put the top card of the exiled pile into its owner's hand.
|
||||
SVar:TrigLeavePlay:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ ExileEffect | Static$ True
|
||||
SVar:RepMangarasTome:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ ExileEffect
|
||||
|
||||
@@ -2,13 +2,13 @@ Name:Parallel Thoughts
|
||||
ManaCost:3 U U
|
||||
Types:Enchantment
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When CARDNAME enters the battlefield, search your library for seven cards, exile them in a face-down pile, and shuffle that pile. Then shuffle your library.
|
||||
SVar:TrigSearch:DB$ ChangeZone | ChangeNum$ 7 | ChangeType$ Card | Origin$ Library | Destination$ Exile | ShuffleChangedPile$ True | ExileFaceDown$ True | RememberChanged$ True
|
||||
SVar:TrigSearch:DB$ ChangeZone | ChangeNum$ 7 | ChangeType$ Card | Mandatory$ True | Origin$ Library | Destination$ Exile | ShuffleChangedPile$ True | ExileFaceDown$ True | RememberChanged$ True
|
||||
T:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | Static$ True | Execute$ TrigForget
|
||||
SVar:TrigForget:DB$ Pump | ForgetObjects$ TriggeredCard
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReset | Static$ True
|
||||
SVar:TrigReset:DB$ Cleanup | ClearRemembered$ True
|
||||
R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ You | ReplaceWith$ RepParallelThoughts | Optional$ True | OptionalDecider$ You | Description$ If you would draw a card, you may instead put the top card of the pile you exiled into your hand.
|
||||
SVar:RepParallelThoughts:DB$ ChangeZone | Defined$ FirstRemembered | Origin$ Exile | Destination$ Hand
|
||||
SVar:RepParallelThoughts:DB$ ChangeZone | Defined$ RememberedFirst | Origin$ Exile | Destination$ Hand
|
||||
AI:RemoveDeck:All
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:When Parallel Thoughts enters the battlefield, search your library for seven cards, exile them in a face-down pile, and shuffle that pile. Then shuffle your library.\nIf you would draw a card, you may instead put the top card of the pile you exiled into your hand.
|
||||
|
||||
@@ -10,6 +10,6 @@ SVar:ShadowUpkeepTrig:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZo
|
||||
SVar:ShadowRememberSelf:DB$ Pump | ImprintCards$ OriginalHost | SubAbility$ ShadowDestroyEnchanted
|
||||
SVar:ShadowDestroyEnchanted:DB$ Destroy | Defined$ Self | SubAbility$ ShadowRevealCards
|
||||
SVar:ShadowRevealCards:DB$ DigUntil | Valid$ Creature | ValidDescription$ creature | FoundDestination$ Battlefield | RevealedDestination$ Library | RevealedLibraryPosition$ -1 | RevealRandomOrder$ True | RememberFound$ True | SubAbility$ ShadowReattach
|
||||
SVar:ShadowReattach:DB$ Attach | Defined$ LastRemembered | Object$ Imprinted | SubAbility$ DBCleanup
|
||||
SVar:ShadowReattach:DB$ Attach | Defined$ RememberedLast | Object$ Imprinted | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
|
||||
Oracle:Enchant creature\nEnchanted creature has haste and "At the beginning of your upkeep, destroy this creature. Reveal cards from the top of your library until you reveal a creature card. Put that card onto the battlefield and attach Shifting Shadow to it, then put all other cards revealed this way on the bottom of your library in a random order."
|
||||
|
||||
@@ -359,7 +359,7 @@ public final class CardScriptParser {
|
||||
"Self", "OriginalHost", "EffectSource", "Equipped", "Enchanted",
|
||||
"TopOfLibrary", "BottomOfLibrary", "Targeted", "ThisTargetedCard",
|
||||
"ParentTarget", "Remembered", "DirectRemembered",
|
||||
"DelayTriggerRemembered", "FirstRemembered", "Clones", "Imprinted",
|
||||
"DelayTriggerRemembered", "RememberedFirst", "Clones", "Imprinted",
|
||||
"ChosenCard", "SacrificedCards", "Sacrificed", "DiscardedCards",
|
||||
"Discarded", "ExiledCards", "Exiled", "TappedCards", "Tapped",
|
||||
"UntappedCards", "Untapped", "Parent", "SourceFirstSpell");
|
||||
|
||||
@@ -743,14 +743,14 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
Card show = null;
|
||||
Object o = null;
|
||||
switch (sa.getParam("ShowCardInPrompt")) {
|
||||
case "FirstRemembered":
|
||||
case "RememberedFirst":
|
||||
o = sa.getHostCard().getFirstRemembered();
|
||||
if (o instanceof Card) {
|
||||
show = (Card) o;
|
||||
}
|
||||
break;
|
||||
case "LastRemembered":
|
||||
o = sa.getHostCard().getFirstRemembered();
|
||||
case "RememberedLast":
|
||||
o = Iterables.getLast(sa.getHostCard().getRemembered(), null);
|
||||
if (o instanceof Card) {
|
||||
show = (Card) o;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user