diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ConditionalAsThoughTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ConditionalAsThoughTest.java new file mode 100644 index 00000000000..6077a23cf19 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ConditionalAsThoughTest.java @@ -0,0 +1,136 @@ +package org.mage.test.cards.conditional; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneAllEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCardInOpponentsGraveyard; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class ConditionalAsThoughTest extends CardTestPlayerBase { + + @Test + public void test_PlayFromNotOwnHandZoneAllEffect() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + + addCustomCardWithAbility("play any library on any creature", playerA, new SimpleStaticAbility( + Zone.ALL, + new ConditionalAsThoughEffect( + new PlayFromNotOwnHandZoneAllEffect( + StaticFilters.FILTER_CARD, + Zone.LIBRARY, + false, + TargetController.ANY, + Duration.EndOfTurn + ), + new PermanentsOnTheBattlefieldCondition( + StaticFilters.FILTER_PERMANENT_CREATURE, + ComparisonType.MORE_THAN, + 0 + ) + ) + )); + addCard(Zone.HAND, playerA, "Grizzly Bears"); + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); + addCard(Zone.LIBRARY, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + + // can't play lib's card before good condition + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + + // make good condition - now we can play any lib's card + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", false); + checkPlayableAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", true); + checkPlayableAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } + + @Test(expected = IllegalArgumentException.class) + public void test_TargetCardInLibrary_CantUseAsAbilityTarget() { + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new InfoEffect("test"), + new ManaCostsImpl("{R}") + ); + ability.addTarget(new TargetCardInLibrary()); + } + + @Test + public void test_PlayFromNotOwnHandZoneTargetEffect() { + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new ConditionalAsThoughEffect( + new PlayFromNotOwnHandZoneTargetEffect( + Zone.GRAVEYARD, + TargetController.ANY, + Duration.EndOfTurn + ), + new PermanentsOnTheBattlefieldCondition( + StaticFilters.FILTER_PERMANENT_CREATURE, + ComparisonType.MORE_THAN, + 0 + ) + ).setText("allow target cast"), + new ManaCostsImpl("{R}") + ); + ability.addTarget(new TargetCardInOpponentsGraveyard(StaticFilters.FILTER_CARD)); + addCustomCardWithAbility("play any opponent hand", playerA, ability); + + addCard(Zone.HAND, playerA, "Grizzly Bears"); + addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + + // can't play grave before + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + + // activate target effect + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}: Allow"); + addTarget(playerA, "Lightning Bolt"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // can't play grave after but without good condition + checkPlayableAbility("after bad", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + checkPlayableAbility("after bad", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("after bad", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + + // make good condition - now we can play opponent's grave + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("after good", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", false); + checkPlayableAbility("after good", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false); + checkPlayableAbility("after good", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertAllCommandsUsed(); + } +} 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 c211a2fa8bf..ed258018eb6 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 @@ -2400,7 +2400,7 @@ public class TestPlayer implements Player { } // card in hand (only own hand supports here) - // TODO: add not own hand too, example + // cards from non-own hand must be targeted through revealed cards if (target.getOriginalTarget() instanceof TargetCardInHand || target.getOriginalTarget() instanceof TargetDiscard || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.HAND)) { @@ -2569,6 +2569,13 @@ public class TestPlayer implements Player { } } + // library + if (target.getOriginalTarget() instanceof TargetCardInLibrary + || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.LIBRARY)) { + // user don't have access to library, so it must be targeted through list/revealed cards + Assert.fail("Library zone is private, you must target through cards list, e.g. revealed: " + target.getOriginalTarget().getClass().getCanonicalName()); + } + // uninplemented TargetCard's zone if (target.getOriginalTarget() instanceof TargetCard && !targetCardZonesChecked.contains(target.getOriginalTarget().getZone())) { Assert.fail("Found unimplemented TargetCard's zone or TargetCard's extented class: " diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 5b07c7502e5..44306218224 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -26,7 +26,9 @@ import mage.game.stack.Spell; import mage.game.stack.StackAbility; import mage.players.Player; import mage.target.Target; +import mage.target.TargetCard; import mage.target.Targets; +import mage.target.common.TargetCardInLibrary; import mage.target.targetadjustment.TargetAdjuster; import mage.util.CardUtil; import mage.util.GameLog; @@ -881,6 +883,12 @@ public abstract class AbilityImpl implements Ability { @Override public void addTarget(Target target) { + // verify check + if (target instanceof TargetCardInLibrary + || (target instanceof TargetCard && target.getZone().equals(Zone.LIBRARY))) { + throw new IllegalArgumentException("Wrong usage of TargetCardInLibrary - you must use it with SearchLibrary only"); + } + if (target != null) { getTargets().add(target); } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java index 2ad82b0b8be..fdaee170b57 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalAsThoughEffect.java @@ -65,6 +65,19 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl { return false; } + @Override + public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + conditionState = condition.apply(game, source); + if (conditionState) { + effect.setTargetPointer(this.targetPointer); + return effect.applies(sourceId, affectedAbility, source, game, playerId); + } else if (otherwiseEffect != null) { + otherwiseEffect.setTargetPointer(this.targetPointer); + return otherwiseEffect.applies(sourceId, affectedAbility, source, game, playerId); + } + return false; + } + @Override public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { conditionState = condition.apply(game, source); diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java index bc4d6d4f6c2..e3e5beb5556 100644 --- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffect.java @@ -15,6 +15,7 @@ public interface AsThoughEffect extends ContinuousEffect { * Apply to ONE affected ability from the object (sourceId) *
* Warning, if you don't need ability to check then ignore it (by default it calls full object check) + * Warning, if you use conditional effect then you must override both applies methods to support different types * * @param sourceId * @param affectedAbility ability to check (example: check if spell ability can be cast from non hand) diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java index c9e7f2f5c4e..9386508f97b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/PlayFromNotOwnHandZoneTargetEffect.java @@ -85,6 +85,8 @@ public class PlayFromNotOwnHandZoneTargetEffect extends AsThoughEffectImpl { if (affectedAbility == null) { // ContinuousEffects.asThough already checks affectedAbility, so that error must never be called here // PLAY_FROM_NOT_OWN_HAND_ZONE must applies to affectedAbility only + // If you see it then parent conditional effect must override both applies methods to support different + // AsThough effect types in one conditional effect throw new IllegalArgumentException("ERROR, can't call applies method on empty affectedAbility"); } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 6be8a2d3289..89140dc946f 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3794,8 +3794,8 @@ public abstract class PlayerImpl implements Player, Serializable { } ApprovingObject approvingObject; - if (isPlaySpell || isPlayLand) { - // play hand from non hand zone + if ((isPlaySpell || isPlayLand) && (fromZone != Zone.BATTLEFIELD)) { + // play hand from non hand zone (except battlefield - you can't play already played permanents) approvingObject = game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game); } else { diff --git a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java index 5fa73b53a11..59b39f4ba89 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java @@ -19,6 +19,9 @@ import java.util.List; import java.util.UUID; /** + * + * Can be used with SearchLibrary only. User hasn't access to libs. + * * @author BetaSteward_at_googlemail.com */ public class TargetCardInLibrary extends TargetCard {