Merge pull request 'master' (#7) from External/mage:master into master
All checks were successful
/ example-docker-compose (push) Successful in 15m4s

Reviewed-on: #7
This commit is contained in:
Failure 2024-12-07 15:43:53 -08:00
commit a204b33f69
26 changed files with 253 additions and 58 deletions

View file

@ -5,6 +5,8 @@ import mage.client.util.AppUtil;
import mage.client.util.GUISizeHelper;
import mage.util.CardUtil;
import java.nio.charset.Charset;
/**
* GUI: error dialog with copyable error message
* // TODO: add game logs and data for game errors (client side info from GameView)
@ -28,7 +30,10 @@ public class ErrorDialog extends MageDialog {
// add additional info
String fullError = "Error type: " + fullTitle + "\n"
+ "\n"
+ "Client version: " + MageFrame.getInstance().getVersion().toString() + "\n"
+ "Java version: " + System.getProperty("java.version") + "\n"
+ "Default charset: " + Charset.defaultCharset() + "\n"
+ "\n"
+ errorText;
this.textError.setText(fullError);
@ -68,7 +73,8 @@ public class ErrorDialog extends MageDialog {
AppUtil.openUrlInSystemBrowser(url);
}
/** This method is called from within the constructor to
/**
* This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.

View file

@ -17,6 +17,7 @@ import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
import mage.game.permanent.PermanentToken;
/**
* @author TheElk801
@ -44,7 +45,7 @@ public final class ComeBackWrong extends CardImpl {
class ComeBackWrongEffect extends OneShotEffect {
ComeBackWrongEffect() {
super(Outcome.Benefit);
super(Outcome.Neutral);
staticText = "destroy target creature. If a creature card is put into a graveyard this way, " +
"return it to the battlefield under your control. Sacrifice it at the beginning of your next end step";
}
@ -65,8 +66,14 @@ class ComeBackWrongEffect extends OneShotEffect {
return false;
}
permanent.destroy(source, game);
// tokens are not creature cards
if (permanent instanceof PermanentToken) {
return false;
}
Card card = permanent.getMainCard();
if (card == null || !card.isCreature(game) || !Zone.GRAVEYARD.match(game.getState().getZone(card.getId()))) {
if (card == null
|| !card.isCreature(game)
|| !Zone.GRAVEYARD.match(game.getState().getZone(card.getId()))) {
return true;
}
Player player = game.getPlayer(source.getControllerId());

View file

@ -16,8 +16,10 @@ import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.TokenImpl;
import mage.target.TargetPermanent;
import mage.target.targetpointer.FixedTarget;
/**
*
@ -78,13 +80,18 @@ class ElvishBranchbenderEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
int xValue = new PermanentsOnBattlefieldCount(filter).calculate(game, source, this);
Permanent targetForest = game.getPermanent(this.getTargetPointer().copy().getFirst(game, source));
if (targetForest == null) {
return false;
}
ContinuousEffect effect = new BecomesCreatureTargetEffect(
new ElvishBranchbenderToken(xValue),
false, false, Duration.EndOfTurn)
.withDurationRuleAtStart(true);
effect.setTargetPointer(this.getTargetPointer().copy());
// works well with blinked effects
effect.setTargetPointer(new FixedTarget(targetForest, game));
game.addEffect(effect, source);
return false;
return true;
}
}
@ -101,6 +108,7 @@ class ElvishBranchbenderToken extends TokenImpl {
super(token);
}
@Override
public ElvishBranchbenderToken copy() {
return new ElvishBranchbenderToken(this);
}

View file

@ -69,7 +69,9 @@ class ForTheAncestorsEffect extends OneShotEffect {
return false;
}
ChoiceCreatureType choice = new ChoiceCreatureType(game, source);
player.choose(outcome, choice, game);
if (!player.choose(outcome, choice, game)) {
return false;
}
SubType subType = SubType.byDescription(choice.getChoiceKey());
FilterCard filter;
if (subType != null) {

View file

@ -66,18 +66,14 @@ class HarshMercyEffect extends OneShotEffect {
MageObject sourceObject = game.getObject(source);
if (controller != null && sourceObject != null) {
Set<String> chosenTypes = new HashSet<>();
PlayerIteration:
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
Choice typeChoice = new ChoiceCreatureType(game, source);
if (player != null && !player.choose(Outcome.DestroyPermanent, typeChoice, game)) {
continue PlayerIteration;
}
String chosenType = typeChoice.getChoiceKey();
if (chosenType != null) {
game.informPlayers(sourceObject.getIdName() + ": " + player.getLogName() + " has chosen " + chosenType);
chosenTypes.add(chosenType);
continue;
}
game.informPlayers(sourceObject.getIdName() + ": " + player.getLogName() + " has chosen " + typeChoice.getChoiceKey());
chosenTypes.add(typeChoice.getChoiceKey());
}
FilterPermanent filter = new FilterCreaturePermanent("creatures");

View file

@ -128,13 +128,10 @@ class KaronaFalseGodEffect extends OneShotEffect {
if (!controller.choose(Outcome.BoostCreature, typeChoice, game)) {
return false;
}
String typeChosen = typeChoice.getChoiceKey();
if (!typeChosen.isEmpty()) {
game.informPlayers(controller.getLogName() + " has chosen " + typeChosen);
FilterCreaturePermanent filter = new FilterCreaturePermanent();
filter.add(SubType.byDescription(typeChosen).getPredicate());
game.addEffect(new BoostAllEffect(3, 3, Duration.EndOfTurn, filter, false), source);
}
game.informPlayers(controller.getLogName() + " has chosen " + typeChoice.getChoiceKey());
FilterCreaturePermanent filter = new FilterCreaturePermanent();
filter.add(SubType.byDescription(typeChoice.getChoiceKey()).getPredicate());
game.addEffect(new BoostAllEffect(3, 3, Duration.EndOfTurn, filter, false), source);
return true;
}
return false;

View file

@ -109,7 +109,7 @@ class LongListOfTheEntsEffect extends OneShotEffect {
if (player == null) {
return false;
}
ChoiceCreatureType choice = new ChoiceCreatureType(game, source);
Object existingEntList = game.getState().getValue(LongListOfTheEnts.getKey(game, source, 0));
int offset;
Set<SubType> newEntList;
@ -124,12 +124,13 @@ class LongListOfTheEntsEffect extends OneShotEffect {
.stream()
.map(SubType::toString)
.collect(Collectors.toSet());
ChoiceCreatureType choice = new ChoiceCreatureType(game, source);
choice.getKeyChoices().keySet().removeIf(chosenTypes::contains);
player.choose(Outcome.BoostCreature, choice, game);
SubType subType = SubType.byDescription(choice.getChoiceKey());
if (subType == null) {
if (!player.choose(Outcome.BoostCreature, choice, game)) {
return false;
}
SubType subType = SubType.byDescription(choice.getChoiceKey());
game.informPlayers(player.getLogName() + " notes the creature type " + subType);
newEntList.add(subType);
game.getState().setValue(LongListOfTheEnts.getKey(game, source, offset), newEntList);

View file

@ -78,10 +78,8 @@ class BecomesChosenCreatureTypeControlledEffect extends OneShotEffect {
String chosenType = "";
if (player != null && card != null) {
Choice typeChoice = new ChoiceCreatureType(game, source);
while (!player.choose(Outcome.BoostCreature, typeChoice, game)) {
if (!player.canRespond()) {
return false;
}
if (!player.choose(Outcome.BoostCreature, typeChoice, game)) {
return false;
}
game.informPlayers(card.getName() + ": " + player.getLogName() + " has chosen " + typeChoice.getChoiceKey());
chosenType = typeChoice.getChoiceKey();

View file

@ -83,6 +83,9 @@ class NadierAgentOfTheDuskenelEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
// For Nadier's second ability, use its power from when it was last on the battlefield to determine
// how many tokens to create.
// (2020-11-10)
Object obj = getValue("permanentLeftBattlefield");
if (!(obj instanceof Permanent)) {
return false;

View file

@ -69,9 +69,8 @@ class PatriarchsBiddingEffect extends OneShotEffect {
if (!player.choose(Outcome.PutCreatureInPlay, typeChoice, game)) {
continue;
}
String chosenType = typeChoice.getChoiceKey();
game.informPlayers(sourceObject.getLogName() + ": " + player.getLogName() + " has chosen " + chosenType);
chosenTypes.add(chosenType);
game.informPlayers(sourceObject.getLogName() + ": " + player.getLogName() + " has chosen " + typeChoice.getChoiceKey());
chosenTypes.add(typeChoice.getChoiceKey());
}
List<SubType.SubTypePredicate> predicates = new ArrayList<>();

View file

@ -73,11 +73,10 @@ class PolJamaarIllusionistEffect extends OneShotEffect {
return false;
}
ChoiceCreatureType choice = new ChoiceCreatureType(game, source);
player.choose(outcome, choice, game);
SubType subType = SubType.byDescription(choice.getChoice());
if (subType == null) {
if (!player.choose(outcome, choice, game)) {
return false;
}
SubType subType = SubType.byDescription(choice.getChoiceKey());
game.informPlayers(player.getLogName() + " chooses " + subType);
int amount = game
.getBattlefield()

View file

@ -57,7 +57,6 @@ class StandardizeEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source);
String chosenType = "";
if (player != null && sourceObject != null) {
Choice typeChoice = new ChoiceCreatureType(game, source);
typeChoice.setMessage("Choose a creature type other than Wall");
@ -66,16 +65,12 @@ class StandardizeEffect extends OneShotEffect {
return false;
}
game.informPlayers(sourceObject.getLogName() + ": " + player.getLogName() + " has chosen " + typeChoice.getChoiceKey());
chosenType = typeChoice.getChoiceKey();
if (chosenType != null && !chosenType.isEmpty()) {
// ADD TYPE TO TARGET
game.addEffect(new BecomesSubtypeAllEffect(
Duration.EndOfTurn, Arrays.asList(SubType.byDescription(chosenType)),
StaticFilters.FILTER_PERMANENT_CREATURE, true
), source);
return true;
}
// ADD TYPE TO TARGET
game.addEffect(new BecomesSubtypeAllEffect(
Duration.EndOfTurn, Arrays.asList(SubType.byDescription(typeChoice.getChoiceKey())),
StaticFilters.FILTER_PERMANENT_CREATURE, true
), source);
return true;
}
return false;
}

View file

@ -50,6 +50,7 @@ class ThreeTreeScribeTriggeredAbility extends TriggeredAbilityImpl {
super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.P1P1.createInstance()));
this.setTriggerPhrase("Whenever {this} or another creature you control leaves the battlefield without dying, ");
this.addTarget(new TargetControlledCreaturePermanent());
setLeavesTheBattlefieldTrigger(true);
}
private ThreeTreeScribeTriggeredAbility(final ThreeTreeScribeTriggeredAbility ability) {

View file

@ -67,9 +67,7 @@ class TribalUnityEffect extends OneShotEffect {
Choice typeChoice = new ChoiceCreatureType(game, source);
if (player != null && player.choose(outcome, typeChoice, game)) {
int boost = amount.calculate(game, source, this);
if (typeChoice.getChoiceKey() != null) {
game.informPlayers(sourceObject.getLogName() + " chosen type: " + typeChoice.getChoiceKey());
}
game.informPlayers(sourceObject.getLogName() + " chosen type: " + typeChoice.getChoiceKey());
FilterCreaturePermanent filterCreaturePermanent = new FilterCreaturePermanent();
filterCreaturePermanent.add(SubType.byDescription(typeChoice.getChoiceKey()).getPredicate());
game.addEffect(new BoostAllEffect(

View file

@ -61,6 +61,7 @@ class LeavesTheBattlefieldAttachedTriggeredAbility extends ZoneChangeTriggeredAb
public LeavesTheBattlefieldAttachedTriggeredAbility() {
super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken()), "When enchanted creature leaves the battlefield, ", Boolean.FALSE);
setLeavesTheBattlefieldTrigger(true);
}
private LeavesTheBattlefieldAttachedTriggeredAbility(final LeavesTheBattlefieldAttachedTriggeredAbility ability) {

View file

@ -0,0 +1,83 @@
package org.mage.test.cards.single.cmr;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommanderDuelBase;
/**
* @author JayDi85
*/
public class NadierAgentOfTheDuskenelTest extends CardTestCommanderDuelBase {
@Test
public void test_DieAnother() {
addCustomEffect_TargetDestroy(playerA);
// Whenever a token you control leaves the battlefield, put a +1/+1 counter on Nadier, Agent of the Duskenel.
// When Nadier leaves the battlefield, create a number of 1/1 green Elf Warrior creature tokens equal to its power.
addCard(Zone.BATTLEFIELD, playerA, "Nadier, Agent of the Duskenel", 1);
//
// {5}, {T}: Create a 1/1 colorless Insect artifact creature token with flying named Wasp.
addCard(Zone.BATTLEFIELD, playerA, "The Hive", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
// prepare token
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}, {T}: Create");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wasp", 1);
// on leaves non-token -- nothing to happen
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "The Hive");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1);
checkGraveyardCount("on non-token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Hive", 1);
checkStackSize("on non-token - no triggers", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 0);
// on leaves token - must trigger
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Wasp");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("on token", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wasp", 0);
checkPermanentCounters("on token - must trigger", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nadier, Agent of the Duskenel", CounterType.P1P1, 1);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
@Test
public void test_DieItself() {
addCustomEffect_TargetDestroy(playerA, 2);
// Whenever a token you control leaves the battlefield, put a +1/+1 counter on Nadier, Agent of the Duskenel.
// When Nadier leaves the battlefield, create a number of 1/1 green Elf Warrior creature tokens equal to its power.
addCard(Zone.BATTLEFIELD, playerA, "Nadier, Agent of the Duskenel", 1);
//
// {5}, {T}: Create a 1/1 colorless Insect artifact creature token with flying named Wasp.
addCard(Zone.BATTLEFIELD, playerA, "The Hive", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
// prepare token
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}, {T}: Create");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wasp", 1);
// on leaves token and itself -- must x2 triggers:
// * one with counter to fizzle
// * one with new token
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Wasp^Nadier, Agent of the Duskenel");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1);
checkStackSize("must triggers x2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
setChoice(playerA, "Whenever a token you control leaves"); // x2 triggers from Nadier
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "The Hive", 1);
assertPermanentCount(playerA, "Wasp", 0);
assertGraveyardCount(playerA, "Nadier, Agent of the Duskenel", 1);
assertPermanentCount(playerA, "Elf Warrior Token", 3);
}
}

