From a5967d9b2a7de813c3d331a5d6d69552a160ec8b Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 19 Apr 2015 09:13:58 +0200 Subject: [PATCH 1/7] Added another test. --- .../src/mage/sets/gatecrash/MimingSlime.java | 236 +++++++++--------- .../src/mage/sets/gatecrash/OozeFlux.java | 220 ++++++++-------- .../activated/PutOntoBattlefieldTest.java | 51 ++++ .../java/org/mage/test/player/TestPlayer.java | 12 + 4 files changed, 289 insertions(+), 230 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/PutOntoBattlefieldTest.java diff --git a/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java b/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java index 1b209d6186..a6c1216c76 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java +++ b/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java @@ -1,119 +1,117 @@ -/* - * 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.gatecrash; - -import java.util.List; -import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.cards.CardImpl; -import mage.filter.common.FilterCreaturePermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.game.permanent.token.Token; -import mage.players.Player; - -/** - * - * @author LevelX2 - */ -public class MimingSlime extends CardImpl { - - public MimingSlime(UUID ownerId) { - super(ownerId, 126, "Miming Slime", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{2}{G}"); - this.expansionSetCode = "GTC"; - - this.color.setGreen(true); - - // Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control. - this.getSpellAbility().addEffect(new MimingSlimeEffect()); - } - - public MimingSlime(final MimingSlime card) { - super(card); - } - - @Override - public MimingSlime copy() { - return new MimingSlime(this); - } -} - -class MimingSlimeEffect extends OneShotEffect { - - public MimingSlimeEffect() { - super(Outcome.PutCreatureInPlay); - staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control"; - } - - public MimingSlimeEffect(final MimingSlimeEffect effect) { - super(effect); - } - - @Override - public MimingSlimeEffect copy() { - return new MimingSlimeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - List creatures = game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game); - int amount = 0; - for (Permanent creature : creatures) { - int power = creature.getPower().getValue(); - if (amount < power) { - amount = power; - } - } - OozeToken oozeToken = new OozeToken(); - oozeToken.getPower().initValue(amount); - oozeToken.getToughness().initValue(amount); - oozeToken.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); - return true; - } - return false; - } -} - -class OozeToken extends Token { - public OozeToken() { - super("Ooze", "X/X green Ooze creature token"); - cardType.add(CardType.CREATURE); - subtype.add("Ooze"); - color.setGreen(true); - power = new MageInt(0); - toughness = new MageInt(0); - setOriginalExpansionSetCode("RTR"); - } -} +/* + * 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.gatecrash; + +import java.util.List; +import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.Token; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class MimingSlime extends CardImpl { + + public MimingSlime(UUID ownerId) { + super(ownerId, 126, "Miming Slime", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{2}{G}"); + this.expansionSetCode = "GTC"; + + // Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control. + this.getSpellAbility().addEffect(new MimingSlimeEffect()); + } + + public MimingSlime(final MimingSlime card) { + super(card); + } + + @Override + public MimingSlime copy() { + return new MimingSlime(this); + } +} + +class MimingSlimeEffect extends OneShotEffect { + + public MimingSlimeEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control"; + } + + public MimingSlimeEffect(final MimingSlimeEffect effect) { + super(effect); + } + + @Override + public MimingSlimeEffect copy() { + return new MimingSlimeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + List creatures = game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game); + int amount = 0; + for (Permanent creature : creatures) { + int power = creature.getPower().getValue(); + if (amount < power) { + amount = power; + } + } + OozeToken oozeToken = new OozeToken(); + oozeToken.getPower().initValue(amount); + oozeToken.getToughness().initValue(amount); + oozeToken.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); + return true; + } + return false; + } +} + +class OozeToken extends Token { + public OozeToken() { + super("Ooze", "X/X green Ooze creature token"); + cardType.add(CardType.CREATURE); + subtype.add("Ooze"); + color.setGreen(true); + power = new MageInt(0); + toughness = new MageInt(0); + setOriginalExpansionSetCode("RTR"); + } +} diff --git a/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java b/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java index 99deed4389..8fdb455bce 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java +++ b/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java @@ -1,111 +1,109 @@ -/* - * 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.gatecrash; - -import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.constants.Zone; -import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.RemoveVariableCountersTargetCost; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; -import mage.cards.CardImpl; -import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.game.Game; -import mage.game.permanent.token.Token; - -/** - * - * @author LevelX2 - */ -public class OozeFlux extends CardImpl { - - public OozeFlux(UUID ownerId) { - super(ownerId, 128, "Ooze Flux", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); - this.expansionSetCode = "GTC"; - - this.color.setGreen(true); - - // {1}{G}, Remove one or more +1/+1 counters from among creatures you control: Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new OozeFluxCreateTokenEffect(new OozeToken()),new ManaCostsImpl("{1}{G}")); - ability.addCost(new RemoveVariableCountersTargetCost(new FilterControlledCreaturePermanent("creatures you control"), CounterType.P1P1, "one or more", 1)); - this.addAbility(ability); - } - - public OozeFlux(final OozeFlux card) { - super(card); - } - - @Override - public OozeFlux copy() { - return new OozeFlux(this); - } -} - -class OozeFluxCreateTokenEffect extends OneShotEffect { - - private Token token; - - public OozeFluxCreateTokenEffect(Token token) { - super(Outcome.PutCreatureInPlay); - this.token = token; - staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way"; - } - - public OozeFluxCreateTokenEffect(final OozeFluxCreateTokenEffect effect) { - super(effect); - this.token = effect.token.copy(); - } - - @Override - public OozeFluxCreateTokenEffect copy() { - return new OozeFluxCreateTokenEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - int xValue = 0; - for (Cost cost : source.getCosts()) { - if (cost instanceof RemoveVariableCountersTargetCost) { - xValue = ((RemoveVariableCountersTargetCost) cost).getAmount(); - break; - } - } - Token tokenCopy = token.copy(); - tokenCopy.getAbilities().newId(); - tokenCopy.getPower().initValue(xValue); - tokenCopy.getToughness().initValue(xValue); - tokenCopy.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); - return true; - } -} +/* + * 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.gatecrash; + +import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.RemoveVariableCountersTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.token.Token; + +/** + * + * @author LevelX2 + */ +public class OozeFlux extends CardImpl { + + public OozeFlux(UUID ownerId) { + super(ownerId, 128, "Ooze Flux", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + this.expansionSetCode = "GTC"; + + // {1}{G}, Remove one or more +1/+1 counters from among creatures you control: Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new OozeFluxCreateTokenEffect(new OozeToken()),new ManaCostsImpl("{1}{G}")); + ability.addCost(new RemoveVariableCountersTargetCost(new FilterControlledCreaturePermanent("creatures you control"), CounterType.P1P1, "one or more", 1)); + this.addAbility(ability); + } + + public OozeFlux(final OozeFlux card) { + super(card); + } + + @Override + public OozeFlux copy() { + return new OozeFlux(this); + } +} + +class OozeFluxCreateTokenEffect extends OneShotEffect { + + private final Token token; + + public OozeFluxCreateTokenEffect(Token token) { + super(Outcome.PutCreatureInPlay); + this.token = token; + staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way"; + } + + public OozeFluxCreateTokenEffect(final OozeFluxCreateTokenEffect effect) { + super(effect); + this.token = effect.token.copy(); + } + + @Override + public OozeFluxCreateTokenEffect copy() { + return new OozeFluxCreateTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xValue = 0; + for (Cost cost : source.getCosts()) { + if (cost instanceof RemoveVariableCountersTargetCost) { + xValue = ((RemoveVariableCountersTargetCost) cost).getAmount(); + break; + } + } + Token tokenCopy = token.copy(); + tokenCopy.getAbilities().newId(); + tokenCopy.getPower().initValue(xValue); + tokenCopy.getToughness().initValue(xValue); + tokenCopy.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); + return true; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/PutOntoBattlefieldTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/PutOntoBattlefieldTest.java new file mode 100644 index 0000000000..1ab753078b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/PutOntoBattlefieldTest.java @@ -0,0 +1,51 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.abilities.activated; + +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; + +/** + * + * @author LevelX2 + */ + +public class PutOntoBattlefieldTest extends CardTestPlayerBase { + + /** + * Tests to put a token onto the battlefield + */ + @Test + public void testOozeFlux() { + // Enchantment + // {1}{G}, Remove one or more +1/+1 counters from among creatures you control: Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Ooze Flux"); + // Trample + // Kalonian Hydra enters the battlefield with four +1/+1 counters on it. + // Whenever Kalonian Hydra attacks, double the number of +1/+1 counters on each creature you control. + addCard(Zone.BATTLEFIELD, playerA, "Kalonian Hydra"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{G},"); + setChoice(playerA, "X=2"); // Remove how many + setChoice(playerA,"Kalonian Hydra"); + setChoice(playerA, "X=2"); // Remove from Hydra + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Kalonian Hydra", 2, 2); + assertPermanentCount(playerA, "Ooze", 1); + assertPowerToughness(playerA, "Ooze", 2, 2); + + } + + +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 1e9ba488b0..a3811e1bfd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -502,6 +502,18 @@ public class TestPlayer extends ComputerPlayer { return super.announceXCost(min, max, message, game, ability, null); } + @Override + public int getAmount(int min, int max, String message, Game game) { + if (!choices.isEmpty()) { + if (choices.get(0).startsWith("X=")) { + int xValue = Integer.parseInt(choices.get(0).substring(2)); + choices.remove(0); + return xValue; + } + } + return super.getAmount(min, max, message, game); + } + protected Permanent findPermanent(FilterPermanent filter, UUID controllerId, Game game) { List permanents = game.getBattlefield().getAllActivePermanents(filter, controllerId, game); if (permanents.size() > 0) { From 568f62c66f0b3bc6ff80dfa34ff2c55dba0983b1 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 19 Apr 2015 10:43:12 +0200 Subject: [PATCH 2/7] Some changes to restrained event handling (simultaneous events) (fixes #897). --- .../championsofkamigawa/SiftThroughSands.java | 291 ++-- .../sets/darkascension/DungeonGeists.java | 59 +- .../test/cards/single/DungeonGeistsTest.java | 8 +- .../triggers/state/SynodCenturionTest.java | 66 + Mage/src/mage/abilities/AbilityImpl.java | 10 +- Mage/src/mage/cards/CardImpl.java | 1518 ++++++++--------- .../mage/game/permanent/PermanentToken.java | 2 +- Mage/src/mage/game/stack/Spell.java | 4 +- 8 files changed, 1023 insertions(+), 935 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/SynodCenturionTest.java diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java b/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java index f6050c8ffd..520cb44c28 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java @@ -1,145 +1,146 @@ -/* - * 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.championsofkamigawa; - -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalOneShotEffect; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.discard.DiscardControllerEffect; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; -import mage.cards.CardImpl; -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.constants.WatcherScope; -import mage.filter.common.FilterCreatureCard; -import mage.filter.predicate.mageobject.NamePredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.stack.Spell; -import mage.target.common.TargetCardInLibrary; -import mage.watchers.Watcher; - -/** - * - * @author LevelX2 - */ -public class SiftThroughSands extends CardImpl { - - private static final String rule = "If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library"; - private static final FilterCreatureCard filter = new FilterCreatureCard("a card named The Unspeakable"); - static { - filter.add(new NamePredicate("The Unspeakable")); - } - - public SiftThroughSands(UUID ownerId) { - super(ownerId, 84, "Sift Through Sands", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}{U}"); - this.expansionSetCode = "CHK"; - this.subtype.add("Arcane"); - - this.color.setBlue(true); - - // Draw two cards, then discard a card. - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); - Effect effect = new DiscardControllerEffect(1); - effect.setText(", then discard a card"); - this.getSpellAbility().addEffect(effect); - // If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library. - this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), false, true), new SiftThroughSandsCondition(), rule)); - this.getSpellAbility().addWatcher(new SiftThroughSandsWatcher()); - } - - public SiftThroughSands(final SiftThroughSands card) { - super(card); - } - - @Override - public SiftThroughSands copy() { - return new SiftThroughSands(this); - } -} - -class SiftThroughSandsCondition implements Condition { - - @Override - public boolean apply(Game game, Ability source) { - SiftThroughSandsWatcher watcher = (SiftThroughSandsWatcher) game.getState().getWatchers().get("SiftThroughSandsWatcher", source.getControllerId()); - if (watcher != null) { - return watcher.conditionMet(); - } - return false; - } -} - -class SiftThroughSandsWatcher extends Watcher { - - boolean castPeerThroughDepths = false; - boolean castReachThroughMists = false; - - public SiftThroughSandsWatcher() { - super("SiftThroughSandsWatcher", WatcherScope.PLAYER); - } - - public SiftThroughSandsWatcher(final SiftThroughSandsWatcher watcher) { - super(watcher); - this.castPeerThroughDepths = watcher.castPeerThroughDepths; - this.castReachThroughMists = watcher.castReachThroughMists; - } - - @Override - public SiftThroughSandsWatcher copy() { - return new SiftThroughSandsWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (condition == true) { //no need to check - condition has already occured - return; - } - if (event.getType() == EventType.SPELL_CAST - && controllerId == event.getPlayerId()) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell.getCard().getName().equals("Peer Through Depths")) { - castPeerThroughDepths = true; - } else if (spell.getCard().getName().equals("Reach Through Mists")) { - castReachThroughMists = true; - } - condition = castPeerThroughDepths && castReachThroughMists; - } - } - - @Override - public void reset() { - super.reset(); - this.castPeerThroughDepths = false; - this.castReachThroughMists = false; - } -} +/* + * 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.championsofkamigawa; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.WatcherScope; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.stack.Spell; +import mage.target.common.TargetCardInLibrary; +import mage.watchers.Watcher; + +/** + * + * @author LevelX2 + */ +public class SiftThroughSands extends CardImpl { + + private static final String rule = "If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library"; + private static final FilterCreatureCard filter = new FilterCreatureCard("a card named The Unspeakable"); + static { + filter.add(new NamePredicate("The Unspeakable")); + } + + public SiftThroughSands(UUID ownerId) { + super(ownerId, 84, "Sift Through Sands", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}{U}"); + this.expansionSetCode = "CHK"; + this.subtype.add("Arcane"); + + this.color.setBlue(true); + + // Draw two cards, then discard a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + Effect effect = new DiscardControllerEffect(1); + effect.setText(", then discard a card"); + this.getSpellAbility().addEffect(effect); + + // If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), false, true), new SiftThroughSandsCondition(), rule)); + this.getSpellAbility().addWatcher(new SiftThroughSandsWatcher()); + } + + public SiftThroughSands(final SiftThroughSands card) { + super(card); + } + + @Override + public SiftThroughSands copy() { + return new SiftThroughSands(this); + } +} + +class SiftThroughSandsCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + SiftThroughSandsWatcher watcher = (SiftThroughSandsWatcher) game.getState().getWatchers().get("SiftThroughSandsWatcher", source.getControllerId()); + if (watcher != null) { + return watcher.conditionMet(); + } + return false; + } +} + +class SiftThroughSandsWatcher extends Watcher { + + boolean castPeerThroughDepths = false; + boolean castReachThroughMists = false; + + public SiftThroughSandsWatcher() { + super("SiftThroughSandsWatcher", WatcherScope.PLAYER); + } + + public SiftThroughSandsWatcher(final SiftThroughSandsWatcher watcher) { + super(watcher); + this.castPeerThroughDepths = watcher.castPeerThroughDepths; + this.castReachThroughMists = watcher.castReachThroughMists; + } + + @Override + public SiftThroughSandsWatcher copy() { + return new SiftThroughSandsWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + if (condition == true) { //no need to check - condition has already occured + return; + } + if (event.getType() == EventType.SPELL_CAST + && controllerId == event.getPlayerId()) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell.getCard().getName().equals("Peer Through Depths")) { + castPeerThroughDepths = true; + } else if (spell.getCard().getName().equals("Reach Through Mists")) { + castReachThroughMists = true; + } + condition = castPeerThroughDepths && castReachThroughMists; + } + } + + @Override + public void reset() { + super.reset(); + this.castPeerThroughDepths = false; + this.castReachThroughMists = false; + } +} diff --git a/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java b/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java index 730543b46f..e7578f9772 100644 --- a/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java +++ b/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java @@ -70,7 +70,6 @@ public class DungeonGeists extends CardImpl { this.expansionSetCode = "DKA"; this.subtype.add("Spirit"); - this.color.setBlue(true); this.power = new MageInt(3); this.toughness = new MageInt(3); @@ -112,40 +111,52 @@ class DungeonGeistsEffect extends ContinuousRuleModifyingEffectImpl { } @Override - public boolean apply(Game game, Ability source) { - return false; + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.UNTAP || event.getType() == GameEvent.EventType.ZONE_CHANGE || event.getType() == GameEvent.EventType.LOST_CONTROL; } + @Override public boolean applies(GameEvent event, Ability source, Game game) { // Source must be on the battlefield (it's neccessary to check here because if as response to the enter // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL // event will happen before this effect is applied ever) - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + Permanent sourcePermanent = (Permanent) source.getSourceObjectIfItStillExists(game); if (sourcePermanent == null || !sourcePermanent.getControllerId().equals(source.getControllerId())) { - this.used = true; + discard(); return false; } - if (event.getType() == GameEvent.EventType.LOST_CONTROL) { - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } + switch(event.getType()) { + case ZONE_CHANGE: + // end effect if source does a zone move + if (event.getTargetId().equals(source.getSourceId())) { + ZoneChangeEvent zEvent = (ZoneChangeEvent)event; + if (zEvent.getFromZone() == Zone.BATTLEFIELD) { + discard(); + return false; + } + } + break; + case UNTAP: + // prevent to untap the target creature + if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getTargetId().equals(targetPointer.getFirst(game, source))) { + Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + if (targetCreature != null) { + return targetCreature.getControllerId().equals(game.getActivePlayerId()); + } else { + discard(); + return false; + } + } + break; + case LOST_CONTROL: + // end effect if source control is changed + if (event.getTargetId().equals(source.getSourceId())) { + discard(); + return false; + } + break; } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent)event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - } - - if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == GameEvent.EventType.UNTAP) { - if (event.getTargetId().equals(targetPointer.getFirst(game, source))) { - return true; - } - } - return false; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java index 6e53c7e1d1..07b27ebbda 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java @@ -74,16 +74,20 @@ public class DungeonGeistsTest extends CardTestPlayerBase { public void testWithBlink() { addCard(Zone.BATTLEFIELD, playerA, "Island", 4); addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + // When Dungeon Geists enters the battlefield, tap target creature an opponent controls. + // That creature doesn't untap during its controller's untap step for as long as you control Dungeon Geists. addCard(Zone.HAND, playerA, "Dungeon Geists"); addCard(Zone.HAND, playerA, "Cloudshift"); addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm"); addCard(Zone.BATTLEFIELD, playerB, "Elite Vanguard"); - addTarget(playerA, "Craw Wurm"); // first target Craw Wurm - addTarget(playerA, "Elite Vanguard"); // after Cloudshift effect (return back to battlefield) target Elite Vanguard castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dungeon Geists"); + addTarget(playerA, "Craw Wurm"); // first target Craw Wurm + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cloudshift", "Dungeon Geists"); + addTarget(playerA, "Elite Vanguard"); // after Cloudshift effect (return back to battlefield) target Elite Vanguard + setStopAt(2, PhaseStep.DRAW); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/SynodCenturionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/SynodCenturionTest.java new file mode 100644 index 0000000000..fee51299c4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/SynodCenturionTest.java @@ -0,0 +1,66 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.triggers.state; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class SynodCenturionTest extends CardTestPlayerBase { + + + /** + * Check that Synod Centurion gets sacrificed if no other artifacts are on the battlefield + * + */ + @Test + public void testAlone() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + addCard(Zone.BATTLEFIELD, playerA, "Demon's Horn"); + addCard(Zone.HAND, playerA, "Shatter"); + addCard(Zone.HAND, playerA, "Synod Centurion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Synod Centurion"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Shatter", "Demon's Horn"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Demon's Horn", 1); + assertGraveyardCount(playerA, "Shatter", 1); + assertGraveyardCount(playerA, "Synod Centurion", 1); + } + + /** + * Check that Synod Centurion gets sacrificed if the only other + * artifact left the battlefiled for a short time + * + */ + @Test + public void testWithFlicker() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.BATTLEFIELD, playerA, "Bottle Gnomes"); + addCard(Zone.HAND, playerA, "Cloudshift"); + addCard(Zone.HAND, playerA, "Synod Centurion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Synod Centurion"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cloudshift", "Bottle Gnomes"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Bottle Gnomes", 1); + assertGraveyardCount(playerA, "Cloudshift", 1); + assertGraveyardCount(playerA, "Synod Centurion", 1); + } +} \ No newline at end of file diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index ccef814220..3ad6958cd7 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -215,14 +215,20 @@ public abstract class AbilityImpl implements Ability { else { game.addEffect((ContinuousEffect) effect, this); } + /** + * All restrained trigger events are fired now. + * To restrain the events is mainly neccessary because of the movement of multiple object at once. + * If the event is fired directly as one object moved, other objects are not already in the correct zone + * to check for their effects. (e.g. Valakut, the Molten Pinnacle) + */ + game.getState().handleSimultaneousEvent(game); + game.resetShortLivingLKI(); /** * game.applyEffects() has to be done at least for every effect that moves cards/permanent between zones, * so Static effects work as intened if dependant from the moved objects zone it is in * Otherwise for example were static abilities with replacement effects deactivated to late * Example: {@link org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy} */ -// game.applyEffects(); - // some effects must be applied before next effect is resolved, because effect is dependend. if (effect.applyEffectsAfter()) { game.applyEffects(); } diff --git a/Mage/src/mage/cards/CardImpl.java b/Mage/src/mage/cards/CardImpl.java index 71fc93c6cd..d8a295896f 100644 --- a/Mage/src/mage/cards/CardImpl.java +++ b/Mage/src/mage/cards/CardImpl.java @@ -1,759 +1,759 @@ -/* -* 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.cards; - -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; -import mage.MageObject; -import mage.MageObjectImpl; -import mage.Mana; -import mage.abilities.Abilities; -import mage.abilities.AbilitiesImpl; -import mage.abilities.Ability; -import mage.abilities.PlayLandAbility; -import mage.abilities.SpellAbility; -import mage.abilities.mana.ManaAbility; -import mage.constants.CardType; -import mage.constants.ColoredManaSymbol; -import mage.constants.Rarity; -import mage.constants.SpellAbilityType; -import mage.constants.TimingRule; -import mage.constants.Zone; -import static mage.constants.Zone.BATTLEFIELD; -import static mage.constants.Zone.COMMAND; -import static mage.constants.Zone.EXILED; -import static mage.constants.Zone.GRAVEYARD; -import static mage.constants.Zone.HAND; -import static mage.constants.Zone.LIBRARY; -import static mage.constants.Zone.OUTSIDE; -import static mage.constants.Zone.PICK; -import static mage.constants.Zone.STACK; -import mage.counters.Counter; -import mage.counters.Counters; -import mage.game.CardState; -import mage.game.Game; -import mage.game.command.Commander; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.PermanentCard; -import mage.game.stack.Spell; -import mage.game.stack.StackObject; -import mage.watchers.Watcher; -import org.apache.log4j.Logger; - -public abstract class CardImpl extends MageObjectImpl implements Card { - private static final long serialVersionUID = 1L; - - private static final Logger logger = Logger.getLogger(CardImpl.class); - - protected UUID ownerId; - protected int cardNumber; - protected String expansionSetCode; - protected String tokenSetCode; - protected Rarity rarity; - protected boolean canTransform; - protected Card secondSideCard; - protected boolean nightCard; - protected SpellAbility spellAbility; - protected boolean flipCard; - protected String flipCardName; - protected boolean usesVariousArt = false; - protected boolean splitCard; - protected boolean morphCard; - - public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs) { - this(ownerId, cardNumber, name, rarity, cardTypes, costs, SpellAbilityType.BASE); - } - - public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) { - this(ownerId, name); - this.rarity = rarity; - this.cardNumber = cardNumber; - this.cardType.addAll(Arrays.asList(cardTypes)); - this.manaCost.load(costs); - setDefaultColor(); - if (cardType.contains(CardType.LAND)) { - Ability ability = new PlayLandAbility(name); - ability.setSourceId(this.getId()); - abilities.add(ability); - } - else { - SpellAbility ability = new SpellAbility(manaCost, name, Zone.HAND, spellAbilityType); - if (!cardType.contains(CardType.INSTANT)) { - ability.setTiming(TimingRule.SORCERY); - } - ability.setSourceId(this.getId()); - abilities.add(ability); - } - this.usesVariousArt = Character.isDigit(this.getClass().getName().charAt(this.getClass().getName().length()-1)); - this.morphCard = false; - } - - private void setDefaultColor() { - this.color.setWhite(this.manaCost.containsColor(ColoredManaSymbol.W)); - this.color.setBlue(this.manaCost.containsColor(ColoredManaSymbol.U)); - this.color.setBlack(this.manaCost.containsColor(ColoredManaSymbol.B)); - this.color.setRed(this.manaCost.containsColor(ColoredManaSymbol.R)); - this.color.setGreen(this.manaCost.containsColor(ColoredManaSymbol.G)); - } - - protected CardImpl(UUID ownerId, String name) { - this.ownerId = ownerId; - this.name = name; - } - - protected CardImpl(UUID id, UUID ownerId, String name) { - super(id); - this.ownerId = ownerId; - this.name = name; - } - - public CardImpl(final CardImpl card) { - super(card); - ownerId = card.ownerId; - cardNumber = card.cardNumber; - expansionSetCode = card.expansionSetCode; - rarity = card.rarity; - - canTransform = card.canTransform; - if (canTransform) { - secondSideCard = card.secondSideCard; - nightCard = card.nightCard; - } - flipCard = card.flipCard; - flipCardName = card.flipCardName; - splitCard = card.splitCard; - usesVariousArt = card.usesVariousArt; - } - - @Override - public void assignNewId() { - this.objectId = UUID.randomUUID(); - this.abilities.newOriginalId(); - this.abilities.setSourceId(objectId); - } - - public static Card createCard(String name) { - try { - return createCard(Class.forName(name)); - } catch (ClassNotFoundException ex) { - logger.fatal("Error loading card: " + name, ex); - return null; - } - } - - public static Card createCard(Class clazz) { - try { - Constructor con = clazz.getConstructor(new Class[]{UUID.class}); - Card card = (Card) con.newInstance(new Object[]{null}); - card.build(); - return card; - } catch (Exception e) { - logger.fatal("Error loading card: " + clazz.getCanonicalName(), e); - return null; - } - } - - @Override - public UUID getOwnerId() { - return ownerId; - } - - @Override - public int getCardNumber() { - return cardNumber; - } - - @Override - public Rarity getRarity() { - return rarity; - } - - @Override - public void addInfo(String key, String value, Game game) { - game.getState().getCardState(objectId).addInfo(key, value); - } - - protected static final ArrayList rulesError = new ArrayList() {{add("Exception occured in rules generation");}}; - - @Override - public List getRules() { - try { - return abilities.getRules(this.getLogName()); - } catch (Exception e) { - logger.info("Exception in rules generation for card: " + this.getName(), e); - } - return rulesError; - } - - @Override - public List getRules(Game game) { - try { - List rules = getRules(); - if (game != null) { - CardState cardState = game.getState().getCardState(objectId); - if (cardState != null) { - for (String data : cardState.getInfo().values()) { - rules.add(data); - } - for (Ability ability: cardState.getAbilities()) { - rules.add(ability.getRule()); - } - } - } - return rules; - } catch (Exception e) { - logger.error("Exception in rules generation for card: " + this.getName(), e); - } - return rulesError; - } - - /** - * Gets all base abilities - does not include additional abilities added by - * other cards or effects - * @return A list of {@link Ability} - this collection is modifiable - */ - @Override - public Abilities getAbilities() { - return super.getAbilities(); - } - - /** - * Gets all current abilities - includes additional abilities added by - * other cards or effects - * @param game - * @return A list of {@link Ability} - this collection is not modifiable - */ - @Override - public Abilities getAbilities(Game game) { - Abilities otherAbilities = game.getState().getAllOtherAbilities(objectId); - if (otherAbilities == null) { - return abilities; - } - Abilities all = new AbilitiesImpl<>(); - all.addAll(abilities); - all.addAll(otherAbilities); - return all; - } - - protected void addAbility(Ability ability) { - ability.setSourceId(this.getId()); - abilities.add(ability); - for (Ability subAbility: ability.getSubAbilities()) { - abilities.add(subAbility); - } - } - - protected void addAbilities(List abilities) { - for (Ability ability: abilities) { - addAbility(ability); - } - } - - protected void addAbility(Ability ability, Watcher watcher) { - addAbility(ability); - ability.addWatcher(watcher); - } - - @Override - public SpellAbility getSpellAbility() { - if (spellAbility == null) { - for (Ability ability : abilities.getActivatedAbilities(Zone.HAND)) { - // name check prevents that alternate casting methods (like "cast [card name] using bestow") are returned here - if (ability instanceof SpellAbility && ability.toString().endsWith(getName())) { - spellAbility = (SpellAbility) ability; - } - } - } - return spellAbility; - } - - @Override - public void setOwnerId(UUID ownerId) { - this.ownerId = ownerId; - abilities.setControllerId(ownerId); - } - - @Override - public String getExpansionSetCode() { - return expansionSetCode; - } - - @Override - public String getTokenSetCode() { - return tokenSetCode; - } - - @Override - public List getMana() { - List mana = new ArrayList<>(); - for (ManaAbility ability : this.abilities.getManaAbilities(Zone.BATTLEFIELD)) { - for (Mana netMana: ability.getNetMana(null)) { - mana.add(netMana); - } - } - return mana; - } - - @Override - public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag) { - return this.moveToZone(toZone, sourceId, game, flag, null); - } - - @Override - public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { - Zone fromZone = game.getState().getZone(objectId); - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, toZone, appliedEffects); - if (!game.replaceEvent(event)) { - if (event.getFromZone() != null) { - switch (event.getFromZone()) { - case GRAVEYARD: - game.getPlayer(ownerId).removeFromGraveyard(this, game); - break; - case HAND: - game.getPlayer(ownerId).removeFromHand(this, game); - break; - case LIBRARY: - game.getPlayer(ownerId).removeFromLibrary(this, game); - break; - case EXILED: - game.getExile().removeCard(this, game); - break; - case OUTSIDE: - game.getPlayer(ownerId).getSideboard().remove(this); - break; - case COMMAND: - game.getState().getCommand().remove((Commander)game.getObject(objectId)); - break; - case STACK: - StackObject stackObject = game.getStack().getSpell(getId()); - if (stackObject != null) { - game.getStack().remove(stackObject); - } - break; - case PICK: - case BATTLEFIELD: // for sacrificing permanents or putting to library - break; - default: - Card sourceCard = game.getCard(sourceId); - logger.fatal(new StringBuilder("Invalid from zone [").append(fromZone) - .append("] for card [").append(this.getName()) - .append("] to zone [").append(toZone) - .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); - break; - } - game.rememberLKI(objectId, event.getFromZone(), this); - } - - setFaceDown(false, game); - updateZoneChangeCounter(game); - switch (event.getToZone()) { - case GRAVEYARD: - game.getPlayer(ownerId).putInGraveyard(this, game, !flag); - break; - case HAND: - game.getPlayer(ownerId).getHand().add(this); - break; - case STACK: - game.getStack().push(new Spell(this, this.getSpellAbility().copy(), ownerId, event.getFromZone())); - break; - case EXILED: - game.getExile().getPermanentExile().add(this); - break; - case COMMAND: - game.addCommander(new Commander(this)); - break; - case LIBRARY: - if (flag) { - game.getPlayer(ownerId).getLibrary().putOnTop(this, game); - } - else { - game.getPlayer(ownerId).getLibrary().putOnBottom(this, game); - } - break; - case BATTLEFIELD: - PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); // controller can be replaced (e.g. Gather Specimens) - game.addPermanent(permanent); - game.setZone(objectId, Zone.BATTLEFIELD); - game.setScopeRelevant(true); - game.applyEffects(); - permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); - game.setScopeRelevant(false); - game.applyEffects(); - if (flag) { - permanent.setTapped(true); - } - event.setTarget(permanent); - break; - default: - Card sourceCard = game.getCard(sourceId); - logger.fatal(new StringBuilder("Invalid to zone [").append(toZone) - .append("] for card [").append(this.getName()) - .append("] to zone [").append(toZone) - .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); - return false; - } - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return game.getState().getZone(objectId) == toZone; - } - return false; - } - - @Override - public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { - Card mainCard = getMainCard(); - ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK); - if (!game.replaceEvent(event)) { - if (event.getFromZone() != null) { - switch (event.getFromZone()) { - case GRAVEYARD: - game.getPlayer(ownerId).removeFromGraveyard(mainCard, game); - break; - case HAND: - game.getPlayer(ownerId).removeFromHand(mainCard, game); - break; - case LIBRARY: - game.getPlayer(ownerId).removeFromLibrary(mainCard, game); - break; - case EXILED: - game.getExile().removeCard(mainCard, game); - break; - case OUTSIDE: - game.getPlayer(ownerId).getSideboard().remove(mainCard); - break; - - case COMMAND: - game.getState().getCommand().remove((Commander)game.getObject(mainCard.getId())); - break; - default: - //logger.warning("moveToZone, not fully implemented: from="+event.getFromZone() + ", to="+event.getToZone()); - } - game.rememberLKI(mainCard.getId(), event.getFromZone(), this); - } - game.getStack().push(new Spell(this, ability.copy(), controllerId, event.getFromZone())); - setZone(event.getToZone(), game); - game.fireEvent(event); - return game.getState().getZone(mainCard.getId()) == Zone.STACK; - } - return false; - } - @Override - public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { - return moveToExile(exileId, name, sourceId, game, null); - } - - @Override - public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { - Zone fromZone = game.getState().getZone(objectId); - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); - if (!game.replaceEvent(event)) { - if (fromZone != null) { - switch (fromZone) { - case GRAVEYARD: - game.getPlayer(ownerId).removeFromGraveyard(this, game); - break; - case HAND: - game.getPlayer(ownerId).removeFromHand(this, game); - break; - case LIBRARY: - game.getPlayer(ownerId).removeFromLibrary(this, game); - break; - case EXILED: - game.getExile().removeCard(this, game); - break; - case STACK: - case PICK: - // nothing to do - break; - default: - MageObject object = game.getObject(sourceId); - logger.warn(new StringBuilder("moveToExile, not fully implemented: from = ").append(fromZone).append(" - ").append(object != null ? object.getName():"null")); - } - game.rememberLKI(objectId, event.getFromZone(), this); - } - - if (exileId == null) { - game.getExile().getPermanentExile().add(this); - } else { - game.getExile().createZone(exileId, name).add(this); - } - setFaceDown(false, game); - updateZoneChangeCounter(game); - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return true; - } - return false; - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId) { - return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, false, false, null); - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped){ - return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, false, null); - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown){ - return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, facedown, null); - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects){ - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, fromZone, Zone.BATTLEFIELD, appliedEffects, tapped); - if (facedown) { - this.setFaceDown(true, game); - } - if (!game.replaceEvent(event)) { - if (facedown) { - this.setFaceDown(false, game); - } - if (fromZone != null) { - boolean removed = false; - switch (fromZone) { - case GRAVEYARD: - removed = game.getPlayer(ownerId).removeFromGraveyard(this, game); - break; - case HAND: - removed = game.getPlayer(ownerId).removeFromHand(this, game); - break; - case LIBRARY: - removed = game.getPlayer(ownerId).removeFromLibrary(this, game); - break; - case EXILED: - game.getExile().removeCard(this, game); - removed = true; - break; - case COMMAND: - // command object (commander) is only on the stack, so no removing neccessary here - removed = true; - break; - case PICK: - removed = true; - break; - default: - logger.warn("putOntoBattlefield, not fully implemented: fromZone="+fromZone); - } - game.rememberLKI(objectId, event.getFromZone(), this); - if (!removed) { - logger.warn("Couldn't find card in fromZone, card=" + getName() + ", fromZone=" + fromZone); - } - } - updateZoneChangeCounter(game); - PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); - // make sure the controller of all continuous effects of this card are switched to the current controller - game.getContinuousEffects().setController(objectId, event.getPlayerId()); - game.addPermanent(permanent); - setZone(Zone.BATTLEFIELD, game); - game.setScopeRelevant(true); - permanent.setTapped(tapped); - permanent.setFaceDown(facedown, game); - permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); - game.setScopeRelevant(false); - game.applyEffects(); - game.fireEvent(new ZoneChangeEvent(permanent, event.getPlayerId(), fromZone, Zone.BATTLEFIELD)); - return true; - } - if (facedown) { - this.setFaceDown(false, game); - } - return false; - } - - @Override - public void setFaceDown(boolean value, Game game) { - game.getState().getCardState(objectId).setFaceDown(value); - } - - @Override - public boolean isFaceDown(Game game) { - return game.getState().getCardState(objectId).isFaceDown(); - } - - @Override - public boolean turnFaceUp(Game game, UUID playerId) { - GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEUP, getId(), playerId); - if (!game.replaceEvent(event)) { - setFaceDown(false, game); - for (Ability ability :abilities) { // abilities that were set to not visible face down must be set to visible again - if (ability.getWorksFaceDown() && !ability.getRuleVisible()) { - ability.setRuleVisible(true); - } - } - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEUP, getId(), playerId)); - return true; - } - return false; - } - - @Override - public boolean turnFaceDown(Game game, UUID playerId) { - GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEDOWN, getId(), playerId); - if (!game.replaceEvent(event)) { - setFaceDown(true, game); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEDOWN, getId(), playerId)); - return true; - } - return false; - } - - @Override - public boolean canTransform() { - return this.canTransform; - } - - @Override - public Card getSecondCardFace() { - return this.secondSideCard; - } - - @Override - public boolean isNightCard() { - return this.nightCard; - } - - @Override - public boolean isFlipCard() { - return flipCard; - } - - @Override - public String getFlipCardName() { - return flipCardName; - } - - @Override - public boolean isSplitCard() { - return splitCard; - } - - @Override - public void build() {} - - @Override - public boolean getUsesVariousArt() { - return usesVariousArt; - } - - @Override - public Counters getCounters(Game game) { - return game.getState().getCardState(this.objectId).getCounters(); - } - - @Override - public void addCounters(String name, int amount, Game game) { - addCounters(name, amount, game, null); - } - - @Override - public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { - GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, name, amount); - countersEvent.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(countersEvent)) { - for (int i = 0; i < countersEvent.getAmount(); i++) { - GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, name, 1); - event.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(event)) { - game.getState().getCardState(this.objectId).getCounters().addCounter(name, 1); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, name, 1)); - } - } - } - } - - @Override - public void addCounters(Counter counter, Game game) { - addCounters(counter, game, null); - } - - @Override - public void addCounters(Counter counter, Game game, ArrayList appliedEffects) { - GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, counter.getName(), counter.getCount()); - countersEvent.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(countersEvent)) { - int amount = countersEvent.getAmount(); - for (int i = 0; i < amount; i++) { - Counter eventCounter = counter.copy(); - eventCounter.remove(amount - 1); - GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, counter.getName(), 1); - event.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(event)) { - game.getState().getCardState(this.objectId).getCounters().addCounter(eventCounter); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, counter.getName(), 1)); - } - } - } - } - - @Override - public void removeCounters(String name, int amount, Game game) { - for (int i = 0; i < amount; i++) { - game.getState().getCardState(this.objectId).getCounters().removeCounter(name, 1); - GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, ownerId); - event.setData(name); - game.fireEvent(event); - } - } - - @Override - public void removeCounters(Counter counter, Game game) { - removeCounters(counter.getName(), counter.getCount(), game); - } - - @Override - public String getLogName() { -// if (this.isFaceDown()) { -// return "facedown card"; -// } - return name; - } - - @Override - public Card getMainCard() { - return this; - } - - @Override - public void setZone(Zone zone, Game game) { - game.setZone(getId(), zone); - } - - @Override - public void setSpellAbility(SpellAbility ability) { - spellAbility = ability; - } - -} +/* +* 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.cards; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import mage.MageObject; +import mage.MageObjectImpl; +import mage.Mana; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; +import mage.abilities.PlayLandAbility; +import mage.abilities.SpellAbility; +import mage.abilities.mana.ManaAbility; +import mage.constants.CardType; +import mage.constants.ColoredManaSymbol; +import mage.constants.Rarity; +import mage.constants.SpellAbilityType; +import mage.constants.TimingRule; +import mage.constants.Zone; +import static mage.constants.Zone.BATTLEFIELD; +import static mage.constants.Zone.COMMAND; +import static mage.constants.Zone.EXILED; +import static mage.constants.Zone.GRAVEYARD; +import static mage.constants.Zone.HAND; +import static mage.constants.Zone.LIBRARY; +import static mage.constants.Zone.OUTSIDE; +import static mage.constants.Zone.PICK; +import static mage.constants.Zone.STACK; +import mage.counters.Counter; +import mage.counters.Counters; +import mage.game.CardState; +import mage.game.Game; +import mage.game.command.Commander; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.PermanentCard; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.watchers.Watcher; +import org.apache.log4j.Logger; + +public abstract class CardImpl extends MageObjectImpl implements Card { + private static final long serialVersionUID = 1L; + + private static final Logger logger = Logger.getLogger(CardImpl.class); + + protected UUID ownerId; + protected int cardNumber; + protected String expansionSetCode; + protected String tokenSetCode; + protected Rarity rarity; + protected boolean canTransform; + protected Card secondSideCard; + protected boolean nightCard; + protected SpellAbility spellAbility; + protected boolean flipCard; + protected String flipCardName; + protected boolean usesVariousArt = false; + protected boolean splitCard; + protected boolean morphCard; + + public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs) { + this(ownerId, cardNumber, name, rarity, cardTypes, costs, SpellAbilityType.BASE); + } + + public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) { + this(ownerId, name); + this.rarity = rarity; + this.cardNumber = cardNumber; + this.cardType.addAll(Arrays.asList(cardTypes)); + this.manaCost.load(costs); + setDefaultColor(); + if (cardType.contains(CardType.LAND)) { + Ability ability = new PlayLandAbility(name); + ability.setSourceId(this.getId()); + abilities.add(ability); + } + else { + SpellAbility ability = new SpellAbility(manaCost, name, Zone.HAND, spellAbilityType); + if (!cardType.contains(CardType.INSTANT)) { + ability.setTiming(TimingRule.SORCERY); + } + ability.setSourceId(this.getId()); + abilities.add(ability); + } + this.usesVariousArt = Character.isDigit(this.getClass().getName().charAt(this.getClass().getName().length()-1)); + this.morphCard = false; + } + + private void setDefaultColor() { + this.color.setWhite(this.manaCost.containsColor(ColoredManaSymbol.W)); + this.color.setBlue(this.manaCost.containsColor(ColoredManaSymbol.U)); + this.color.setBlack(this.manaCost.containsColor(ColoredManaSymbol.B)); + this.color.setRed(this.manaCost.containsColor(ColoredManaSymbol.R)); + this.color.setGreen(this.manaCost.containsColor(ColoredManaSymbol.G)); + } + + protected CardImpl(UUID ownerId, String name) { + this.ownerId = ownerId; + this.name = name; + } + + protected CardImpl(UUID id, UUID ownerId, String name) { + super(id); + this.ownerId = ownerId; + this.name = name; + } + + public CardImpl(final CardImpl card) { + super(card); + ownerId = card.ownerId; + cardNumber = card.cardNumber; + expansionSetCode = card.expansionSetCode; + rarity = card.rarity; + + canTransform = card.canTransform; + if (canTransform) { + secondSideCard = card.secondSideCard; + nightCard = card.nightCard; + } + flipCard = card.flipCard; + flipCardName = card.flipCardName; + splitCard = card.splitCard; + usesVariousArt = card.usesVariousArt; + } + + @Override + public void assignNewId() { + this.objectId = UUID.randomUUID(); + this.abilities.newOriginalId(); + this.abilities.setSourceId(objectId); + } + + public static Card createCard(String name) { + try { + return createCard(Class.forName(name)); + } catch (ClassNotFoundException ex) { + logger.fatal("Error loading card: " + name, ex); + return null; + } + } + + public static Card createCard(Class clazz) { + try { + Constructor con = clazz.getConstructor(new Class[]{UUID.class}); + Card card = (Card) con.newInstance(new Object[]{null}); + card.build(); + return card; + } catch (Exception e) { + logger.fatal("Error loading card: " + clazz.getCanonicalName(), e); + return null; + } + } + + @Override + public UUID getOwnerId() { + return ownerId; + } + + @Override + public int getCardNumber() { + return cardNumber; + } + + @Override + public Rarity getRarity() { + return rarity; + } + + @Override + public void addInfo(String key, String value, Game game) { + game.getState().getCardState(objectId).addInfo(key, value); + } + + protected static final ArrayList rulesError = new ArrayList() {{add("Exception occured in rules generation");}}; + + @Override + public List getRules() { + try { + return abilities.getRules(this.getLogName()); + } catch (Exception e) { + logger.info("Exception in rules generation for card: " + this.getName(), e); + } + return rulesError; + } + + @Override + public List getRules(Game game) { + try { + List rules = getRules(); + if (game != null) { + CardState cardState = game.getState().getCardState(objectId); + if (cardState != null) { + for (String data : cardState.getInfo().values()) { + rules.add(data); + } + for (Ability ability: cardState.getAbilities()) { + rules.add(ability.getRule()); + } + } + } + return rules; + } catch (Exception e) { + logger.error("Exception in rules generation for card: " + this.getName(), e); + } + return rulesError; + } + + /** + * Gets all base abilities - does not include additional abilities added by + * other cards or effects + * @return A list of {@link Ability} - this collection is modifiable + */ + @Override + public Abilities getAbilities() { + return super.getAbilities(); + } + + /** + * Gets all current abilities - includes additional abilities added by + * other cards or effects + * @param game + * @return A list of {@link Ability} - this collection is not modifiable + */ + @Override + public Abilities getAbilities(Game game) { + Abilities otherAbilities = game.getState().getAllOtherAbilities(objectId); + if (otherAbilities == null) { + return abilities; + } + Abilities all = new AbilitiesImpl<>(); + all.addAll(abilities); + all.addAll(otherAbilities); + return all; + } + + protected void addAbility(Ability ability) { + ability.setSourceId(this.getId()); + abilities.add(ability); + for (Ability subAbility: ability.getSubAbilities()) { + abilities.add(subAbility); + } + } + + protected void addAbilities(List abilities) { + for (Ability ability: abilities) { + addAbility(ability); + } + } + + protected void addAbility(Ability ability, Watcher watcher) { + addAbility(ability); + ability.addWatcher(watcher); + } + + @Override + public SpellAbility getSpellAbility() { + if (spellAbility == null) { + for (Ability ability : abilities.getActivatedAbilities(Zone.HAND)) { + // name check prevents that alternate casting methods (like "cast [card name] using bestow") are returned here + if (ability instanceof SpellAbility && ability.toString().endsWith(getName())) { + spellAbility = (SpellAbility) ability; + } + } + } + return spellAbility; + } + + @Override + public void setOwnerId(UUID ownerId) { + this.ownerId = ownerId; + abilities.setControllerId(ownerId); + } + + @Override + public String getExpansionSetCode() { + return expansionSetCode; + } + + @Override + public String getTokenSetCode() { + return tokenSetCode; + } + + @Override + public List getMana() { + List mana = new ArrayList<>(); + for (ManaAbility ability : this.abilities.getManaAbilities(Zone.BATTLEFIELD)) { + for (Mana netMana: ability.getNetMana(null)) { + mana.add(netMana); + } + } + return mana; + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag) { + return this.moveToZone(toZone, sourceId, game, flag, null); + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { + Zone fromZone = game.getState().getZone(objectId); + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, toZone, appliedEffects); + if (!game.replaceEvent(event)) { + if (event.getFromZone() != null) { + switch (event.getFromZone()) { + case GRAVEYARD: + game.getPlayer(ownerId).removeFromGraveyard(this, game); + break; + case HAND: + game.getPlayer(ownerId).removeFromHand(this, game); + break; + case LIBRARY: + game.getPlayer(ownerId).removeFromLibrary(this, game); + break; + case EXILED: + game.getExile().removeCard(this, game); + break; + case OUTSIDE: + game.getPlayer(ownerId).getSideboard().remove(this); + break; + case COMMAND: + game.getState().getCommand().remove((Commander)game.getObject(objectId)); + break; + case STACK: + StackObject stackObject = game.getStack().getSpell(getId()); + if (stackObject != null) { + game.getStack().remove(stackObject); + } + break; + case PICK: + case BATTLEFIELD: // for sacrificing permanents or putting to library + break; + default: + Card sourceCard = game.getCard(sourceId); + logger.fatal(new StringBuilder("Invalid from zone [").append(fromZone) + .append("] for card [").append(this.getName()) + .append("] to zone [").append(toZone) + .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); + break; + } + game.rememberLKI(objectId, event.getFromZone(), this); + } + + setFaceDown(false, game); + updateZoneChangeCounter(game); + switch (event.getToZone()) { + case GRAVEYARD: + game.getPlayer(ownerId).putInGraveyard(this, game, !flag); + break; + case HAND: + game.getPlayer(ownerId).getHand().add(this); + break; + case STACK: + game.getStack().push(new Spell(this, this.getSpellAbility().copy(), ownerId, event.getFromZone())); + break; + case EXILED: + game.getExile().getPermanentExile().add(this); + break; + case COMMAND: + game.addCommander(new Commander(this)); + break; + case LIBRARY: + if (flag) { + game.getPlayer(ownerId).getLibrary().putOnTop(this, game); + } + else { + game.getPlayer(ownerId).getLibrary().putOnBottom(this, game); + } + break; + case BATTLEFIELD: + PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); // controller can be replaced (e.g. Gather Specimens) + game.addPermanent(permanent); + game.setZone(objectId, Zone.BATTLEFIELD); + game.setScopeRelevant(true); + game.applyEffects(); + permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); + game.setScopeRelevant(false); + game.applyEffects(); + if (flag) { + permanent.setTapped(true); + } + event.setTarget(permanent); + break; + default: + Card sourceCard = game.getCard(sourceId); + logger.fatal(new StringBuilder("Invalid to zone [").append(toZone) + .append("] for card [").append(this.getName()) + .append("] to zone [").append(toZone) + .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); + return false; + } + game.setZone(objectId, event.getToZone()); + game.addSimultaneousEvent(event); + return game.getState().getZone(objectId) == toZone; + } + return false; + } + + @Override + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { + Card mainCard = getMainCard(); + ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK); + if (!game.replaceEvent(event)) { + if (event.getFromZone() != null) { + switch (event.getFromZone()) { + case GRAVEYARD: + game.getPlayer(ownerId).removeFromGraveyard(mainCard, game); + break; + case HAND: + game.getPlayer(ownerId).removeFromHand(mainCard, game); + break; + case LIBRARY: + game.getPlayer(ownerId).removeFromLibrary(mainCard, game); + break; + case EXILED: + game.getExile().removeCard(mainCard, game); + break; + case OUTSIDE: + game.getPlayer(ownerId).getSideboard().remove(mainCard); + break; + + case COMMAND: + game.getState().getCommand().remove((Commander)game.getObject(mainCard.getId())); + break; + default: + //logger.warning("moveToZone, not fully implemented: from="+event.getFromZone() + ", to="+event.getToZone()); + } + game.rememberLKI(mainCard.getId(), event.getFromZone(), this); + } + game.getStack().push(new Spell(this, ability.copy(), controllerId, event.getFromZone())); + setZone(event.getToZone(), game); + game.fireEvent(event); + return game.getState().getZone(mainCard.getId()) == Zone.STACK; + } + return false; + } + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { + return moveToExile(exileId, name, sourceId, game, null); + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { + Zone fromZone = game.getState().getZone(objectId); + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); + if (!game.replaceEvent(event)) { + if (fromZone != null) { + switch (fromZone) { + case GRAVEYARD: + game.getPlayer(ownerId).removeFromGraveyard(this, game); + break; + case HAND: + game.getPlayer(ownerId).removeFromHand(this, game); + break; + case LIBRARY: + game.getPlayer(ownerId).removeFromLibrary(this, game); + break; + case EXILED: + game.getExile().removeCard(this, game); + break; + case STACK: + case PICK: + // nothing to do + break; + default: + MageObject object = game.getObject(sourceId); + logger.warn(new StringBuilder("moveToExile, not fully implemented: from = ").append(fromZone).append(" - ").append(object != null ? object.getName():"null")); + } + game.rememberLKI(objectId, event.getFromZone(), this); + } + + if (exileId == null) { + game.getExile().getPermanentExile().add(this); + } else { + game.getExile().createZone(exileId, name).add(this); + } + setFaceDown(false, game); + updateZoneChangeCounter(game); + game.setZone(objectId, event.getToZone()); + game.addSimultaneousEvent(event); + return true; + } + return false; + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId) { + return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, false, false, null); + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped){ + return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, false, null); + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown){ + return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, facedown, null); + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects){ + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, fromZone, Zone.BATTLEFIELD, appliedEffects, tapped); + if (facedown) { + this.setFaceDown(true, game); + } + if (!game.replaceEvent(event)) { + if (facedown) { + this.setFaceDown(false, game); + } + if (fromZone != null) { + boolean removed = false; + switch (fromZone) { + case GRAVEYARD: + removed = game.getPlayer(ownerId).removeFromGraveyard(this, game); + break; + case HAND: + removed = game.getPlayer(ownerId).removeFromHand(this, game); + break; + case LIBRARY: + removed = game.getPlayer(ownerId).removeFromLibrary(this, game); + break; + case EXILED: + game.getExile().removeCard(this, game); + removed = true; + break; + case COMMAND: + // command object (commander) is only on the stack, so no removing neccessary here + removed = true; + break; + case PICK: + removed = true; + break; + default: + logger.warn("putOntoBattlefield, not fully implemented: fromZone="+fromZone); + } + game.rememberLKI(objectId, event.getFromZone(), this); + if (!removed) { + logger.warn("Couldn't find card in fromZone, card=" + getName() + ", fromZone=" + fromZone); + } + } + updateZoneChangeCounter(game); + PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); + // make sure the controller of all continuous effects of this card are switched to the current controller + game.getContinuousEffects().setController(objectId, event.getPlayerId()); + game.addPermanent(permanent); + setZone(Zone.BATTLEFIELD, game); + game.setScopeRelevant(true); + permanent.setTapped(tapped); + permanent.setFaceDown(facedown, game); + permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); + game.setScopeRelevant(false); + game.applyEffects(); + game.addSimultaneousEvent(new ZoneChangeEvent(permanent, event.getPlayerId(), fromZone, Zone.BATTLEFIELD)); + return true; + } + if (facedown) { + this.setFaceDown(false, game); + } + return false; + } + + @Override + public void setFaceDown(boolean value, Game game) { + game.getState().getCardState(objectId).setFaceDown(value); + } + + @Override + public boolean isFaceDown(Game game) { + return game.getState().getCardState(objectId).isFaceDown(); + } + + @Override + public boolean turnFaceUp(Game game, UUID playerId) { + GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEUP, getId(), playerId); + if (!game.replaceEvent(event)) { + setFaceDown(false, game); + for (Ability ability :abilities) { // abilities that were set to not visible face down must be set to visible again + if (ability.getWorksFaceDown() && !ability.getRuleVisible()) { + ability.setRuleVisible(true); + } + } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEUP, getId(), playerId)); + return true; + } + return false; + } + + @Override + public boolean turnFaceDown(Game game, UUID playerId) { + GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEDOWN, getId(), playerId); + if (!game.replaceEvent(event)) { + setFaceDown(true, game); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEDOWN, getId(), playerId)); + return true; + } + return false; + } + + @Override + public boolean canTransform() { + return this.canTransform; + } + + @Override + public Card getSecondCardFace() { + return this.secondSideCard; + } + + @Override + public boolean isNightCard() { + return this.nightCard; + } + + @Override + public boolean isFlipCard() { + return flipCard; + } + + @Override + public String getFlipCardName() { + return flipCardName; + } + + @Override + public boolean isSplitCard() { + return splitCard; + } + + @Override + public void build() {} + + @Override + public boolean getUsesVariousArt() { + return usesVariousArt; + } + + @Override + public Counters getCounters(Game game) { + return game.getState().getCardState(this.objectId).getCounters(); + } + + @Override + public void addCounters(String name, int amount, Game game) { + addCounters(name, amount, game, null); + } + + @Override + public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { + GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, name, amount); + countersEvent.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(countersEvent)) { + for (int i = 0; i < countersEvent.getAmount(); i++) { + GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, name, 1); + event.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(event)) { + game.getState().getCardState(this.objectId).getCounters().addCounter(name, 1); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, name, 1)); + } + } + } + } + + @Override + public void addCounters(Counter counter, Game game) { + addCounters(counter, game, null); + } + + @Override + public void addCounters(Counter counter, Game game, ArrayList appliedEffects) { + GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, counter.getName(), counter.getCount()); + countersEvent.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(countersEvent)) { + int amount = countersEvent.getAmount(); + for (int i = 0; i < amount; i++) { + Counter eventCounter = counter.copy(); + eventCounter.remove(amount - 1); + GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, counter.getName(), 1); + event.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(event)) { + game.getState().getCardState(this.objectId).getCounters().addCounter(eventCounter); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, counter.getName(), 1)); + } + } + } + } + + @Override + public void removeCounters(String name, int amount, Game game) { + for (int i = 0; i < amount; i++) { + game.getState().getCardState(this.objectId).getCounters().removeCounter(name, 1); + GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, ownerId); + event.setData(name); + game.fireEvent(event); + } + } + + @Override + public void removeCounters(Counter counter, Game game) { + removeCounters(counter.getName(), counter.getCount(), game); + } + + @Override + public String getLogName() { +// if (this.isFaceDown()) { +// return "facedown card"; +// } + return name; + } + + @Override + public Card getMainCard() { + return this; + } + + @Override + public void setZone(Zone zone, Game game) { + game.setZone(getId(), zone); + } + + @Override + public void setSpellAbility(SpellAbility ability) { + spellAbility = ability; + } + +} diff --git a/Mage/src/mage/game/permanent/PermanentToken.java b/Mage/src/mage/game/permanent/PermanentToken.java index 75dcbb9e9b..69d2aa3744 100644 --- a/Mage/src/mage/game/permanent/PermanentToken.java +++ b/Mage/src/mage/game/permanent/PermanentToken.java @@ -108,7 +108,7 @@ public class PermanentToken extends PermanentImpl { if (!game.replaceEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED))) { game.rememberLKI(objectId, Zone.BATTLEFIELD, this); if (game.getPlayer(controllerId).removeFromBattlefield(this, game)) { - game.fireEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED)); + game.addSimultaneousEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED)); return true; } } diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index 9041b9cb61..18de6258c9 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -201,8 +201,8 @@ public class Spell implements StackObject, Card { result |= spellAbility.resolve(game); } } - game.getState().handleSimultaneousEvent(game); - game.resetShortLivingLKI(); +// game.getState().handleSimultaneousEvent(game); +// game.resetShortLivingLKI(); index++; } } From 87bf3408b3047aa9d913f4897944d52756349684 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 19 Apr 2015 11:19:26 +0200 Subject: [PATCH 3/7] * Grand Abolisher - Fixed that it also prevented wrongly abilities of cards not on the battlefield (e.g. Cycling). --- .../mage/sets/magic2012/GrandAbolisher.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java b/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java index 714eac76ea..e08b5280a8 100644 --- a/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java +++ b/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java @@ -106,30 +106,24 @@ class GrandAbolisherEffect extends ContinuousRuleModifyingEffectImpl { } @Override - public boolean applies(GameEvent event, Ability source, Game game) { - boolean spell = event.getType() == GameEvent.EventType.CAST_SPELL; - boolean activated = event.getType() == GameEvent.EventType.ACTIVATE_ABILITY; - if ((spell || activated) && game.getActivePlayerId().equals(source.getControllerId()) && game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) { - - if (spell) { - return true; - } - - // check source of activated ability - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - return permanent.getCardType().contains(CardType.ARTIFACT) || permanent.getCardType().contains(CardType.CREATURE) - || permanent.getCardType().contains(CardType.ENCHANTMENT); - } else { - MageObject object = game.getObject(event.getSourceId()); - if (object != null) { - return object.getCardType().contains(CardType.ARTIFACT) || object.getCardType().contains(CardType.CREATURE) - || object.getCardType().contains(CardType.ENCHANTMENT); - } - } - } - - return false; + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL || event.getType() == GameEvent.EventType.ACTIVATE_ABILITY; } + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (game.getActivePlayerId().equals(source.getControllerId()) && game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) { + switch(event.getType()) { + case CAST_SPELL: + return true; + case ACTIVATE_ABILITY: + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null) { + return permanent.getCardType().contains(CardType.ARTIFACT) || permanent.getCardType().contains(CardType.CREATURE) + || permanent.getCardType().contains(CardType.ENCHANTMENT); + } + } + } + return false; + } } From 34bd8aefca7cf1ac64b88f6f413bc05ee49e4e58 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 19 Apr 2015 16:17:57 +0200 Subject: [PATCH 4/7] * Painful Quandary - Fixed that the triggered effect did not work. --- Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java index 5444eecf2a..7fb923d038 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java @@ -37,6 +37,9 @@ import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.FilterSpell; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInHand; @@ -52,7 +55,7 @@ public class PainfulQuandary extends CardImpl { this.expansionSetCode = "SOM"; // Whenever an opponent casts a spell, that player loses 5 life unless he or she discards a card. - this.addAbility(new SpellCastOpponentTriggeredAbility(new PainfulQuandryEffect(), false)); + this.addAbility(new SpellCastOpponentTriggeredAbility(Zone.BATTLEFIELD, new PainfulQuandryEffect(), new FilterSpell(), false, SetTargetPointer.PLAYER)); } public PainfulQuandary(final PainfulQuandary card) { From 5edf91fd5de98d8849f48fe40e6689dee100e46d Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 19 Apr 2015 17:58:17 +0200 Subject: [PATCH 5/7] * Spined Fluke - Fixed that no creature has to be sacrificed as Spined Fluke enters the battlefield. --- Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java b/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java index 90816ead69..8e141aa20d 100644 --- a/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java +++ b/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java @@ -36,6 +36,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ColoredManaCost; import mage.abilities.effects.common.RegenerateSourceEffect; +import mage.abilities.effects.common.SacrificeControllerEffect; import mage.abilities.effects.common.SacrificeEffect; import mage.cards.CardImpl; import mage.constants.ColoredManaSymbol; @@ -54,7 +55,6 @@ public class SpinedFluke extends CardImpl { this.subtype.add("Worm"); this.subtype.add("Horror"); - this.color.setBlack(true); this.power = new MageInt(5); this.toughness = new MageInt(1); } @@ -62,7 +62,7 @@ public class SpinedFluke extends CardImpl { @Override public void build() { // When Spined Fluke enters the battlefield, sacrifice a creature. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeEffect(new FilterCreaturePermanent("a creature"), 1, ""))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeControllerEffect(new FilterCreaturePermanent("a creature"), 1, ""))); // {B}: Regenerate Spined Fluke. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new RegenerateSourceEffect(), new ColoredManaCost(ColoredManaSymbol.B))); } From f4166ac3b32fc526bcff4f7dba9fd17d556cf706 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 19 Apr 2015 23:34:49 +0200 Subject: [PATCH 6/7] * Timetwister - Fixed that card moving of Timetwister did not allow to move a commander in the command zone. --- .../mage/sets/limitedalpha/Timetwister.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java b/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java index 2496e3e987..015dc2f6c2 100644 --- a/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java +++ b/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java @@ -30,10 +30,12 @@ package mage.sets.limitedalpha; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; +import mage.constants.Zone; import mage.game.Game; import mage.players.Player; @@ -81,14 +83,23 @@ class TimetwisterEffect extends OneShotEffect { for (UUID playerId: sourcePlayer.getInRange()) { Player player = game.getPlayer(playerId); if (player != null) { - player.getLibrary().addAll(player.getHand().getCards(game), game); - player.getLibrary().addAll(player.getGraveyard().getCards(game), game); + for (Card card: player.getHand().getCards(game)) { + card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); + } + for (Card card: player.getGraveyard().getCards(game)) { + card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); + } player.shuffleLibrary(game); - player.getHand().clear(); - player.getGraveyard().clear(); - player.drawCards(7, game); + } } + game.getState().handleSimultaneousEvent(game); // needed here so state based triggered effects + for (UUID playerId: sourcePlayer.getInRange()) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.drawCards(7, game); + } + } return true; } From 4735e73967c0825039d549626edfc79ddac7b91a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 20 Apr 2015 00:05:57 +0200 Subject: [PATCH 7/7] CardUtil - renamed and made adjustAbilityCost public. --- Mage/src/mage/util/CardUtil.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mage/src/mage/util/CardUtil.java b/Mage/src/mage/util/CardUtil.java index c9261ede99..88116bd390 100644 --- a/Mage/src/mage/util/CardUtil.java +++ b/Mage/src/mage/util/CardUtil.java @@ -132,7 +132,7 @@ public class CardUtil { * @param increaseCount */ public static void increaseCost(Ability ability, int increaseCount) { - adjustCost(ability, -increaseCount); + adjustAbilityCost(ability, -increaseCount); adjustAlternativeCosts(ability, -increaseCount); } @@ -143,7 +143,7 @@ public class CardUtil { * @param reduceCount */ public static void reduceCost(Ability ability, int reduceCount) { - adjustCost(ability, reduceCount); + adjustAbilityCost(ability, reduceCount); adjustAlternativeCosts(ability, reduceCount); } @@ -154,7 +154,7 @@ public class CardUtil { * @param reduceCount */ public static void adjustCost(SpellAbility spellAbility, int reduceCount) { - CardUtil.adjustCost((Ability) spellAbility, reduceCount); + CardUtil.adjustAbilityCost((Ability) spellAbility, reduceCount); adjustAlternativeCosts(spellAbility, reduceCount); } @@ -208,7 +208,7 @@ public class CardUtil { * @param ability * @param reduceCount */ - private static void adjustCost(Ability ability, int reduceCount) { + public static void adjustAbilityCost(Ability ability, int reduceCount) { ManaCosts adjustedCost = adjustCost(ability.getManaCostsToPay(), reduceCount); ability.getManaCostsToPay().clear(); ability.getManaCostsToPay().addAll(adjustedCost);