diff --git a/Mage.Client/serverlist.txt b/Mage.Client/serverlist.txt index db59294453e..59d6968638a 100644 --- a/Mage.Client/serverlist.txt +++ b/Mage.Client/serverlist.txt @@ -1,7 +1,6 @@ woogerworks (North America/USA) :xmage.woogerworks.com:17171 -Xmage.de 1 (Europe/Germany) :xmage.de:17171 -XMage.info 1 (Europe/France) :176.31.186.181:17171 -XMage.info 2 (Europe/France) :176.31.186.181:17000 +XMage.de 1 (Europe/Germany) fast :xmage.de:17171 +XMage.info 2 (Europe/France) slow :176.31.186.181:17000 IceMage (Europe/Netherlands) :ring0.cc:17171 Seedds Server (Asia) :115.29.203.80:17171 localhost -> connect to your local server (must be started):localhost:17171 diff --git a/Mage.Sets/src/mage/sets/futuresight/ScoutsWarning.java b/Mage.Sets/src/mage/sets/futuresight/ScoutsWarning.java index f6802ed2829..624abe4f6e5 100644 --- a/Mage.Sets/src/mage/sets/futuresight/ScoutsWarning.java +++ b/Mage.Sets/src/mage/sets/futuresight/ScoutsWarning.java @@ -56,8 +56,6 @@ public class ScoutsWarning extends CardImpl { super(ownerId, 16, "Scout's Warning", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{W}"); this.expansionSetCode = "FUT"; - this.color.setWhite(true); - // The next creature card you play this turn can be played as though it had flash. this.getSpellAbility().addEffect(new ScoutsWarningAsThoughEffect()); this.getSpellAbility().addWatcher(new ScoutsWarningWatcher()); @@ -117,7 +115,7 @@ class ScoutsWarningAsThoughEffect extends AsThoughEffectImpl { if (watcher.isScoutsWarningSpellActive(source.getSourceId(), zoneChangeCounter)) { Card card = game.getCard(sourceId); if (card != null && card.getCardType().contains(CardType.CREATURE) && source.getControllerId().equals(affectedControllerId)) { - return card.getSpellAbility().isInUseableZone(game, card, false); + return card.getSpellAbility().isInUseableZone(game, card, null); } } return false; diff --git a/Mage.Sets/src/mage/sets/guildpact/Quicken.java b/Mage.Sets/src/mage/sets/guildpact/Quicken.java index b2f3f466f97..be65ccea161 100644 --- a/Mage.Sets/src/mage/sets/guildpact/Quicken.java +++ b/Mage.Sets/src/mage/sets/guildpact/Quicken.java @@ -118,7 +118,7 @@ class QuickenAsThoughEffect extends AsThoughEffectImpl { if (quickenWatcher.isQuickenSpellActive(source.getSourceId(), zoneChangeCounter)) { Card card = game.getCard(sourceId); if (card != null && card.getCardType().contains(CardType.SORCERY) && source.getControllerId().equals(affectedControllerId)) { - return card.getSpellAbility().isInUseableZone(game, card, false); + return card.getSpellAbility().isInUseableZone(game, card, null); } } return false; diff --git a/Mage.Sets/src/mage/sets/magic2014/SavageSummoning.java b/Mage.Sets/src/mage/sets/magic2014/SavageSummoning.java index db2399fb71f..63694af386e 100644 --- a/Mage.Sets/src/mage/sets/magic2014/SavageSummoning.java +++ b/Mage.Sets/src/mage/sets/magic2014/SavageSummoning.java @@ -132,7 +132,7 @@ class SavageSummoningAsThoughEffect extends AsThoughEffectImpl { if (watcher.isSavageSummoningSpellActive()) { Card card = game.getCard(sourceId); if (card != null && card.getCardType().contains(CardType.CREATURE) && card.getOwnerId().equals(source.getControllerId())) { - return card.getSpellAbility().isInUseableZone(game, card, false); + return card.getSpellAbility().isInUseableZone(game, card, null); } } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java new file mode 100644 index 00000000000..97cf0d07987 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class ExploitTest extends CardTestPlayerBase { + + /** + * 702.109. Exploit + * + * 702.109a Exploit is a triggered ability. “Exploit” means “When this creature enters the battlefield, you may sacrifice a creature.” + * + * 702.109b A creature with exploit “exploits a creature” when the controller of the exploit ability sacrifices a creature as that ability resolves. + * + * You choose whether to sacrifice a creature and which creature to sacrifice as the exploit ability resolves. + * You can sacrifice the creature with exploit if it’s still on the battlefield. This will cause its other ability to trigger. + * You can’t sacrifice more than one creature to any one exploit ability. + * + */ + + @Test + public void testNormalUse() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + // Exploit (When this creature enters the battlefield, you may sacrifice a creature.) + // When Silumgar Butcher exploits a creature, target creature gets -3/-3 until end of turn. + addCard(Zone.HAND, playerA, "Silumgar Butcher"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + addCard(Zone.BATTLEFIELD, playerB, "Thundering Giant"); // 4/3 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silumgar Butcher"); + setChoice(playerA, "Yes"); + addTarget(playerA, "Silvercoat Lion"); // sacrifice to Exploit + addTarget(playerA, "Thundering Giant"); // Target for the -3/-3 + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Silumgar Butcher", 1); + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertGraveyardCount(playerB, "Thundering Giant", 1); + + } + /** + * Test that the Exploit ability won't trigger if the creature with + * exploit left the battlefiled before the Enters the battlefield + * triggered ability resolves. + * + */ + @Test + public void testExploitTriggerWontGo() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + // Exploit (When this creature enters the battlefield, you may sacrifice a creature.) + // When Silumgar Butcher exploits a creature, target creature gets -3/-3 until end of turn. + addCard(Zone.HAND, playerA, "Silumgar Butcher"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerB, "Thundering Giant"); // 4/3 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silumgar Butcher"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silumgar Butcher"); + setChoice(playerA, "Yes"); + addTarget(playerA, "Silvercoat Lion"); // sacrifice to Exploit + addTarget(playerA, "Thundering Giant"); // Target for the -3/-3 + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertGraveyardCount(playerA, "Silumgar Butcher", 1); + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertPermanentCount(playerB, "Thundering Giant", 1); + + } + + /** + * Test that the Exploit ability won't trigger if the creature with + * exploit left the battlefiled before the Enters the battlefield + * triggered ability resolves. + * + */ + @Test + public void testSacrificeCreatureWithExploit() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Exploit (When this creature enters the battlefield, you may sacrifice a creature.) + // When Qarsi Sadist exploits a creature, target opponent loses 2 life and you gain 2 life. + addCard(Zone.HAND, playerA, "Qarsi Sadist"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Qarsi Sadist"); + setChoice(playerA, "Yes"); + addTarget(playerA, "Qarsi Sadist"); // sacrifice to Exploit + addTarget(playerA, playerB); // Target for lose life + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Qarsi Sadist", 1); + + assertLife(playerA, 22); + assertLife(playerB, 18); + + } + +} \ No newline at end of file diff --git a/Mage/src/mage/abilities/Ability.java b/Mage/src/mage/abilities/Ability.java index 8216042b1e8..7d39b32193c 100644 --- a/Mage/src/mage/abilities/Ability.java +++ b/Mage/src/mage/abilities/Ability.java @@ -47,6 +47,7 @@ import mage.constants.EffectType; import mage.constants.Zone; import mage.game.Controllable; import mage.game.Game; +import mage.game.events.GameEvent; import mage.players.Player; import mage.target.Target; import mage.target.Targets; @@ -382,10 +383,10 @@ public interface Ability extends Controllable, Serializable { * * @param game * @param source - * @param checkLKI + * @param event * @return */ - boolean isInUseableZone(Game game, MageObject source, boolean checkLKI); + boolean isInUseableZone(Game game, MageObject source, GameEvent event); /** * Returns true if this ability has to be shown as topmost of all the rules of the object diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index 84fec853f2f..d38d793a59b 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -67,6 +67,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.command.Emblem; import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; import mage.game.events.ManaEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -864,11 +865,10 @@ public abstract class AbilityImpl implements Ability { * * @param game * @param source - * @param checkShortLivingLKI if the object was in the needed zone as the effect that's currently applied started, the check returns true * @return */ @Override - public boolean isInUseableZone(Game game, MageObject source, boolean checkShortLivingLKI) { + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { if (zone.equals(Zone.COMMAND)) { if (this.getSourceId() == null) { // commander effects return true; @@ -880,17 +880,6 @@ public abstract class AbilityImpl implements Ability { } } - // try LKI first (was the object with the id in the needed zone before) - if (checkShortLivingLKI) { - if (game.getShortLivingLKI(getSourceId(), zone)) { - return true; - } - } else { - if (game.getLastKnownInformation(getSourceId(), zone) != null) { - return true; - } - } - MageObject object; UUID parameterSourceId; // for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects diff --git a/Mage/src/mage/abilities/StaticAbility.java b/Mage/src/mage/abilities/StaticAbility.java index a0d8dd8e6e1..d868f704384 100644 --- a/Mage/src/mage/abilities/StaticAbility.java +++ b/Mage/src/mage/abilities/StaticAbility.java @@ -28,9 +28,13 @@ package mage.abilities; +import mage.MageObject; +import mage.abilities.effects.Effect; import mage.constants.AbilityType; import mage.constants.Zone; -import mage.abilities.effects.Effect; +import mage.game.Game; +import mage.game.events.GameEvent; + /** * @@ -49,6 +53,14 @@ public abstract class StaticAbility extends AbilityImpl { } } + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + if (game.getShortLivingLKI(getSourceId(), zone)) { + return true; + } + return super.isInUseableZone(game, source, event); + } + public StaticAbility(StaticAbility ability) { super(ability); } diff --git a/Mage/src/mage/abilities/TriggeredAbilities.java b/Mage/src/mage/abilities/TriggeredAbilities.java index d2ed7faf5ab..903a97a8dca 100644 --- a/Mage/src/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/mage/abilities/TriggeredAbilities.java @@ -72,7 +72,7 @@ public class TriggeredAbilities extends ConcurrentHashMap