diff --git a/Mage.Sets/src/mage/sets/stronghold/VolrathsShapeshifter.java b/Mage.Sets/src/mage/sets/stronghold/VolrathsShapeshifter.java new file mode 100644 index 00000000000..17d9e59e21c --- /dev/null +++ b/Mage.Sets/src/mage/sets/stronghold/VolrathsShapeshifter.java @@ -0,0 +1,53 @@ +/* + * 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 mage.sets.stronghold; + +import java.util.UUID; + +/** + * + * @author ImperatorPrime + + */ +public class VolrathsShapeshifter extends mage.sets.vintagemasters.VolrathsShapeshifter { + + public VolrathsShapeshifter(UUID ownerId) { + super(ownerId); + this.cardNumber = 48; + this.expansionSetCode = "STH"; + } + + public VolrathsShapeshifter(final VolrathsShapeshifter card) { + super(card); + } + + @Override + public VolrathsShapeshifter copy() { + return new VolrathsShapeshifter(this); + } +} diff --git a/Mage.Sets/src/mage/sets/vintagemasters/VolrathsShapeshifter.java b/Mage.Sets/src/mage/sets/vintagemasters/VolrathsShapeshifter.java new file mode 100644 index 00000000000..cd9b949838f --- /dev/null +++ b/Mage.Sets/src/mage/sets/vintagemasters/VolrathsShapeshifter.java @@ -0,0 +1,142 @@ +/* + * 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 mage.sets.vintagemasters; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.SubLayer; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author ImperatorPrime + + */ +public class VolrathsShapeshifter extends CardImpl { + + public VolrathsShapeshifter(UUID ownerId) { + super(ownerId, 101, "Volrath's Shapeshifter", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{1}{U}{U}"); + this.expansionSetCode = "VMA"; + this.subtype.add("Shapeshifter"); + this.power = new MageInt(0); + this.toughness = new MageInt(1); + + // As long as the top card of your graveyard is a creature card, Volrath's Shapeshifter has the full text of that card and has the text "{2}: Discard a card." + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new VolrathsShapeshifterEffect())); + + // {2}: Discard a card. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new DiscardControllerEffect(1), new ManaCostsImpl("{2}"))); + } + + public VolrathsShapeshifter(final VolrathsShapeshifter card) { + super(card); + } + + @Override + public VolrathsShapeshifter copy() { + return new VolrathsShapeshifter(this); + } +} + +class VolrathsShapeshifterEffect extends ContinuousEffectImpl { + + public VolrathsShapeshifterEffect() { + super(Duration.WhileOnBattlefield, Layer.TextChangingEffects_3, SubLayer.NA, Outcome.BecomeCreature); + } + + public VolrathsShapeshifterEffect(final VolrathsShapeshifterEffect effect) { + super(effect); + } + + @Override + public VolrathsShapeshifterEffect copy() { + return new VolrathsShapeshifterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getPlayer(source.getControllerId()).getGraveyard().getTopCard(game); + Permanent permanent = game.getPermanent(source.getSourceId()); + + if (card == null || permanent == null || !card.getCardType().contains(CardType.CREATURE)) { + return false; + } + + permanent.getPower().setValue(card.getPower().getValue()); + permanent.getToughness().setValue(card.getToughness().getValue()); + permanent.getColor().setColor(card.getColor()); + permanent.getManaCost().clear(); + permanent.getManaCost().add(card.getManaCost()); + permanent.getCardType().clear(); + permanent.setName(card.getName()); + + for (CardType type : card.getCardType()) { + if (!permanent.getCardType().contains(type)) { + permanent.getCardType().add(type); + } + } + + permanent.getSubtype().clear(); + for (String type : card.getSubtype()) { + if (!permanent.getSubtype().contains(type)) { + permanent.getSubtype().add(type); + } + } + + permanent.getSupertype().clear(); + for (String type : card.getSupertype()) { + if (!permanent.getSupertype().contains(type)) { + permanent.getSupertype().add(type); + } + } + + for (Ability ability : card.getAbilities()) { + if (!permanent.getAbilities().contains(ability)) { + permanent.addAbility(ability, source.getSourceId(), game); + } + } + + return true; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/VolrathsShapshifterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/VolrathsShapshifterTest.java new file mode 100644 index 00000000000..47f708c9ff9 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/VolrathsShapshifterTest.java @@ -0,0 +1,97 @@ +package org.mage.test.cards.copy; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; + +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * Volrath's Shapeshifter + * + * As long as the top card of your graveyard is a creature card, + * Volrath's Shapeshifter has the full text of that card and has the + * text "2: Discard a card." (Volrath's Shapeshifter has that card's name, + * mana cost, color, types, abilities, power, and toughness.) + * + */ +public class VolrathsShapshifterTest extends CardTestPlayerBase { + + /** + * Tests copy simple creature + */ + @Test + public void testCopySimpleCreature() { + addCard(Zone.BATTLEFIELD, playerA, "Volrath's Shapeshifter", 1); + + // Flying 3/2 + addCard(Zone.GRAVEYARD, playerA, "Assault Griffin", 1); + addCard(Zone.LIBRARY, playerA, "Forest", 1); + skipInitShuffling(); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Assault Griffin", 1); + assertPowerToughness(playerA, "Assault Griffin", 3, 2); + + Permanent shapeshifter = getPermanent("Assault Griffin", playerA.getId()); + Assert.assertTrue(shapeshifter.getSubtype().contains("Griffin")); + Assert.assertTrue("Volrath's Shapeshifter must have flying", shapeshifter.getAbilities().contains(FlyingAbility.getInstance())); + boolean hasShapeshifterOriginalAbility = false; + for (Ability ability : shapeshifter.getAbilities()) { + if(ability instanceof SimpleActivatedAbility) { + SimpleActivatedAbility simpleActivatedAbility = (SimpleActivatedAbility)ability; + hasShapeshifterOriginalAbility = simpleActivatedAbility.getZone() == Zone.BATTLEFIELD && simpleActivatedAbility.getEffects().size() == 1 && + simpleActivatedAbility.getEffects().get(0) instanceof DiscardControllerEffect && simpleActivatedAbility.getManaCosts().size() == 1 + && simpleActivatedAbility.getManaCosts().get(0) instanceof GenericManaCost && simpleActivatedAbility.getManaCosts().get(0).convertedManaCost() == 2; + } + } + Assert.assertTrue("Volrath's Shapeshifter must have {2} : Discard a card", hasShapeshifterOriginalAbility); + } + + /** + * Tests turing back into Volrath's Shapeshifter after a new card is put on top that isn't a creature + */ + @Test + public void testLosingCopy() { + addCard(Zone.BATTLEFIELD, playerA, "Volrath's Shapeshifter", 1); + // Codex Shredder - Artifact + // {T}: Target player puts the top card of his or her library into his or her graveyard. + // {5}, {T}, Sacrifice Codex Shredder: Return target card from your graveyard to your hand. + addCard(Zone.BATTLEFIELD, playerA, "Codex Shredder", 1); + + // Flying 3/2 + addCard(Zone.GRAVEYARD, playerA, "Assault Griffin", 1); + addCard(Zone.LIBRARY, playerA, "Forest", 1); + skipInitShuffling(); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card of his or her library into his or her graveyard.", playerA); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Volrath's Shapeshifter", 1); + assertPowerToughness(playerA, "Volrath's Shapeshifter", 0, 1); + + Permanent shapeshifter = getPermanent("Volrath's Shapeshifter", playerA.getId()); + Assert.assertTrue(shapeshifter.getSubtype().contains("Shapeshifter")); + boolean hasShapeshifterOriginalAbility = false; + for (Ability ability : shapeshifter.getAbilities()) { + if(ability instanceof SimpleActivatedAbility) { + SimpleActivatedAbility simpleActivatedAbility = (SimpleActivatedAbility)ability; + hasShapeshifterOriginalAbility = simpleActivatedAbility.getZone() == Zone.BATTLEFIELD && simpleActivatedAbility.getEffects().size() == 1 && + simpleActivatedAbility.getEffects().get(0) instanceof DiscardControllerEffect && simpleActivatedAbility.getManaCosts().size() == 1 + && simpleActivatedAbility.getManaCosts().get(0) instanceof GenericManaCost && simpleActivatedAbility.getManaCosts().get(0).convertedManaCost() == 2; + } + } + Assert.assertTrue("Volrath's Shapeshifter must have {2} : Discard a card", hasShapeshifterOriginalAbility); + } +} diff --git a/Mage/src/mage/abilities/effects/common/discard/DiscardControllerEffect.java b/Mage/src/mage/abilities/effects/common/discard/DiscardControllerEffect.java index 982ed443e5c..da0da5f3cba 100644 --- a/Mage/src/mage/abilities/effects/common/discard/DiscardControllerEffect.java +++ b/Mage/src/mage/abilities/effects/common/discard/DiscardControllerEffect.java @@ -90,7 +90,7 @@ public class DiscardControllerEffect extends OneShotEffect { } } } else { - player.discard(amount.calculate(game, source, this), source, game); + player.discard(amount.calculate(game, source, this), false, source, game); result = true; } } diff --git a/Mage/src/mage/game/Graveyard.java b/Mage/src/mage/game/Graveyard.java new file mode 100644 index 00000000000..40fbbb52df0 --- /dev/null +++ b/Mage/src/mage/game/Graveyard.java @@ -0,0 +1,32 @@ +package mage.game; + +import java.util.UUID; + +import mage.cards.Card; +import mage.cards.CardsImpl; +import mage.constants.Zone; + +public class Graveyard extends CardsImpl { + + public Graveyard() { + super(Zone.GRAVEYARD); + } + + public Graveyard(final Graveyard graveyard) { + super(graveyard); + } + + @Override + public Graveyard copy() { + return new Graveyard(this); + } + + public Card getTopCard(Game game) { + UUID card = null; + for (UUID cardId : this) { + card = cardId; + } + return card != null ? game.getCard(card) : null; + } + +} diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 22ed5a5218d..f9cbb0666c7 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -28,9 +28,22 @@ package mage.players; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + import mage.MageItem; import mage.MageObject; -import mage.abilities.*; +import mage.abilities.Abilities; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.Mode; +import mage.abilities.Modes; +import mage.abilities.SpellAbility; +import mage.abilities.TriggeredAbility; import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.ManaCost; @@ -40,14 +53,18 @@ import mage.cards.decks.Deck; import mage.choices.Choice; import mage.constants.ManaType; import mage.constants.Outcome; +import mage.constants.PlayerAction; import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.counters.Counter; import mage.counters.Counters; import mage.game.Game; +import mage.game.Graveyard; import mage.game.Table; +import mage.game.combat.CombatGroup; import mage.game.draft.Draft; import mage.game.match.Match; +import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.tournament.Tournament; import mage.players.net.UserData; @@ -57,12 +74,6 @@ import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; import mage.util.Copyable; -import java.io.Serializable; -import java.util.*; -import mage.constants.PlayerAction; -import mage.game.combat.CombatGroup; -import mage.game.match.MatchPlayer; - /** * * @author BetaSteward_at_googlemail.com @@ -74,7 +85,7 @@ public interface Player extends MageItem, Copyable { RangeOfInfluence getRange(); Library getLibrary(); Cards getSideboard(); - Cards getGraveyard(); + Graveyard getGraveyard(); Abilities getAbilities(); void addAbility(Ability ability); Counters getCounters(); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 6f6a2753cbe..9307ff0d293 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -41,6 +41,7 @@ import java.util.Map.Entry; import java.util.Random; import java.util.Set; import java.util.UUID; + import mage.MageObject; import mage.Mana; import mage.abilities.Abilities; @@ -88,14 +89,6 @@ import mage.constants.ManaType; import mage.constants.Outcome; import mage.constants.PhaseStep; import mage.constants.PlayerAction; -import static mage.constants.PlayerAction.PASS_PRIORITY_CANCEL_ALL_ACTIONS; -import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_MY_NEXT_TURN; -import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_NEXT_MAIN_PHASE; -import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_NEXT_TURN; -import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_STACK_RESOLVED; -import static mage.constants.PlayerAction.PASS_PRIORITY_UNTIL_TURN_END_STEP; -import static mage.constants.PlayerAction.PERMISSION_REQUESTS_ALLOWED_OFF; -import static mage.constants.PlayerAction.PERMISSION_REQUESTS_ALLOWED_ON; import mage.constants.RangeOfInfluence; import mage.constants.SpellAbilityType; import mage.constants.TimingRule; @@ -111,6 +104,7 @@ import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.ExileZone; import mage.game.Game; +import mage.game.Graveyard; import mage.game.Table; import mage.game.combat.CombatGroup; import mage.game.command.CommandObject; @@ -134,6 +128,7 @@ import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetDiscard; import mage.util.CardUtil; import mage.watchers.common.BloodthirstWatcher; + import org.apache.log4j.Logger; public abstract class PlayerImpl implements Player, Serializable { @@ -156,7 +151,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected Library library; protected Cards sideboard; protected Cards hand; - protected Cards graveyard; + protected Graveyard graveyard; protected UUID commanderId; protected Abilities abilities; protected Counters counters; @@ -245,7 +240,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.name = name; this.range = range; hand = new CardsImpl(Zone.HAND); - graveyard = new CardsImpl(Zone.GRAVEYARD); + graveyard = new Graveyard(); abilities = new AbilitiesImpl<>(); counters = new Counters(); manaPool = new ManaPool(playerId); @@ -1485,7 +1480,7 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public Cards getGraveyard() { + public Graveyard getGraveyard() { return graveyard; }