Fix crash after removing target when copying divided spell (#8714)

Co-authored-by: tool4EvEr <tool4EvEr@>
This commit is contained in:
tool4ever
2025-09-15 14:27:21 +02:00
committed by GitHub
parent 10d359e7d7
commit 79fd4a3f8d
10 changed files with 27 additions and 35 deletions

View File

@@ -298,13 +298,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
boolean activateForCost = ComputerUtil.activateForCost(sa, ai); boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (sa.hasParam("Origin")) { if (sa.hasParam("Origin")) {
try {
origin = ZoneType.listValueOf(sa.getParam("Origin")); origin = ZoneType.listValueOf(sa.getParam("Origin"));
} catch (IllegalArgumentException ex) {
// This happens when Origin is something like
// "Graveyard,Library" (Doomsday)
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
} }
final String destination = sa.getParam("Destination"); final String destination = sa.getParam("Destination");

View File

@@ -928,7 +928,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
List<ZoneType> origin = Lists.newArrayList(); List<ZoneType> origin = Lists.newArrayList();
if (sa.hasParam("Origin")) { if (sa.hasParam("Origin")) {
origin = ZoneType.listValueOf(sa.getParam("Origin")); origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
} }
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination")); ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));

View File

@@ -9,6 +9,7 @@ import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CounterType; import forge.game.card.CounterType;
@@ -20,7 +21,6 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.CardTranslation; import forge.util.CardTranslation;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.collect.FCollection;
public class TimeTravelEffect extends SpellAbilityEffect { public class TimeTravelEffect extends SpellAbilityEffect {
@@ -41,10 +41,8 @@ public class TimeTravelEffect extends SpellAbilityEffect {
final CounterType counterType = CounterEnumType.TIME; final CounterType counterType = CounterEnumType.TIME;
for (int i = 0; i < num; i++) { for (int i = 0; i < num; i++) {
FCollection<Card> list = new FCollection<>();
// card you own that is suspended // card you own that is suspended
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Exile), CardPredicates.hasSuspend())); CardCollection list = CardLists.filter(activator.getCardsIn(ZoneType.Exile), CardPredicates.hasSuspend());
// permanent you control with time counter // permanent you control with time counter
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.hasCounter(counterType))); list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.hasCounter(counterType)));

View File

