From bf43ea9936df0823eb729566ac54b88834a02989 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 22 Oct 2016 12:45:50 +0200 Subject: [PATCH] Added test and fixed some possible null pointer exception. --- Mage.Common/src/mage/utils/MageVersion.java | 2 +- .../src/mage/player/human/HumanPlayer.java | 9 ++- .../mage/cards/e/EmrakulThePromisedEnd.java | 4 +- Mage.Sets/src/mage/cards/g/Grindclock.java | 30 ++++---- .../org/mage/test/turnmod/ExtraTurnsTest.java | 70 +++++++++++++++++++ .../main/java/mage/abilities/AbilityImpl.java | 15 ++-- .../AddConditionalManaOfAnyColorEffect.java | 13 ++-- .../main/java/mage/cards/ExpansionSet.java | 5 +- .../mage/cards/repository/CardRepository.java | 2 +- Mage/src/main/java/mage/game/GameImpl.java | 11 +++ 10 files changed, 130 insertions(+), 31 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java diff --git a/Mage.Common/src/mage/utils/MageVersion.java b/Mage.Common/src/mage/utils/MageVersion.java index 020bd898a61..70173cb480b 100644 --- a/Mage.Common/src/mage/utils/MageVersion.java +++ b/Mage.Common/src/mage/utils/MageVersion.java @@ -41,7 +41,7 @@ public class MageVersion implements Serializable, Comparable { public final static int MAGE_VERSION_MAJOR = 1; public final static int MAGE_VERSION_MINOR = 4; public final static int MAGE_VERSION_PATCH = 16; - public final static String MAGE_VERSION_MINOR_PATCH = "v3"; + public final static String MAGE_VERSION_MINOR_PATCH = "v4"; public final static String MAGE_VERSION_INFO = ""; private final int major; diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index c25a5c38453..8d05903ba05 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -735,12 +735,14 @@ public class HumanPlayer extends PlayerImpl { } private boolean checkPassStep(Game game) { - if (game.getStep() != null) { + try { if (playerId.equals(game.getActivePlayerId())) { return !this.getUserData().getUserSkipPrioritySteps().getYourTurn().isPhaseStepSet(game.getStep().getType()); } else { return !this.getUserData().getUserSkipPrioritySteps().getOpponentTurn().isPhaseStepSet(game.getStep().getType()); } + } catch (NullPointerException ex) { + logger.error("null pointer exception UserData = " + userData == null ? "null" : "not null"); } return true; } @@ -1464,6 +1466,11 @@ public class HumanPlayer extends PlayerImpl { protected void updateGameStatePriority(String methodName, Game game) { if (game.getState().getPriorityPlayerId() != null) { // don't do it if priority was set to null before (e.g. discard in cleanaup) + if (getId() == null) { + logger.fatal("Player with no ID: " + name); + this.quit(game); + return; + } logger.debug("Setting game priority to " + getId() + " [" + methodName + "]"); game.getState().setPriorityPlayerId(getId()); } diff --git a/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java b/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java index cda0e45b44d..175adfd6be0 100644 --- a/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java +++ b/Mage.Sets/src/mage/cards/e/EmrakulThePromisedEnd.java @@ -34,7 +34,6 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CastSourceTriggeredAbility; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.effects.common.turn.ControlTargetPlayerNextTurnEffect; @@ -64,12 +63,13 @@ import mage.util.CardUtil; public class EmrakulThePromisedEnd extends CardImpl { private static final FilterCard filter = new FilterCard("instants"); + static { filter.add(new CardTypePredicate(CardType.INSTANT)); } public EmrakulThePromisedEnd(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{13}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{13}"); this.supertype.add("Legendary"); this.subtype.add("Eldrazi"); this.power = new MageInt(13); diff --git a/Mage.Sets/src/mage/cards/g/Grindclock.java b/Mage.Sets/src/mage/cards/g/Grindclock.java index fae092d35fa..8a3acf2e2c8 100644 --- a/Mage.Sets/src/mage/cards/g/Grindclock.java +++ b/Mage.Sets/src/mage/cards/g/Grindclock.java @@ -25,13 +25,9 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.cards.g; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Zone; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -39,9 +35,12 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPlayer; @@ -51,15 +50,16 @@ import mage.target.TargetPlayer; */ public class Grindclock extends CardImpl { - public Grindclock (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{2}"); + public Grindclock(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.CHARGE.createInstance()), new TapSourceCost())); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GrindclockEffect(), new TapSourceCost()); ability.addTarget(new TargetPlayer()); this.addAbility(ability); } - public Grindclock (final Grindclock card) { + public Grindclock(final Grindclock card) { super(card); } @@ -71,6 +71,7 @@ public class Grindclock extends CardImpl { } class GrindclockEffect extends OneShotEffect { + public GrindclockEffect() { super(Outcome.Detriment); staticText = "Target player puts the top X cards of his or her library into his or her graveyard, where X is the number of charge counters on {this}"; @@ -82,11 +83,14 @@ class GrindclockEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - int amount = game.getPermanent(source.getSourceId()).getCounters(game).getCount(CounterType.CHARGE); - Player targetPlayer = game.getPlayer(source.getFirstTarget()); - if (targetPlayer != null) { - targetPlayer.moveCards(targetPlayer.getLibrary().getTopCards(game, amount), Zone.GRAVEYARD, source, game); - return true; + Permanent sourceObject = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (sourceObject != null) { + int amount = sourceObject.getCounters(game).getCount(CounterType.CHARGE); + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + if (targetPlayer != null) { + targetPlayer.moveCards(targetPlayer.getLibrary().getTopCards(game, amount), Zone.GRAVEYARD, source, game); + return true; + } } return false; } @@ -96,4 +100,4 @@ class GrindclockEffect extends OneShotEffect { return new GrindclockEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java new file mode 100644 index 00000000000..347e271c08e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.turnmod; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class ExtraTurnsTest extends CardTestPlayerBase { + + /** + * Emrakul, the Promised End not giving an extra turn when cast in the + * opponent's turn + */ + @Test + public void testEmrakulCastOnOpponentsTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 12); + addCard(Zone.GRAVEYARD, playerA, "Island", 1); + // Emrakul, the Promised End costs {1} less to cast for each card type among cards in your graveyard. + // When you cast Emrakul, you gain control of target opponent during that player's next turn. After that turn, that player takes an extra turn. + // Flying + // Trample + // Protection from instants + addCard(Zone.HAND, playerA, "Emrakul, the Promised End", 1); // {13} + // Flash (You may cast this spell any time you could cast an instant.) + // Creature cards you own that aren't on the battlefield have flash. + // Each opponent can cast spells only any time he or she could cast a sorcery. + addCard(Zone.BATTLEFIELD, playerA, "Teferi, Mage of Zhalfir", 1); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Emrakul, the Promised End"); + + setStopAt(3, PhaseStep.UPKEEP); + execute(); + + assertPermanentCount(playerA, "Emrakul, the Promised End", 1); + + Assert.assertTrue("For extra turn, playerB has to be the active player ", currentGame.getActivePlayerId().equals(playerB.getId())); + } +} diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index cf82795eff0..3e4ecc12036 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -328,13 +328,16 @@ public abstract class AbilityImpl implements Ability { if (sourceObject != null && !this.getAbilityType().equals(AbilityType.TRIGGERED)) { // triggered abilities check this already in playerImpl.triggerAbility sourceObject.adjustTargets(this, game); } - // Flashback abilities haven't made the choices the underlying spell might need for targetting. - if (!(this instanceof FlashbackAbility) && getTargets().size() > 0 && getTargets().chooseTargets( - getEffects().get(0).getOutcome(), this.controllerId, this, noMana, game) == false) { - if ((variableManaCost != null || announceString != null) && !game.isSimulation()) { - game.informPlayer(controller, (sourceObject != null ? sourceObject.getIdName() : "") + ": no valid targets with this value of X"); + // Flashback abilities haven't made the choices the underlying spell might need for targeting. + if (!(this instanceof FlashbackAbility) + && getTargets().size() > 0) { + Outcome outcome = getEffects().isEmpty() ? Outcome.Detriment : getEffects().get(0).getOutcome(); + if (getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game) == false) { + if ((variableManaCost != null || announceString != null) && !game.isSimulation()) { + game.informPlayer(controller, (sourceObject != null ? sourceObject.getIdName() : "") + ": no valid targets with this value of X"); + } + return false; // when activation of ability is canceled during target selection } - return false; // when activation of ability is canceled during target selection } } // end modes diff --git a/Mage/src/main/java/mage/abilities/effects/common/AddConditionalManaOfAnyColorEffect.java b/Mage/src/main/java/mage/abilities/effects/common/AddConditionalManaOfAnyColorEffect.java index e0275a85c62..bd2cc3227c6 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/AddConditionalManaOfAnyColorEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/AddConditionalManaOfAnyColorEffect.java @@ -85,13 +85,14 @@ public class AddConditionalManaOfAnyColorEffect extends ManaEffect { int value = amount.calculate(game, source, this); boolean result = false; - ChoiceColor choice = new ChoiceColor(false); - for (int i = 0; i < value; i++) { - if (!choice.isChosen()) { - if (!controller.choose(outcome, choice, game)) { - return false; - } + ChoiceColor choice = new ChoiceColor(true); + while (!choice.isChosen()) { + controller.choose(outcome, choice, game); + if (!controller.isInGame()) { + return false; } + } + for (int i = 0; i < value; i++) { Mana mana = null; if (choice.getColor().isBlack()) { mana = manaBuilder.setMana(Mana.BlackMana(1), source, game).build(); diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index 4ea414f18bd..8a94962d239 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -193,8 +193,11 @@ public abstract class ExpansionSet implements Serializable { if (15 > theBooster.size()) { List commons = getCardsByRarity(Rarity.COMMON); - while (15 > theBooster.size()) { + while (15 > theBooster.size() && !commons.isEmpty()) { addToBooster(theBooster, commons); + if (commons.isEmpty()) { + commons = getCardsByRarity(Rarity.COMMON); + } } } diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index f1108943dd4..35f30921e8c 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -64,7 +64,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 47; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 63; + private static final long CARD_CONTENT_VERSION = 64; private Dao cardDao; private Set classNames; diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 9fd14a95530..eecb0914418 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1499,6 +1499,17 @@ public abstract class GameImpl implements Game, Serializable { @Override public void addTriggeredAbility(TriggeredAbility ability) { + if (ability.getControllerId() == null) { + String sourceName = "no sourceId"; + if (ability.getSourceId() != null) { + MageObject mageObject = getObject(ability.getSourceId()); + if (mageObject != null) { + sourceName = mageObject.getName(); + } + } + logger.fatal("Added triggered ability without controller: " + sourceName + " rule: " + ability.getRule()); + return; + } if (ability instanceof TriggeredManaAbility || ability instanceof DelayedTriggeredManaAbility) { // 20110715 - 605.4 Ability manaAbiltiy = ability.copy();