* Cascade - Fixed a bug that caused the controlling player to lose the game if all cards of the library were exiled with cascade although the cards were returned to library.

This commit is contained in:
LevelX2 2015-03-07 14:26:26 +01:00
parent 3a89d12948
commit a1208f1a73
4 changed files with 152 additions and 98 deletions

View file

@ -31,18 +31,16 @@ import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.abilities.keyword.CascadeAbility;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.Watcher;
@ -56,12 +54,6 @@ public class MaelstromNexus extends CardImpl {
super(ownerId, 130, "Maelstrom Nexus", Rarity.MYTHIC, new CardType[]{CardType.ENCHANTMENT}, "{W}{U}{B}{R}{G}");
this.expansionSetCode = "ARB";
// The first spell you cast each turn has cascade.
this.addAbility(new MaelstromNexusTriggeredAbility(), new FirstSpellCastThisTurnWatcher());
@ -164,40 +156,7 @@ class CascadeEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Card card;
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
ExileZone exile = game.getExile().createZone(source.getSourceId(), player.getName() + " Cascade");
Card stackCard = game.getCard(targetPointer.getFirst(game, source));
if (stackCard == null) {
return false;
}
int sourceCost = stackCard.getManaCost().convertedManaCost();
do {
card = player.getLibrary().removeFromTop(game);
if (card == null) {
break;
}
player.moveCardToExileWithInfo(card, source.getSourceId(), exile.getName(), source.getSourceId(), game, Zone.LIBRARY);
} while (card.getCardType().contains(CardType.LAND) || card.getManaCost().convertedManaCost() >= sourceCost);
if (card != null) {
if (player.chooseUse(outcome, "Use cascade effect on " + card.getName() + "?", game)) {
player.cast(card.getSpellAbility(), game, true);
exile.remove(card.getId());
}
}
while (exile.size() > 0) {
card = exile.getRandom(game);
exile.remove(card.getId());
player.moveCardToLibraryWithInfo(card, source.getSourceId(), game, Zone.EXILED, false, false);
}
return true;
return CascadeAbility.applyCascade(outcome, game, source);
}
@Override

View file

@ -1,7 +1,9 @@
package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -70,4 +72,62 @@ public class CascadeTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Arbor Elf", 0);
}
// test that player does not lose if all cards are exiled by cascade
// If you cast a spell with cascade and there are no nonland cards in your library with a converted mana
// cost that's less that that spell's converted mana cost, you'll exile your entire library. Then you'll
// randomly rearrange those cards and put them back as your library. Although you're essentially shuffling
// those cards, you're not technically doing so; abilities that trigger whenever you shuffle your library
// won't trigger.
@Test
public void testEmptyLibraryCascasde() {
playerA.getLibrary().clear();
addCard(Zone.LIBRARY, playerA, "Plains", 10);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
// Ardent Plea - Enchantment {1}{W}{U}
// Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.)
// Cascade
addCard(Zone.HAND, playerA, "Ardent Plea");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ardent Plea");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Ardent Plea", 1);
// the 10 lands go back to library
Assert.assertEquals("The 10 lands went back to library", 10, playerA.getLibrary().size());
Assert.assertTrue("Player A is still in game", playerA.isInGame());
}
@Test
public void testEmptyLibraryCascasdeNexus() {
playerA.getLibrary().clear();
addCard(Zone.LIBRARY, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, "Maelstrom Nexus");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
// Aven Skirmisher - Creature {W}
addCard(Zone.HAND, playerA, "Aven Skirmisher");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aven Skirmisher");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Maelstrom Nexus", 1);
Assert.assertTrue("Player A is still in game", playerA.isInGame());
assertHandCount(playerA, "Aven Skirmisher", 0);
assertPermanentCount(playerA, "Aven Skirmisher", 1);
// the 2 lands go back to library
Assert.assertEquals("The 2 lands went back to library", 2, playerA.getLibrary().size());
Assert.assertTrue("Player A is still in game", playerA.isInGame());
}
}

View file