View file

@ -0,0 +1,70 @@
package org.mage.test.cards.single.khm;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class ValorOfTheWorthyTest extends CardTestPlayerBase {
@Test
public void test_DieTarget() {
addCustomEffect_TargetDestroy(playerA);
// Enchant creature
// Enchanted creature gets +1/+1.
// When enchanted creature leaves the battlefield, create a 1/1 white Spirit creature token with flying.
addCard(Zone.HAND, playerA, "Valor of the Worthy"); // {W}
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
// attach
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Valor of the Worthy", "Grizzly Bears");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
checkPT("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 2 + 1, 2 + 1);
// destroy target - must trigger
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy");
addTarget(playerA, "Grizzly Bears");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); // resolve destroy
checkStackObject("must trigger on destroy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "When enchanted creature leaves the battlefield", 1);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertTokenCount(playerA, "Spirit Token", 1);
}
@Test
public void test_DieItself() {
addCustomEffect_TargetDestroy(playerA, 2);
// Enchant creature
// Enchanted creature gets +1/+1.
// When enchanted creature leaves the battlefield, create a 1/1 white Spirit creature token with flying.
addCard(Zone.HAND, playerA, "Valor of the Worthy"); // {W}
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
// attach
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Valor of the Worthy", "Grizzly Bears");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
checkPT("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 2 + 1, 2 + 1);
// destroy all - must trigger anyway
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy");
addTarget(playerA, "Valor of the Worthy^Grizzly Bears");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); // resolve destroy
checkStackObject("must trigger on destroy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "When enchanted creature leaves the battlefield", 1);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertTokenCount(playerA, "Spirit Token", 1);
}
}

View file

@ -86,7 +86,7 @@ public class PrimalPrayersTest extends CardTestPlayerBase {
setChoice(playerA, "Cast with alternative cost: Pay {E}"); // alternative cost chosen
checkPlayableAbility("no more energy to cast third Bears", 1, PhaseStep.BEGIN_COMBAT, playerA,
"Cast Grizzly Bears", false);
runCode("3: energy counter is 0", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkEnergyCount(info, player, 0));
runCode("3: energy counter is 0", 1, PhaseStep.BEGIN_COMBAT, playerA, (info, player, game) -> checkEnergyCount(info, player, 0));
setStopAt(1, PhaseStep.END_COMBAT);
execute();

View file

@ -102,10 +102,11 @@ public class BloodCultistTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blood Cultist");
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: {this} deals", "Devilthorn Fox");
activateAbility(5, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: {this} deals", "Shambling Ghoul");
attack(5, playerA, "Silvercoat Lion");
block(5, playerB, "Shambling Ghoul", "Silvercoat Lion");
activateAbility(5, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: {this} deals", "Shambling Ghoul");
setStrictChooseMode(true);
setStopAt(5, PhaseStep.END_TURN);
execute();

View file

@ -57,6 +57,8 @@ public class PlayerAction {
@Override
public String toString() {
return "T" + this.turnNum + "." + this.step.getStepShortText() + ": " + this.action;
return "T" + this.turnNum + "." + this.step.getStepShortText()
+ ": " + this.action
+ (this.actionName.isEmpty() ? "" : " (" + this.actionName + ")");
}
}

View file

@ -262,6 +262,28 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
+ " (found actions after stop on " + maxTurn + " / " + maxPhase + ")",
(maxTurn > this.stopOnTurn) || (maxTurn == this.stopOnTurn && maxPhase > this.stopAtStep.getIndex()));
// check commands order
for (Player player : currentGame.getPlayers().values()) {
if (true) break; // TODO: delete/comment and fix all failed tests
if (player instanceof TestPlayer) {
TestPlayer testPlayer = (TestPlayer) player;
int lastActionIndex = 0;
PlayerAction lastAction = null;
for (PlayerAction currentAction : testPlayer.getActions()) {
int currentActionIndex = 1000 * currentAction.getTurnNum() + currentAction.getStep().getIndex();
if (currentActionIndex < lastActionIndex) {
// how-to fix: find typo in step/turn number
Assert.fail("Found wrong commands order for " + testPlayer.getName() + ":" + "\n"
+ lastAction + "\n"
+ currentAction);
} else {
lastActionIndex = currentActionIndex;
lastAction = currentAction;
}
}
}
}
if (!currentGame.isPaused()) {
// workaround to fill range info (cause real range fills after game start, but some cheated cards needs range on ETB)
for (Player player : currentGame.getPlayers().values()) {

View file

@ -2021,10 +2021,12 @@ public class VerifyCardDataTest {
boolean isPutToGraveAbility = rules.contains("put into")
&& rules.contains("graveyard")
&& rules.contains("from the battlefield");
boolean isLeavesBattlefield = rules.contains("leaves the battlefield");
isLeavesBattlefield = false; // TODO: remove and fix all bad cards
if (triggeredAbility.isLeavesTheBattlefieldTrigger()) {
// TODO: add check for wrongly enabled settings too?
} else {
if (isDiesAbility || isPutToGraveAbility) {
if (isDiesAbility || isPutToGraveAbility || isLeavesBattlefield) {
fail(card, "abilities", "dies related trigger must use setLeavesTheBattlefieldTrigger(true) and possibly override isInUseableZone - "
+ triggeredAbility.getClass().getSimpleName());
}

View file

@ -39,6 +39,7 @@ public class LeavesBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl {
this.filter = filter;
this.setTargetPointer = setTargetPointer;
setTriggerPhrase("Whenever " + filter.getMessage() + " leaves the battlefield, ");
setLeavesTheBattlefieldTrigger(true);
}
protected LeavesBattlefieldAllTriggeredAbility(final LeavesBattlefieldAllTriggeredAbility ability) {

View file

@ -59,10 +59,9 @@ public class BecomesChosenCreatureTypeTargetEffect extends OneShotEffect {
if (nonWall) {
typeChoice.getKeyChoices().remove(SubType.WALL.getDescription());
}
while (!player.choose(Outcome.BoostCreature, typeChoice, game)) {
if (!player.canRespond()) {
return false;
}
if (!player.choose(Outcome.BoostCreature, typeChoice, game)) {
return false;
}
game.informPlayers(card.getName() + ": " + player.getLogName() + " has chosen " + typeChoice.getChoiceKey());
chosenType = typeChoice.getChoiceKey();

View file

@ -10,7 +10,9 @@ import java.util.*;
import java.util.stream.Collectors;
/**
* Game's choose dialog to ask about creature type. Return getChoice
* Game's choose dialog to ask about creature type.
* <p>
* Warning, must use getChoiceKey as result
*/
public class ChoiceCreatureType extends ChoiceImpl {

View file

@ -135,7 +135,9 @@ class VolosJournalTokenEffect extends OneShotEffect {
return true;
}
player.choose(outcome, choice, game);
if (!player.choose(outcome, choice, game)) {
return false;
}
notedTypes.add(choice.getChoiceKey());
return true;
}