refactor: improved search in stack

This commit is contained in:
Oleg Agafonov 2025-08-10 02:07:15 +04:00
parent 26adccdfd5
commit 384ce67cc3
20 changed files with 101 additions and 119 deletions

View file

@ -398,7 +398,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
} }
protected void resolve(SimulationNode2 node, int depth, Game game) { protected void resolve(SimulationNode2 node, int depth, Game game) {
StackObject stackObject = game.getStack().getFirst(); StackObject stackObject = game.getStack().getFirstOrNull();
if (stackObject == null) {
throw new IllegalStateException("Catch empty stack on resolve (something wrong with sim code)");
}
if (stackObject instanceof StackAbility) { if (stackObject instanceof StackAbility) {
// AI hint for search effects (calc all possible cards for best score) // AI hint for search effects (calc all possible cards for best score)
SearchEffect effect = getSearchEffect((StackAbility) stackObject); SearchEffect effect = getSearchEffect((StackAbility) stackObject);

View file

@ -638,8 +638,8 @@ public class HumanPlayer extends PlayerImpl {
// Check check if the spell being paid for cares about the color of mana being paid // Check check if the spell being paid for cares about the color of mana being paid
// See: https://github.com/magefree/mage/issues/9070 // See: https://github.com/magefree/mage/issues/9070
boolean caresAboutManaColor = false; boolean caresAboutManaColor = false;
if (!game.getStack().isEmpty() && game.getStack().getFirst() instanceof Spell) { if (game.getStack().getFirstOrNull() instanceof Spell) {
Spell spellBeingCast = (Spell) game.getStack().getFirst(); Spell spellBeingCast = (Spell) game.getStack().getFirstOrNull();
if (!spellBeingCast.isResolving() && spellBeingCast.getControllerId().equals(this.getId())) { if (!spellBeingCast.isResolving() && spellBeingCast.getControllerId().equals(this.getId())) {
CardImpl card = (CardImpl) game.getCard(spellBeingCast.getSourceId()); CardImpl card = (CardImpl) game.getCard(spellBeingCast.getSourceId());
caresAboutManaColor = card.caresAboutManaColor(game); caresAboutManaColor = card.caresAboutManaColor(game);

View file

@ -68,10 +68,7 @@ class AstralDriftTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
if (game.getState().getStack().isEmpty()) { StackObject item = game.getState().getStack().getFirstOrNull();
return false;
}
StackObject item = game.getState().getStack().getFirst();
if (!(item instanceof StackAbility if (!(item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility)) { && item.getStackAbility() instanceof CyclingAbility)) {
return false; return false;

View file

@ -78,11 +78,9 @@ class CobraTrapWatcher extends Watcher {
if (event.getType() == GameEvent.EventType.DESTROYED_PERMANENT) { if (event.getType() == GameEvent.EventType.DESTROYED_PERMANENT) {
Permanent perm = game.getPermanentOrLKIBattlefield(event.getTargetId()); // can regenerate or be indestructible Permanent perm = game.getPermanentOrLKIBattlefield(event.getTargetId()); // can regenerate or be indestructible
if (perm != null && !perm.isCreature(game)) { if (perm != null && !perm.isCreature(game)) {
if (!game.getStack().isEmpty()) { StackObject spell = game.getStack().getStackObject(event.getSourceId());
StackObject spell = game.getStack().getStackObject(event.getSourceId()); if (spell != null && game.getOpponents(perm.getControllerId()).contains(spell.getControllerId())) {
if (spell != null && game.getOpponents(perm.getControllerId()).contains(spell.getControllerId())) { players.add(perm.getControllerId());
players.add(perm.getControllerId());
}
} }
} }
} }

View file

@ -1,7 +1,5 @@
package mage.cards.r; package mage.cards.r;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.ReplacementEffectImpl;
@ -10,20 +8,20 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.stack.StackObject; import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class RainOfGore extends CardImpl { public final class RainOfGore extends CardImpl {
public RainOfGore(UUID ownerId, CardSetInfo setInfo) { public RainOfGore(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{B}{R}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{R}");
// If a spell or ability would cause its controller to gain life, that player loses that much life instead. // If a spell or ability would cause its controller to gain life, that player loses that much life instead.
@ -71,13 +69,10 @@ class RainOfGoreEffect extends ReplacementEffectImpl {
public boolean checksEventType(GameEvent event, Game game) { public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.GAIN_LIFE; return event.getType() == GameEvent.EventType.GAIN_LIFE;
} }
@Override @Override
public boolean applies(GameEvent event, Ability source, Game game) { public boolean applies(GameEvent event, Ability source, Game game) {
if (!game.getStack().isEmpty()) { StackObject stackObject = game.getStack().getFirstOrNull();
StackObject stackObject = game.getStack().getFirst(); return stackObject != null && stackObject.isControlledBy(event.getPlayerId());
return stackObject.isControlledBy(event.getPlayerId());
}
return false;
} }
} }

View file

@ -120,13 +120,8 @@ enum SoulSculptorCondition implements Condition {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
if (!game.getStack().isEmpty()) { StackObject stackObject = game.getStack().getFirstOrNull();
StackObject stackObject = game.getStack().getFirst(); return stackObject != null && !stackObject.getCardType(game).contains(CardType.CREATURE);
if (stackObject != null) {
return !stackObject.getCardType(game).contains(CardType.CREATURE);
}
}
return true;
} }
@Override @Override

View file

@ -21,6 +21,7 @@ import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent; import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import java.util.UUID; import java.util.UUID;
@ -94,18 +95,22 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect
@Override @Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) { public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Spell spell = (Spell) game.getStack().getFirst(); StackObject stackObject = game.getStack().getFirstOrNull();
if (!spell.isCopy() && !spell.isCountered()) { if (stackObject instanceof Spell) {
Card sourceCard = game.getCard(spellId); Spell spell = (Spell) stackObject;
if (sourceCard != null && Zone.STACK.equals(game.getState().getZone(spellId))) { if (!spell.isCopy() && !spell.isCountered()) {
Player player = game.getPlayer(sourceCard.getOwnerId()); Card sourceCard = game.getCard(spellId);
if (player != null) { if (sourceCard != null && Zone.STACK.equals(game.getState().getZone(spellId))) {
player.moveCards(sourceCard, Zone.HAND, source, game); Player player = game.getPlayer(sourceCard.getOwnerId());
discard(); if (player != null) {
return true; player.moveCards(sourceCard, Zone.HAND, source, game);
discard();
return true;
}
} }
} }
} }
return false; return false;
} }
@ -134,8 +139,8 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect
if (zEvent.getFromZone() == Zone.STACK if (zEvent.getFromZone() == Zone.STACK
&& zEvent.getToZone() == Zone.GRAVEYARD && zEvent.getToZone() == Zone.GRAVEYARD
&& event.getTargetId().equals(spellId)) { && event.getTargetId().equals(spellId)) {
if (game.getStack().getFirst() instanceof Spell) { if (game.getStack().getFirstOrNull() instanceof Spell) {
Card cardOfSpell = ((Spell) game.getStack().getFirst()).getCard(); Card cardOfSpell = ((Spell) game.getStack().getFirstOrNull()).getCard();
return cardOfSpell.getMainCard().getId().equals(spellId); return cardOfSpell.getMainCard().getId().equals(spellId);
} }
} }

View file

@ -99,12 +99,10 @@ enum FirstSpellCastFromNotHandEachTurnCondition implements Condition {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
if (game.getStack().isEmpty()) {
return false;
}
TheTwelfthDoctorWatcher watcher = game.getState().getWatcher(TheTwelfthDoctorWatcher.class); TheTwelfthDoctorWatcher watcher = game.getState().getWatcher(TheTwelfthDoctorWatcher.class);
StackObject so = game.getStack().getFirst(); StackObject so = game.getStack().getFirstOrNull();
return watcher != null return so != null
&& watcher != null
&& TheTwelfthDoctorWatcher.checkSpell(so, game); && TheTwelfthDoctorWatcher.checkSpell(so, game);
} }
} }

View file

@ -74,12 +74,11 @@ class ValiantRescuerTriggeredAbility extends TriggeredAbilityImpl {
ValiantRescuerWatcher watcher = game.getState().getWatcher(ValiantRescuerWatcher.class); ValiantRescuerWatcher watcher = game.getState().getWatcher(ValiantRescuerWatcher.class);
if (watcher == null if (watcher == null
|| !watcher.checkSpell(event.getPlayerId(), event.getSourceId()) || !watcher.checkSpell(event.getPlayerId(), event.getSourceId())
|| game.getState().getStack().isEmpty()
|| !event.getPlayerId().equals(this.getControllerId()) || !event.getPlayerId().equals(this.getControllerId())
|| event.getSourceId().equals(this.getSourceId())) { || event.getSourceId().equals(this.getSourceId())) {
return false; return false;
} }
StackObject item = game.getState().getStack().getFirst(); StackObject item = game.getState().getStack().getFirstOrNull();
return item instanceof StackAbility return item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility; && item.getStackAbility() instanceof CyclingAbility;
} }
@ -106,11 +105,10 @@ class ValiantRescuerWatcher extends Watcher {
@Override @Override
public void watch(GameEvent event, Game game) { public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.ACTIVATED_ABILITY if (event.getType() != GameEvent.EventType.ACTIVATED_ABILITY) {
|| game.getState().getStack().isEmpty()) {
return; return;
} }
StackObject item = game.getState().getStack().getFirst(); StackObject item = game.getState().getStack().getFirstOrNull();
if (item instanceof StackAbility if (item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility) { && item.getStackAbility() instanceof CyclingAbility) {
playerMap.computeIfAbsent(event.getPlayerId(), u -> new HashMap<>()); playerMap.computeIfAbsent(event.getPlayerId(), u -> new HashMap<>());

View file

@ -2,7 +2,10 @@ package mage.cards.w;
import mage.MageInt; import mage.MageInt;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.CascadeAbility; import mage.abilities.keyword.CascadeAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
@ -11,14 +14,12 @@ import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.game.stack.StackObject; import mage.game.stack.StackObject;
import mage.players.Player;
import mage.watchers.Watcher; import mage.watchers.Watcher;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.players.Player;
/** /**
* @author TheElk801 * @author TheElk801
@ -35,7 +36,7 @@ public final class WildMagicSorcerer extends CardImpl {
// The first spell you cast from exile each turn has cascade. // The first spell you cast from exile each turn has cascade.
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility(
new WildMagicSorcererGainCascadeFirstSpellCastFromExileEffect()), new WildMagicSorcererGainCascadeFirstSpellCastFromExileEffect()),
new WildMagicSorcererWatcher()); new WildMagicSorcererWatcher());
} }
@ -96,12 +97,10 @@ enum FirstSpellCastFromExileEachTurnCondition implements Condition {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
if (game.getStack().isEmpty()) {
return false;
}
WildMagicSorcererWatcher watcher = game.getState().getWatcher(WildMagicSorcererWatcher.class); WildMagicSorcererWatcher watcher = game.getState().getWatcher(WildMagicSorcererWatcher.class);
StackObject so = game.getStack().getFirst(); StackObject so = game.getStack().getFirstOrNull();
return watcher != null return so != null
&& watcher != null
&& WildMagicSorcererWatcher.checkSpell(so, game); && WildMagicSorcererWatcher.checkSpell(so, game);
} }
} }

View file

@ -55,7 +55,7 @@ public class DisguiseTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dog Walker using Disguise"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dog Walker using Disguise");
runCode("face up on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { runCode("face up on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Assert.assertEquals("stack, server - can't find spell", 1, currentGame.getStack().size()); Assert.assertEquals("stack, server - can't find spell", 1, currentGame.getStack().size());
SpellAbility spellAbility = (SpellAbility) currentGame.getStack().getFirst().getStackAbility(); SpellAbility spellAbility = (SpellAbility) currentGame.getStack().getFirstOrNull().getStackAbility();
Assert.assertEquals("stack, server - can't find spell", "Cast Dog Walker using Disguise", spellAbility.getName()); Assert.assertEquals("stack, server - can't find spell", "Cast Dog Walker using Disguise", spellAbility.getName());
CardView spellView = getGameView(playerA).getStack().values().stream().findFirst().orElse(null); CardView spellView = getGameView(playerA).getStack().values().stream().findFirst().orElse(null);
Assert.assertNotNull("stack, client: can't find spell", spellView); Assert.assertNotNull("stack, client: can't find spell", spellView);

View file

@ -40,7 +40,7 @@ public class DisturbTest extends CardTestPlayerBase {
checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb", 1); checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb", 1);
runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
// Stack must contain another card side, so spell/card characteristics must be diff from main side (only mana value is same) // Stack must contain another card side, so spell/card characteristics must be diff from main side (only mana value is same)
Spell spell = (Spell) game.getStack().getFirst(); Spell spell = (Spell) game.getStack().getFirstOrNull();
Assert.assertEquals("Hook-Haunt Drifter", spell.getName()); Assert.assertEquals("Hook-Haunt Drifter", spell.getName());
Assert.assertEquals(1, spell.getCardType(game).size()); Assert.assertEquals(1, spell.getCardType(game).size());
Assert.assertEquals(CardType.CREATURE, spell.getCardType(game).get(0)); Assert.assertEquals(CardType.CREATURE, spell.getCardType(game).get(0));
@ -91,7 +91,7 @@ public class DisturbTest extends CardTestPlayerBase {
checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Waildrifter using Disturb", 1); checkStackObject("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Waildrifter using Disturb", 1);
runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
// Stack must contain another card side, so spell/card characteristics must be diff from main side (only mana value is same) // Stack must contain another card side, so spell/card characteristics must be diff from main side (only mana value is same)
Spell spell = (Spell) game.getStack().getFirst(); Spell spell = (Spell) game.getStack().getFirstOrNull();
Assert.assertEquals("Waildrifter", spell.getName()); Assert.assertEquals("Waildrifter", spell.getName());
Assert.assertEquals(1, spell.getCardType(game).size()); Assert.assertEquals(1, spell.getCardType(game).size());
Assert.assertEquals(CardType.CREATURE, spell.getCardType(game).get(0)); Assert.assertEquals(CardType.CREATURE, spell.getCardType(game).get(0));
@ -187,7 +187,7 @@ public class DisturbTest extends CardTestPlayerBase {
// cast with disturb // cast with disturb
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb");
runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { runCode("check stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
Spell spell = (Spell) game.getStack().getFirst(); Spell spell = (Spell) game.getStack().getFirstOrNull();
Assert.assertEquals("mana value must be from main side", 2, spell.getManaValue()); Assert.assertEquals("mana value must be from main side", 2, spell.getManaValue());
Assert.assertEquals("mana cost to pay must be modified", "{U}", spell.getSpellAbility().getManaCostsToPay().getText()); Assert.assertEquals("mana cost to pay must be modified", "{U}", spell.getSpellAbility().getManaCostsToPay().getText());
}); });

View file

@ -334,11 +334,8 @@ public class TestPlayer implements Player {
return true; return true;
} else if (groups[2].startsWith("spellOnTopOfStack=")) { } else if (groups[2].startsWith("spellOnTopOfStack=")) {
String spellOnTopOFStack = groups[2].substring(18); String spellOnTopOFStack = groups[2].substring(18);
if (!game.getStack().isEmpty()) { StackObject stackObject = game.getStack().getFirstOrNull();
StackObject stackObject = game.getStack().getFirst(); return stackObject != null && stackObject.getStackAbility().toString().contains(spellOnTopOFStack);
return stackObject != null && stackObject.getStackAbility().toString().contains(spellOnTopOFStack);
}
return false;
} else if (groups[2].startsWith("manaInPool=")) { } else if (groups[2].startsWith("manaInPool=")) {
String manaInPool = groups[2].substring(11); String manaInPool = groups[2].substring(11);
int amountOfMana = Integer.parseInt(manaInPool); int amountOfMana = Integer.parseInt(manaInPool);

View file

@ -53,11 +53,9 @@ public abstract class SpecialAction extends ActivatedAbilityImpl {
if (isManaAction()) { if (isManaAction()) {
// limit play mana abilities by steps // limit play mana abilities by steps
int currentStepOrder = 0; int currentStepOrder = 0;
if (!game.getStack().isEmpty()) { StackObject stackObject = game.getStack().getFirstOrNull();
StackObject stackObject = game.getStack().getFirst(); if (stackObject instanceof Spell) {
if (stackObject instanceof Spell) { currentStepOrder = ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep().getStepOrder();
currentStepOrder = ((Spell) stackObject).getCurrentActivatingManaAbilitiesStep().getStepOrder();
}
} }
if (currentStepOrder > manaAbility.useOnActivationManaAbilityStep().getStepOrder()) { if (currentStepOrder > manaAbility.useOnActivationManaAbilityStep().getStepOrder()) {
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();

View file

@ -31,10 +31,7 @@ public class CycleAllTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
if (game.getState().getStack().isEmpty()) { StackObject item = game.getState().getStack().getFirstOrNull();
return false;
}
StackObject item = game.getState().getStack().getFirst();
return item instanceof StackAbility return item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility; && item.getStackAbility() instanceof CyclingAbility;
} }

View file

@ -42,12 +42,11 @@ public class CycleControllerTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
if (game.getState().getStack().isEmpty() if (!event.getPlayerId().equals(this.getControllerId())
|| !event.getPlayerId().equals(this.getControllerId())
|| (event.getSourceId().equals(this.getSourceId()) && excludeSource)) { || (event.getSourceId().equals(this.getSourceId()) && excludeSource)) {
return false; return false;
} }
StackObject item = game.getState().getStack().getFirst(); StackObject item = game.getState().getStack().getFirstOrNull();
return item instanceof StackAbility return item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility; && item.getStackAbility() instanceof CyclingAbility;
} }

View file

@ -18,25 +18,24 @@ public enum SunburstCount implements DynamicValue {
@Override @Override
public int calculate(Game game, Ability sourceAbility, Effect effect) { public int calculate(Game game, Ability sourceAbility, Effect effect) {
int count = 0; int count = 0;
if (!game.getStack().isEmpty()) {
StackObject spell = game.getStack().getFirst(); StackObject spell = game.getStack().getFirstOrNull();
if (spell instanceof Spell && ((Spell) spell).getSourceId().equals(sourceAbility.getSourceId())) { if (spell instanceof Spell && spell.getSourceId().equals(sourceAbility.getSourceId())) {
Mana mana = ((Spell) spell).getSpellAbility().getManaCostsToPay().getUsedManaToPay(); Mana mana = ((Spell) spell).getSpellAbility().getManaCostsToPay().getUsedManaToPay();
if (mana.getBlack() > 0) { if (mana.getBlack() > 0) {
count++; count++;
} }
if (mana.getBlue() > 0) { if (mana.getBlue() > 0) {
count++; count++;
} }
if (mana.getGreen() > 0) { if (mana.getGreen() > 0) {
count++; count++;
} }
if (mana.getRed() > 0) { if (mana.getRed() > 0) {
count++; count++;
} }
if (mana.getWhite() > 0) { if (mana.getWhite() > 0) {
count++; count++;
}
} }
} }
return count; return count;

View file

@ -513,13 +513,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
// If the top card of your library changes while youre casting a spell, playing a land, or activating an ability, // If the top card of your library changes while youre casting a spell, playing a land, or activating an ability,
// you cant look at the new top card until you finish doing so. This means that if you cast the top card of // you cant look at the new top card until you finish doing so. This means that if you cast the top card of
// your library, you cant look at the next one until youre done paying for that spell. (2019-05-03) // your library, you cant look at the next one until youre done paying for that spell. (2019-05-03)
if (!game.getStack().isEmpty()) { StackObject stackObject = game.getStack().getFirstOrNull();
StackObject stackObject = game.getStack().getFirst(); return !(stackObject instanceof Spell)
return !(stackObject instanceof Spell) || !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone())
|| !Zone.LIBRARY.equals(((Spell) stackObject).getFromZone()) || stackObject.getStackAbility().getManaCostsToPay().isPaid(); // mana payment finished
|| stackObject.getStackAbility().getManaCostsToPay().isPaid(); // mana payment finished
}
return true;
} }
@Override @Override

View file

@ -44,19 +44,16 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
public ActivationStatus canActivate(UUID playerId, Game game) { public ActivationStatus canActivate(UUID playerId, Game game) {
// check if player is in the process of playing spell costs and they are no longer allowed to use // check if player is in the process of playing spell costs and they are no longer allowed to use
// activated mana abilities (e.g. because they started to use improvise or convoke) // activated mana abilities (e.g. because they started to use improvise or convoke)
if (!game.getStack().isEmpty()) { StackObject stackObject = game.getStack().getFirstOrNull();
StackObject stackObject = game.getStack().getFirst(); if (stackObject instanceof Spell) {
if (stackObject instanceof Spell) { switch (((Spell) stackObject).getCurrentActivatingManaAbilitiesStep()) {
switch (((Spell) stackObject).getCurrentActivatingManaAbilitiesStep()) { case BEFORE:
case BEFORE: case NORMAL:
case NORMAL: break;
break; case AFTER:
case AFTER: return ActivationStatus.getFalse();
return ActivationStatus.getFalse();
}
} }
} }
return super.canActivate(playerId, game); return super.canActivate(playerId, game);
} }

View file

@ -51,6 +51,16 @@ public class SpellStack extends ArrayDeque<StackObject> {
} }
} }
@Override
@Deprecated // must use getFirstOrNull instead
public StackObject getFirst() {
return super.getFirst();
}
public StackObject getFirstOrNull() {
return this.isEmpty() ? null : this.getFirst();
}
public boolean remove(StackObject object, Game game) { public boolean remove(StackObject object, Game game) {
for (StackObject spell : this) { for (StackObject spell : this) {
if (spell.getId().equals(object.getId())) { if (spell.getId().equals(object.getId())) {