mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Fix crash after removing target when copying divided spell (#8714)
Co-authored-by: tool4EvEr <tool4EvEr@>
This commit is contained in:
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
final StringBuilder sb = new StringBuilder("Airbend ");
|
final StringBuilder sb = new StringBuilder("Airbend ");
|
||||||
|
|
||||||
Iterable<Card> tgts;
|
Iterable<Card> tgts;
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
tgts = getCardsfromTargets(sa);
|
tgts = getCardsfromTargets(sa);
|
||||||
} else { // otherwise add self to list and go from there
|
} else { // otherwise add self to list and go from there
|
||||||
tgts = sa.knownDetermineDefined(sa.getParam("Defined"));
|
tgts = sa.knownDetermineDefined(sa.getParam("Defined"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append(sa.getParamOrDefault("DefinedDesc", Lang.joinHomogenous(tgts)));
|
sb.append(sa.getParamOrDefault("DefinedDesc", Lang.joinHomogenous(tgts)));
|
||||||
sb.append(".");
|
sb.append(".");
|
||||||
if (Iterables.size(tgts) > 1) {
|
if (Iterables.size(tgts) > 1) {
|
||||||
@@ -46,7 +46,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
final Player pl = sa.getActivatingPlayer();
|
final Player pl = sa.getActivatingPlayer();
|
||||||
|
|
||||||
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
|
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
|
||||||
|
|
||||||
for (Card c : getTargetCards(sa)) {
|
for (Card c : getTargetCards(sa)) {
|
||||||
final Card gameCard = game.getCardState(c, null);
|
final Card gameCard = game.getCardState(c, null);
|
||||||
// gameCard is LKI in that case, the card is not in game anymore
|
// gameCard is LKI in that case, the card is not in game anymore
|
||||||
@@ -55,7 +55,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gameCard.canExiledBy(sa, true)) {
|
if (!gameCard.canExiledBy(sa, true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
handleExiledWith(triggerList.allCards(), sa);
|
handleExiledWith(triggerList.allCards(), sa);
|
||||||
|
|
||||||
pl.triggerElementalBend(TriggerType.Airbend);
|
pl.triggerElementalBend(TriggerType.Airbend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class EarthbendEffect extends SpellAbilityEffect {
|
|||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
final Player pl = sa.getActivatingPlayer();
|
final Player pl = sa.getActivatingPlayer();
|
||||||
int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("Num", "1"), sa);
|
int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("Num", "1"), sa);
|
||||||
|
|
||||||
long ts = game.getNextTimestamp();
|
long ts = game.getNextTimestamp();
|
||||||
|
|
||||||
String desc = "When it dies or is exiled, return it to the battlefield tapped.";
|
String desc = "When it dies or is exiled, return it to the battlefield tapped.";
|
||||||
@@ -59,17 +59,17 @@ public class EarthbendEffect extends SpellAbilityEffect {
|
|||||||
c.addNewPT(0, 0, ts, 0);
|
c.addNewPT(0, 0, ts, 0);
|
||||||
c.addChangedCardTypes(Arrays.asList("Creature"), null, false, EnumSet.noneOf(RemoveType.class), ts, 0, true, false);
|
c.addChangedCardTypes(Arrays.asList("Creature"), null, false, EnumSet.noneOf(RemoveType.class), ts, 0, true, false);
|
||||||
c.addChangedCardKeywords(Arrays.asList("Haste"), null, false, ts, null);
|
c.addChangedCardKeywords(Arrays.asList("Haste"), null, false, ts, null);
|
||||||
|
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
c.addCounter(CounterEnumType.P1P1, num, pl, table);
|
c.addCounter(CounterEnumType.P1P1, num, pl, table);
|
||||||
table.replaceCounterEffect(game, sa, true);
|
table.replaceCounterEffect(game, sa, true);
|
||||||
|
|
||||||
buildTrigger(sa, c, sbTrigA, "Graveyard");
|
buildTrigger(sa, c, sbTrigA, "Graveyard");
|
||||||
buildTrigger(sa, c, sbTrigB, "Exile");
|
buildTrigger(sa, c, sbTrigB, "Exile");
|
||||||
}
|
}
|
||||||
pl.triggerElementalBend(TriggerType.Earthbend);
|
pl.triggerElementalBend(TriggerType.Earthbend);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void buildTrigger(SpellAbility sa, Card c, String sbTrig, String zone) {
|
protected void buildTrigger(SpellAbility sa, Card c, String sbTrig, String zone) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user