@ -1,10 +1,12 @@
package org.mage.test.serverside.base;
import mage.constants.PhaseStep;
import java.io.File;
import java.io.FileNotFoundException;
import mage.cards.Card;
import mage.cards.decks.Deck;
import mage.cards.decks.importer.DeckImporterUtil;
import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
import mage.filter.Filter;
import mage.game.Game;
@ -16,11 +18,12 @@ import mage.players.Player;
import org.junit.Assert;
import org.junit.Before;
import org.mage.test.player.TestPlayer;
import static org.mage.test.serverside.base.MageTestPlayerBase.TESTS_PATH;
import static org.mage.test.serverside.base.MageTestPlayerBase.activePlayer;
import static org.mage.test.serverside.base.MageTestPlayerBase.currentGame;
import static org.mage.test.serverside.base.MageTestPlayerBase.logger;
import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl;
import java.io.File;
import java.io.FileNotFoundException;
/**
* Base class for testing single cards and effects.
*
@ -217,40 +220,42 @@ public abstract class CardTestPlayerBase extends CardTestPlayerAPIImpl {
if (type.equals(CardTestPlayerBase.ExpectedType.RESULT)) {
String expected = getStringParam(line, 1);
String actual = "draw";
if (currentGame.getWinner().equals("Player ComputerA is the winner")) {
actual = "won";
} else if (currentGame.getWinner().equals("Player ComputerB is the winner")) {
actual = "lost";
switch (currentGame.getWinner()) {
case "Player ComputerA is the winner":
actual = "won";
break;
case "Player ComputerB is the winner":
actual = "lost";
break;
}
Assert.assertEquals("Game results are not equal", expected, actual);
return;
}
Player player = null;
String playerName = getStringParam(line, 1);
switch (playerName) {
case "ComputerA":
player = currentGame.getPlayer(playerA.getId());
break;
case "ComputerB":
player = currentGame.getPlayer(playerB.getId());
break;
}
if (player == null) {
throw new IllegalArgumentException("Wrong player in 'battlefield' line, player=" + player + ", line=" + line);
}
if (type.equals(CardTestPlayerBase.ExpectedType.LIFE)) {
String player = getStringParam(line, 1);
int expected = getIntParam(line, 2);
if (player.equals("ComputerA")) {
int actual = currentGame.getPlayer(playerA.getId()).getLife();
Assert.assertEquals("Life amounts are not equal", expected, actual);
} else if (player.equals("ComputerB")) {
int actual = currentGame.getPlayer(playerB.getId()).getLife();
Assert.assertEquals("Life amounts are not equal", expected, actual);
} else {
throw new IllegalArgumentException("Wrong player in 'life' line, player=" + player + ", line=" + line);
}
int actual = player.getLife();
Assert.assertEquals("Life amounts are not equal", expected, actual);
return;
}
if (type.equals(CardTestPlayerBase.ExpectedType.BATTLEFIELD)) {
String playerName = getStringParam(line, 1);
String cardName = getStringParam(line, 2);
int expectedCount = getIntParam(line, 3);
Player player = null;
if (playerName.equals("ComputerA")) {
player = currentGame.getPlayer(playerA.getId());
} else if (playerName.equals("ComputerB")) {
player = currentGame.getPlayer(playerB.getId());
} else {
throw new IllegalArgumentException("Wrong player in 'battlefield' line, player=" + player + ", line=" + line);
}
int actualCount = 0;
for (Permanent permanent : currentGame.getBattlefield().getAllPermanents()) {
if (permanent.getControllerId().equals(player.getId())) {
@ -262,18 +267,10 @@ public abstract class CardTestPlayerBase extends CardTestPlayerAPIImpl {
Assert.assertEquals("(Battlefield) Card counts are not equal (" + cardName + ")", expectedCount, actualCount);
return;
}
if (type.equals(CardTestPlayerBase.ExpectedType.GRAVEYARD)) {
String playerName = getStringParam(line, 1);
String cardName = getStringParam(line, 2);
int expectedCount = getIntParam(line, 3);
Player player = null;
if (playerName.equals("ComputerA")) {
player = currentGame.getPlayer(playerA.getId());
} else if (playerName.equals("ComputerB")) {
player = currentGame.getPlayer(playerB.getId());
} else {
throw new IllegalArgumentException("Wrong player in 'graveyard' line, player=" + player + ", line=" + line);
}
int actualCount = 0;
for (Card card : player.getGraveyard().getCards(currentGame)) {
if (card.getName().equals(cardName)) {

View file

@ -28,17 +28,16 @@
package mage.abilities.keyword;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.stack.Spell;
import mage.players.Player;
@ -94,23 +93,10 @@ public class CascadeAbility extends TriggeredAbilityImpl {
public CascadeAbility copy() {
return new CascadeAbility(this);
}
}
// !!! Changes to the cascade effect here have to be copied to the cascadeEffect of Maelstrom Nexus card eventually.
// There is a functional copy of this effect
// moved to static method because it's called also from class {link} MaelstromNexus
class CascadeEffect extends OneShotEffect {
public CascadeEffect() {
super(Outcome.PutCardInPlay);
}
public CascadeEffect(CascadeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
public static boolean applyCascade(Outcome outcome, Game game, Ability source) {
Card card;
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
@ -125,6 +111,7 @@ class CascadeEffect extends OneShotEffect {
}
player.moveCardToExileWithInfo(card, exile.getId(), exile.getName(), source.getSourceId(), game, Zone.LIBRARY);
} while (player.isInGame() && card.getCardType().contains(CardType.LAND) || card.getManaCost().convertedManaCost() >= sourceCost);
player.getLibrary().reset();
if (card != null) {
if (player.chooseUse(outcome, "Use cascade effect on " + card.getName() + "?", game)) {
@ -142,6 +129,55 @@ class CascadeEffect extends OneShotEffect {
return true;
}
}
// !!! Changes to the cascade effect here have to be copied to the cascadeEffect of Maelstrom Nexus card eventually.
// There is a functional copy of this effect
class CascadeEffect extends OneShotEffect {
public CascadeEffect() {
super(Outcome.PutCardInPlay);
}
public CascadeEffect(CascadeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
return CascadeAbility.applyCascade(outcome, game, source);
// Card card;
// Player player = game.getPlayer(source.getControllerId());
// if (player == null) {
// return false;
// }
// ExileZone exile = game.getExile().createZone(source.getSourceId(), player.getName() + " Cascade");
// int sourceCost = game.getCard(source.getSourceId()).getManaCost().convertedManaCost();
// do {
// card = player.getLibrary().getFromTop(game);
// if (card == null) {
// break;
// }
// player.moveCardToExileWithInfo(card, exile.getId(), exile.getName(), source.getSourceId(), game, Zone.LIBRARY);
// } while (player.isInGame() && card.getCardType().contains(CardType.LAND) || card.getManaCost().convertedManaCost() >= sourceCost);
//
// if (card != null) {
// if (player.chooseUse(outcome, "Use cascade effect on " + card.getName() + "?", game)) {
// if(player.cast(card.getSpellAbility(), game, true)){
// exile.remove(card.getId());
// }
// }
// }
//
// while (exile.size() > 0) {
// card = exile.getRandom(game);
// exile.remove(card.getId());
// player.moveCardToLibraryWithInfo(card, source.getSourceId(), game, Zone.EXILED, false, false);
// }
//
// return true;
}
@Override
public CascadeEffect copy() {
@ -149,3 +185,5 @@ class CascadeEffect extends OneShotEffect {
}
}