@@ -1120,13 +1120,14 @@ public class Player extends GameEntity implements Comparable<Player> {
getGame().fireEvent(new GameEventSurveil(this, numToTop, numToGrave)); getGame().fireEvent(new GameEventSurveil(this, numToTop, numToGrave));
} }
surveilThisTurn++;
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
runParams.put(AbilityKey.FirstTime, surveilThisTurn == 1); runParams.put(AbilityKey.FirstTime, surveilThisTurn == 0);
if (params != null) { if (params != null) {
runParams.putAll(params); runParams.putAll(params);
} }
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false); getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
surveilThisTurn++;
} }
public int getSurveilThisTurn() { public int getSurveilThisTurn() {

View File

@@ -1,7 +1,6 @@
Name:Footsteps of the Goryo Name:Footsteps of the Goryo
ManaCost:2 B ManaCost:2 B
Types:Sorcery Arcane Types:Sorcery Arcane
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature in your graveyard | GainControl$ True | SubAbility$ DBPump | AILogic$ BeforeCombat | SpellDescription$ Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step. A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature in your graveyard | GainControl$ True | AtEOT$ Sacrifice | AILogic$ BeforeCombat | SpellDescription$ Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step.
SVar:DBPump:DB$ Pump | Defined$ Targeted | AtEOT$ Sacrifice
AI:RemoveDeck:Random AI:RemoveDeck:Random
Oracle:Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step. Oracle:Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step.

View File

@@ -1,11 +1,9 @@
Name:Push the Limit Name:Push the Limit
ManaCost:5 R R ManaCost:5 R R
Types:Sorcery Types:Sorcery
A:SP$ ChangeZoneAll | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Mount.YouOwn,Vehicle.YouOwn | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Return all Mount and Vehicle cards from your graveyard to the battlefield. Sacrifice them at the beginning of the next end step. Vehicles you control become artifact creatures until end of turn. Creatures you control gain haste until end of turn. A:SP$ ChangeZoneAll | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Mount.YouOwn,Vehicle.YouOwn | AtEOT$ Sacrifice | SubAbility$ DBAnimateAll | SpellDescription$ Return all Mount and Vehicle cards from your graveyard to the battlefield. Sacrifice them at the beginning of the next end step. Vehicles you control become artifact creatures until end of turn. Creatures you control gain haste until end of turn.
SVar:DBPump:DB$ Pump | Defined$ Remembered | AtEOT$ Sacrifice | SubAbility$ DBAnimateAll
SVar:DBAnimateAll:DB$ AnimateAll | Types$ Artifact,Creature | ValidCards$ Vehicle.YouCtrl | SubAbility$ DBPumpAll SVar:DBAnimateAll:DB$ AnimateAll | Types$ Artifact,Creature | ValidCards$ Vehicle.YouCtrl | SubAbility$ DBPumpAll
SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Haste | SubAbility$ DBCleanup SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Haste
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:PlayMain1:TRUE SVar:PlayMain1:TRUE
DeckHas:Ability$Graveyard DeckHas:Ability$Graveyard
DeckHints:Ability$Discard|Sacrifice DeckHints:Ability$Discard|Sacrifice

View File

@@ -2,8 +2,7 @@ Name:Wake the Dead
ManaCost:X B B ManaCost:X B B
Types:Instant Types:Instant
Text:Cast this spell only during combat on an opponent's turn. Text:Cast this spell only during combat on an opponent's turn.
A:SP$ ChangeZone | TargetMin$ X | TargetMax$ X | OpponentTurn$ True | ActivationPhases$ BeginCombat->EndCombat | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select X target creatures in your graveyard | GainControl$ True | SubAbility$ DBPump | StackDescription$ Return X target creature cards [{c:Targeted}] from your graveyard to the battlefield. Sacrifice those creatures at the beginning of the next end step. | SpellDescription$ Return X target creature cards from your graveyard to the battlefield. Sacrifice those creatures at the beginning of the next end step. A:SP$ ChangeZone | TargetMin$ X | TargetMax$ X | OpponentTurn$ True | ActivationPhases$ BeginCombat->EndCombat | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select X target creatures in your graveyard | GainControl$ True | AtEOT$ Sacrifice | StackDescription$ Return X target creature cards [{c:Targeted}] from your graveyard to the battlefield. Sacrifice those creatures at the beginning of the next end step. | SpellDescription$ Return X target creature cards from your graveyard to the battlefield. Sacrifice those creatures at the beginning of the next end step.
SVar:DBPump:DB$ Pump | Defined$ Targeted | AtEOT$ Sacrifice
SVar:X:Count$xPaid SVar:X:Count$xPaid
AI:RemoveDeck:All AI:RemoveDeck:All
DeckHas:Ability$Graveyard|Sacrifice DeckHas:Ability$Graveyard|Sacrifice

View File

@@ -408,15 +408,18 @@ public final class InputSelectTargets extends InputSyncronizedBase {
} }
private void removeTarget(final GameEntity ge) { private void removeTarget(final GameEntity ge) {
if (divisionValues != null) {
divisionValues.add(sa.getDividedValue(ge));
}
targets.remove(ge); targets.remove(ge);
sa.getTargets().remove(ge); sa.getTargets().remove(ge);
if (ge instanceof Card) { if (ge instanceof Card c) {
getController().getGui().setUsedToPay(CardView.get((Card) ge), false); getController().getGui().setUsedToPay(CardView.get(c), false);
// try to get last selected card // try to get last selected card
lastTarget = Iterables.getLast(IterableUtil.filter(targets, Card.class), null); lastTarget = Iterables.getLast(IterableUtil.filter(targets, Card.class), null);
} }
else if (ge instanceof Player) { else if (ge instanceof Player p) {
getController().getGui().setHighlighted(PlayerView.get((Player) ge), false); getController().getGui().setHighlighted(PlayerView.get(p), false);
} }
this.showMessage(); this.showMessage();