diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index e3254b3a02..75be926ecb 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -1742,7 +1742,7 @@ public class HumanPlayer extends PlayerImpl { if (ability instanceof PlayLandAbility) { return true; } - if (!ability.getSourceId().equals(getCastSourceIdWithAlternateMana()) + if (!getCastSourceIdWithAlternateMana().contains(ability.getSourceId()) && ability.getManaCostsToPay().convertedManaCost() > 0) { return true; } diff --git a/Mage.Sets/src/mage/cards/b/BolassCitadel.java b/Mage.Sets/src/mage/cards/b/BolassCitadel.java index 32da651d46..5741a658b0 100644 --- a/Mage.Sets/src/mage/cards/b/BolassCitadel.java +++ b/Mage.Sets/src/mage/cards/b/BolassCitadel.java @@ -96,15 +96,14 @@ class BolassCitadelPlayTheTopCardEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { - Card cardOnTop = game.getCard(objectId); - if (cardOnTop == null) { - return false; - } - if (playerId.equals(source.getControllerId()) - && cardOnTop.isOwnedBy(source.getControllerId())) { - Player controller = game.getPlayer(cardOnTop.getOwnerId()); + Card cardToCheck = game.getCard(objectId); + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + + if (playerId.equals(source.getControllerId()) && cardToCheck.isOwnedBy(source.getControllerId())) { + Player controller = game.getPlayer(cardToCheck.getOwnerId()); if (controller != null - && cardOnTop.equals(controller.getLibrary().getFromTop(game))) { + && controller.getLibrary().getFromTop(game) != null + && objectId.equals(controller.getLibrary().getFromTop(game).getId())) { if (affectedAbility instanceof ActivatedAbility) { ActivatedAbility activatedAbility = (ActivatedAbility) affectedAbility; // add the life cost first diff --git a/Mage.Sets/src/mage/cards/c/CovetousUrge.java b/Mage.Sets/src/mage/cards/c/CovetousUrge.java index 45b570c261..b3cf2030bf 100644 --- a/Mage.Sets/src/mage/cards/c/CovetousUrge.java +++ b/Mage.Sets/src/mage/cards/c/CovetousUrge.java @@ -156,11 +156,12 @@ class CovetousUrgeSpendAnyManaEffect extends AsThoughEffectImpl implements AsTho @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) && Objects.equals(objectId, fixedTarget.getTarget()) - && fixedTarget.getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && game.getState().getZone(objectId) == Zone.STACK; + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override diff --git a/Mage.Sets/src/mage/cards/c/CunningAbduction.java b/Mage.Sets/src/mage/cards/c/CunningAbduction.java index 48553cd2ea..d0d5bd1cc4 100644 --- a/Mage.Sets/src/mage/cards/c/CunningAbduction.java +++ b/Mage.Sets/src/mage/cards/c/CunningAbduction.java @@ -1,6 +1,5 @@ package mage.cards.c; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.AsThoughEffectImpl; @@ -21,8 +20,9 @@ import mage.target.common.TargetOpponent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author Styxo */ public final class CunningAbduction extends CardImpl { @@ -125,12 +125,8 @@ class CunningAbductionSpendAnyManaEffect extends AsThoughEffectImpl implements A objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - if (affectedControllerId.equals(source.getControllerId())) { - // if the card moved from exile to spell the zone change counter is increased by 1 - if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - return true; - } - } + // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) + return affectedControllerId.equals(source.getControllerId()); } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { // object has moved zone so effect can be discarted this.discard(); diff --git a/Mage.Sets/src/mage/cards/d/DaxosOfMeletis.java b/Mage.Sets/src/mage/cards/d/DaxosOfMeletis.java index e9cc591624..da160f068f 100644 --- a/Mage.Sets/src/mage/cards/d/DaxosOfMeletis.java +++ b/Mage.Sets/src/mage/cards/d/DaxosOfMeletis.java @@ -1,7 +1,5 @@ package mage.cards.d; -import java.util.Objects; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -26,8 +24,10 @@ import mage.players.Player; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.Objects; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class DaxosOfMeletis extends CardImpl { @@ -178,11 +178,11 @@ class DaxosOfMeletisSpendAnyManaEffect extends AsThoughEffectImpl implements AsT @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) - && Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) - && game.getState().getZone(objectId) == Zone.STACK; + && Objects.equals(objectId, fixedTarget.getTarget()) + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DeadMansChest.java b/Mage.Sets/src/mage/cards/d/DeadMansChest.java index 284ec871fb..c88eabc0f7 100644 --- a/Mage.Sets/src/mage/cards/d/DeadMansChest.java +++ b/Mage.Sets/src/mage/cards/d/DeadMansChest.java @@ -1,7 +1,5 @@ package mage.cards.d; -import java.util.Set; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.DiesAttachedTriggeredAbility; @@ -15,14 +13,7 @@ import mage.abilities.keyword.EnchantAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.ManaType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; @@ -32,8 +23,10 @@ import mage.players.Player; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.Set; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class DeadMansChest extends CardImpl { @@ -142,9 +135,7 @@ class DeadMansChestCastFromExileEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { if (objectId.equals(getTargetPointer().getFirst(game, source))) { - if (affectedControllerId.equals(source.getControllerId())) { - return true; - } + return affectedControllerId.equals(source.getControllerId()); } else { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { // object has moved zone so effect can be discarted @@ -181,12 +172,8 @@ class DeadMansChestSpendManaEffect extends AsThoughEffectImpl implements AsThoug objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - if (affectedControllerId.equals(source.getControllerId())) { - // if the card moved from exile to spell the zone change counter is increased by 1 - if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - return true; - } - } + // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) + return affectedControllerId.equals(source.getControllerId()); } else { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { // object has moved zone so effect can be discarted diff --git a/Mage.Sets/src/mage/cards/d/DireFleetDaredevil.java b/Mage.Sets/src/mage/cards/d/DireFleetDaredevil.java index c39708c893..bf72d24447 100644 --- a/Mage.Sets/src/mage/cards/d/DireFleetDaredevil.java +++ b/Mage.Sets/src/mage/cards/d/DireFleetDaredevil.java @@ -1,27 +1,15 @@ package mage.cards.d; -import java.util.Objects; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.AsThoughManaEffect; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.*; import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.ManaType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; @@ -34,8 +22,10 @@ import mage.players.Player; import mage.target.common.TargetCardInOpponentsGraveyard; import mage.target.targetpointer.FixedTarget; +import java.util.Objects; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class DireFleetDaredevil extends CardImpl { @@ -144,10 +134,11 @@ class DireFleetDaredevilSpendAnyManaEffect extends AsThoughEffectImpl implements @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) && Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && game.getState().getZone(objectId) == Zone.STACK; + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override @@ -192,7 +183,7 @@ class DireFleetDaredevilReplacementEffect extends ReplacementEffectImpl { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; return zEvent.getToZone() == Zone.GRAVEYARD && event.getTargetId().equals(((FixedTarget) getTargetPointer()).getTarget()) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 + && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(event.getTargetId()); } } diff --git a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java index 0755e3a282..b4b5adf337 100644 --- a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java +++ b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java @@ -1,8 +1,5 @@ package mage.cards.g; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -25,8 +22,11 @@ import mage.target.common.TargetOpponent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class GontiLordOfLuxury extends CardImpl { @@ -185,22 +185,18 @@ class GontiLordOfLuxurySpendAnyManaEffect extends AsThoughEffectImpl implements @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { Card theCard = game.getCard(objectId); - if(theCard == null){ + if (theCard == null) { return false; } Card mainCard = theCard.getMainCard(); - if(mainCard == null){ + if (mainCard == null) { return false; } objectId = mainCard.getId(); // for split cards if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - if (affectedControllerId.equals(source.getControllerId())) { - // if the card moved from exile to spell the zone change counter is increased by 1 - if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - return true; - } - } + // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) + return affectedControllerId.equals(source.getControllerId()); } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { // object has moved zone so effect can be discarted this.discard(); @@ -238,11 +234,11 @@ class GontiLordOfLuxuryLookEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { Card theCard = game.getCard(objectId); - if(theCard == null){ + if (theCard == null) { return false; } Card mainCard = theCard.getMainCard(); - if(mainCard == null){ + if (mainCard == null) { return false; } objectId = mainCard.getId(); // for split cards diff --git a/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java b/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java index 478e2432a8..6553e92444 100644 --- a/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java +++ b/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java @@ -233,11 +233,12 @@ class GrenzoHavocRaiserSpendAnyManaEffect extends AsThoughEffectImpl implements @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) && Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) - && game.getState().getZone(objectId) == Zone.STACK; + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override diff --git a/Mage.Sets/src/mage/cards/h/HostageTaker.java b/Mage.Sets/src/mage/cards/h/HostageTaker.java index 816ff40e09..64ff610c52 100644 --- a/Mage.Sets/src/mage/cards/h/HostageTaker.java +++ b/Mage.Sets/src/mage/cards/h/HostageTaker.java @@ -106,7 +106,7 @@ class HostageTakerExileEffect extends OneShotEffect { ContinuousEffect effect = new HostageTakerSpendAnyManaEffect(); effect.setTargetPointer(new FixedTarget(card.getId(), game)); game.addEffect(effect, source); - return false; + return true; } } @@ -173,11 +173,12 @@ class HostageTakerSpendAnyManaEffect extends AsThoughEffectImpl implements AsTho @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) && Objects.equals(objectId, fixedTarget.getTarget()) - && fixedTarget.getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && game.getState().getZone(objectId) == Zone.STACK; + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OathOfNissa.java b/Mage.Sets/src/mage/cards/o/OathOfNissa.java index 6f3fd0a697..04821b21b9 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfNissa.java +++ b/Mage.Sets/src/mage/cards/o/OathOfNissa.java @@ -1,7 +1,5 @@ - package mage.cards.o; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -19,8 +17,9 @@ import mage.players.ManaPoolItem; import mage.players.Player; import mage.target.TargetCard; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class OathOfNissa extends CardImpl { @@ -128,13 +127,10 @@ class OathOfNissaSpendAnyManaEffect extends AsThoughEffectImpl implements AsThou @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (source.isControlledBy(affectedControllerId)) { MageObject mageObject = game.getObject(objectId); - if (mageObject != null) { - if (mageObject.isPlaneswalker()) { - return true; - } - } + return mageObject != null && mageObject.isPlaneswalker(); } return false; } diff --git a/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java b/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java index 9cc6cc4f33..9383eae170 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java +++ b/Mage.Sets/src/mage/cards/p/PsychicIntrusion.java @@ -1,6 +1,5 @@ package mage.cards.p; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.AsThoughEffectImpl; @@ -10,12 +9,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.ManaType; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterNonlandCard; import mage.game.Game; import mage.players.ManaPoolItem; @@ -25,8 +19,9 @@ import mage.target.common.TargetOpponent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class PsychicIntrusion extends CardImpl { @@ -149,10 +144,9 @@ class PsychicIntrusionCastFromExileEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (objectId.equals(getTargetPointer().getFirst(game, source))) { - if (affectedControllerId.equals(source.getControllerId())) { - return true; - } + return affectedControllerId.equals(source.getControllerId()); } else { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { // object has moved zone so effect can be discarted @@ -189,12 +183,8 @@ class PsychicIntrusionSpendAnyManaEffect extends AsThoughEffectImpl implements A objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - if (affectedControllerId.equals(source.getControllerId())) { - // if the card moved from exile to spell the zone change counter is increased by 1 - if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - return true; - } - } + // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) + return affectedControllerId.equals(source.getControllerId()); } else { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { // object has moved zone so effect can be discarted diff --git a/Mage.Sets/src/mage/cards/q/QuicksilverElemental.java b/Mage.Sets/src/mage/cards/q/QuicksilverElemental.java index 994db84272..24734a42a8 100644 --- a/Mage.Sets/src/mage/cards/q/QuicksilverElemental.java +++ b/Mage.Sets/src/mage/cards/q/QuicksilverElemental.java @@ -1,7 +1,5 @@ - package mage.cards.q; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; @@ -10,28 +8,20 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughManaEffect; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.ManaType; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.ManaPoolItem; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author spjspj */ public final class QuicksilverElemental extends CardImpl { @@ -151,10 +141,9 @@ class QuickSilverElementalBlueManaEffect extends AsThoughEffectImpl implements A @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (objectId.equals(getTargetPointer().getFirst(game, source))) { - if (affectedControllerId.equals(source.getControllerId())) { - return true; - } + return affectedControllerId.equals(source.getControllerId()); } return false; diff --git a/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java b/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java index 923b51ecb1..68c79579ec 100644 --- a/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java +++ b/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java @@ -194,11 +194,12 @@ class RobberOfTheRichSpendAnyManaEffect extends AsThoughEffectImpl implements As @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) && Objects.equals(objectId, fixedTarget.getTarget()) - && fixedTarget.getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && game.getState().getZone(objectId) == Zone.STACK; + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override diff --git a/Mage.Sets/src/mage/cards/s/StolenStrategy.java b/Mage.Sets/src/mage/cards/s/StolenStrategy.java index e20f44395e..9a89e8c370 100644 --- a/Mage.Sets/src/mage/cards/s/StolenStrategy.java +++ b/Mage.Sets/src/mage/cards/s/StolenStrategy.java @@ -1,8 +1,5 @@ - package mage.cards.s; -import java.util.Objects; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; @@ -13,13 +10,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.ManaType; -import mage.constants.Outcome; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.game.ExileZone; import mage.game.Game; import mage.players.ManaPoolItem; @@ -27,8 +18,10 @@ import mage.players.Player; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.Objects; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class StolenStrategy extends CardImpl { @@ -165,11 +158,11 @@ class StolenStrategySpendAnyManaEffect extends AsThoughEffectImpl implements AsT @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) - && Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) - && game.getState().getZone(objectId) == Zone.STACK; + && Objects.equals(objectId, fixedTarget.getTarget()) + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override diff --git a/Mage.Sets/src/mage/cards/t/ThiefOfSanity.java b/Mage.Sets/src/mage/cards/t/ThiefOfSanity.java index 818970680f..2a44855fa2 100644 --- a/Mage.Sets/src/mage/cards/t/ThiefOfSanity.java +++ b/Mage.Sets/src/mage/cards/t/ThiefOfSanity.java @@ -1,8 +1,5 @@ package mage.cards.t; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; @@ -12,18 +9,8 @@ import mage.abilities.effects.AsThoughManaEffect; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; -import mage.constants.AsThoughEffectType; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.ManaType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.cards.*; +import mage.constants.*; import mage.filter.FilterCard; import mage.game.Game; import mage.players.ManaPoolItem; @@ -32,8 +19,11 @@ import mage.target.TargetCard; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ThiefOfSanity extends CardImpl { @@ -200,12 +190,8 @@ class ThiefOfSanitySpendAnyManaEffect extends AsThoughEffectImpl implements AsTh objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - if (affectedControllerId.equals(authorizedPlayerId)) { - // if the card moved from exile to stack the zone change counter is increased by 1 - if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { - return true; - } - } + // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) + return affectedControllerId.equals(authorizedPlayerId); } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { // object has moved zone so effect can be discarted this.discard(); diff --git a/Mage.Sets/src/mage/cards/t/TobiasBeckett.java b/Mage.Sets/src/mage/cards/t/TobiasBeckett.java index e258e5078d..0ad1a9c4ae 100644 --- a/Mage.Sets/src/mage/cards/t/TobiasBeckett.java +++ b/Mage.Sets/src/mage/cards/t/TobiasBeckett.java @@ -1,19 +1,19 @@ package mage.cards.t; -import java.util.Objects; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.*; -import mage.abilities.effects.common.ExileCardsFromTopOfLibraryTargetEffect; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.BountyAbility; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.counters.CounterType; import mage.game.ExileZone; import mage.game.Game; @@ -24,15 +24,17 @@ import mage.target.common.TargetOpponentsCreaturePermanent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.Objects; +import java.util.UUID; + /** - * * @author NinthWorld */ public final class TobiasBeckett extends CardImpl { public TobiasBeckett(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); - + this.addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.HUNTER); @@ -75,7 +77,7 @@ class TobiasBeckettEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { Permanent bountyTriggered = game.getPermanent(this.getTargetPointer().getFirst(game, source)); - if(bountyTriggered != null) { + if (bountyTriggered != null) { Player opponent = game.getPlayer(bountyTriggered.getControllerId()); if (opponent != null) { MageObject sourceObject = game.getObject(source.getSourceId()); @@ -171,11 +173,11 @@ class TobiasBeckettSpendAnyManaEffect extends AsThoughEffectImpl implements AsTh @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { objectId = game.getCard(objectId).getMainCard().getId(); // for split cards + FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); return source.isControlledBy(affectedControllerId) - && Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) - && (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) - && game.getState().getZone(objectId) == Zone.STACK; + && Objects.equals(objectId, fixedTarget.getTarget()) + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } @Override diff --git a/Mage.Sets/src/mage/cards/v/ViviensInvocation.java b/Mage.Sets/src/mage/cards/v/ViviensInvocation.java index 7eacc7e747..23d46f0a18 100644 --- a/Mage.Sets/src/mage/cards/v/ViviensInvocation.java +++ b/Mage.Sets/src/mage/cards/v/ViviensInvocation.java @@ -73,6 +73,7 @@ class ViviensInvocationEffect extends OneShotEffect { Zone.LIBRARY, new FilterCreatureCard("creature card to put on the battlefield") ); + target.setNotTarget(true); if (controller.choose(Outcome.PutCreatureInPlay, cards, target, game)) { Card card = cards.get(target.getFirstTarget(), game); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java index a51a1e68b5..6e424515cf 100644 --- a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java +++ b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java @@ -1,4 +1,3 @@ - package mage.cards.v; import mage.MageInt; @@ -83,12 +82,10 @@ class VizierOfTheMenagerieTopCardCastEffect extends AsThoughEffectImpl { MageObject vizierOfTheMenagerie = game.getObject(source.getSourceId()); if (vizierOfTheMenagerie != null && topCard != null) { - if (topCard == card + return topCard == card && topCard.isCreature() && topCard.getSpellAbility() != null - && topCard.getSpellAbility().spellCanBeActivatedRegularlyNow(controller.getId(), game)) { - return true; - } + && topCard.getSpellAbility().spellCanBeActivatedRegularlyNow(controller.getId(), game); } } } @@ -120,10 +117,10 @@ class VizierOfTheMenagerieManaEffect extends AsThoughEffectImpl implements AsTho @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards if (source.isControlledBy(affectedControllerId)) { MageObject mageObject = game.getObject(objectId); - return mageObject != null - && mageObject.isCreature(); + return mageObject != null && mageObject.isCreature(); } return false; } diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java index 268b58f7ef..db8796ba96 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java @@ -1,4 +1,3 @@ - package org.mage.test.AI.basic; import mage.constants.PhaseStep; @@ -8,7 +7,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBaseAI; /** - * * @author LevelX2 */ public class CastCreaturesTest extends CardTestPlayerBaseAI { @@ -173,7 +171,7 @@ public class CastCreaturesTest extends CardTestPlayerBaseAI { /** * Tests that the creature is cast if enough mana is available. - * + *

* Once Ammit Eternal is cast against a computer AI opponent, the AI just * decides to sit there and only play basic lands. I've sat there and decked * it because it just plays lands. It's like it views giving the Ammit @@ -194,8 +192,11 @@ public class CastCreaturesTest extends CardTestPlayerBaseAI { addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ammit Eternal"); + + setStrictChooseMode(true); setStopAt(3, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertPermanentCount(playerB, "Ammit Eternal", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java index e1ddf99d6d..8c900e619c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.activated; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class LightningStormTest extends CardTestPlayerBase { @@ -16,10 +14,9 @@ public class LightningStormTest extends CardTestPlayerBase { * So, this just happened to me. My opponent cast Lightning Storm and while * it was on the stack I couldn't use the ability despite having land in * hand which isn't something I've had an issue with before. - * + *

* My opponent had a Leyline of Sanctity in play, so perhaps that was * causing the issue somehow? Does anyone want to try and replicate it? - * */ @Test public void ActivateByBothPlayersTest() { @@ -31,22 +28,32 @@ public class LightningStormTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Mountain"); addCard(Zone.HAND, playerB, "Mountain"); + // A activate castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Storm", playerB); + + // B discard and re-target activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Discard"); - setChoice(playerB, "playerA"); + setChoice(playerB, "Mountain"); + setChoice(playerB, "Yes"); + addTarget(playerB, playerA); + + // A discard and re-target activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); - setChoice(playerA, "playerB"); + setChoice(playerA, "Mountain"); + setChoice(playerA, "Yes"); + addTarget(playerA, playerB); + setStrictChooseMode(true); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); - execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Lightning Storm", 1); assertGraveyardCount(playerB, "Mountain", 1); assertGraveyardCount(playerA, "Mountain", 1); assertLife(playerA, 20); - assertLife(playerB, 13); + assertLife(playerB, 20 - 3 - 2 - 2); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java new file mode 100644 index 0000000000..b4013c696b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java @@ -0,0 +1,128 @@ +package org.mage.test.cards.asthough; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class PlayTopCardFromLibraryTest extends CardTestPlayerBase { + + /* + Bolas's Citadel + {3}{B}{B}{B} + You may look at the top card of your library any time. + You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost. + {T}, Sacrifice ten nonland permanents: Each opponent loses 10 life. + */ + + @Test + public void test_CreaturePlay() { + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); // 2 CMC + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Balduvian Bears", 1); + assertLife(playerA, 20 - 2); + } + + @Test + public void test_CreaturePlay2() { + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Balduvian Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Vizier of the Menagerie", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Balduvian Bears", 1); + } + + @Test + public void test_ManaCostmodifications() { + // + // {5}{B}{B} + // You may cast Scourge of Nel Toth from your graveyard by paying {B}{B} and sacrificing two creatures rather than paying its mana cost. + addCard(Zone.GRAVEYARD, playerA, "Scourge of Nel Toth", 1); + addCard(Zone.BATTLEFIELD, playerA, "Kitesail Corsair", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Nel Toth"); + setChoice(playerA, "Kitesail Corsair"); + setChoice(playerA, "Kitesail Corsair"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Scourge of Nel Toth", 1); + assertLife(playerA, 20); + } + + @Test + public void test_SplitRightPlay() { + // https://github.com/magefree/mage/issues/5912 + // Bolas's citadel requires you to pay mana instead of life for a split card on top of library. + // + // Steps to reproduce: + // + // Bolas's Citadel in play, Revival//Revenge on top of library. + // Cast Revenge, choose target + // receive prompt to pay 4WB. + // + // Expected outcome + // + // No prompt for mana payment, payment of six life instead. + + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Revival // Revenge", 1); + addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1); + + // Double your life total. Target opponent loses half their life, rounded up. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revenge", playerB); // {4}{W}{B} = 6 life + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, (20 - 6) * 2); + assertLife(playerB, 20 / 2); + } + + @Test + public void test_SplitLeftPlay() { + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Revival // Revenge", 1); + addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1); + addCard(Zone.GRAVEYARD, playerA, "Balduvian Bears", 1); + + // Return target creature card with converted mana cost 3 or less from your graveyard to the battlefield. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revival", "Balduvian Bears"); // {W/B}{W/B} = 2 life + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20 - 2); + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Balduvian Bears", 0); + assertPermanentCount(playerA, "Balduvian Bears", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java index 1d117fc88b..3176f1d128 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.asthough; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class SpendOtherManaTest extends CardTestPlayerBase { @@ -47,7 +45,7 @@ public class SpendOtherManaTest extends CardTestPlayerBase { /** * Tron mana doesn't work with Oath of Nissa. (e.g. can't cast Chandra, * Flamecaller with Urza's Tower, Power Plant, and Mine.) - * + *

* AI don't get the Planeswalker as playable card (probably because of the * as thought effect) */ @@ -76,7 +74,7 @@ public class SpendOtherManaTest extends CardTestPlayerBase { * I was unable to cast Nissa, Voice of Zendikar using black mana with Oath * of Nissa in play. Pretty sure Oath is working usually, so here were the * conditions in my game: - * + *

* -Cast Dark Petition with spell mastery -Attempt to cast Nissa, Voice of * Zendikar using the triple black mana from Dark Petition */ @@ -122,20 +120,84 @@ public class SpendOtherManaTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Hostage Taker"); // {2}{U}{B} castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hostage Taker"); - setChoice(playerA, "Silvercoat Lion"); + addTarget(playerA, "Silvercoat Lion"); + // red mana must be used as any mana activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {R}."); // red mana to pool activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {R}."); // red mana to pool castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); // cast it from exile with red mana from pool + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertPermanentCount(playerA, "Hostage Taker", 1); assertTappedCount("Mountain", true, 4); assertPermanentCount(playerA, "Silvercoat Lion", 1); - } + @Test + public void test_QuicksilverElemental_Normal() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // {U}: Quicksilver Elemental gains all activated abilities of target creature until end of turn. + // You may spend blue mana as though it were mana of any color to pay the activation costs of Quicksilver Elemental’s abilities. + addCard(Zone.BATTLEFIELD, playerA, "Quicksilver Elemental"); // Creature {1}{W} + // {R}, {T}: Anaba Shaman deals 1 damage to any target. + addCard(Zone.BATTLEFIELD, playerB, "Anaba Shaman"); + + // gain abilities + checkPlayableAbility("must not have", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}, {T}:", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}:", "Anaba Shaman"); + + // use ability + checkPlayableAbility("must have new ability", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", true); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 20 - 1); + } + + @Test + public void test_QuicksilverElemental_Flicker() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // {U}: Quicksilver Elemental gains all activated abilities of target creature until end of turn. + // You may spend blue mana as though it were mana of any color to pay the activation costs of Quicksilver Elemental’s abilities. + addCard(Zone.BATTLEFIELD, playerA, "Quicksilver Elemental"); // Creature {1}{W} + // {R}, {T}: Anaba Shaman deals 1 damage to any target. + addCard(Zone.BATTLEFIELD, playerB, "Anaba Shaman"); + // Exile target nontoken permanent, then return it to the battlefield under its owner’s control. + addCard(Zone.HAND, playerA, "Flicker"); // {1}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // gain abilities + checkPlayableAbility("must not have", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}, {T}:", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}:", "Anaba Shaman"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPlayableAbility("must have new ability", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}, {T}:", true); + + // renew target + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flicker", "Anaba Shaman"); + + // use ability + checkPlayableAbility("must save ability", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", true); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Flicker", 1); + assertLife(playerA, 20); + assertLife(playerB, 20 - 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java index 681b4981bf..3afca6ea16 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.continuous; import mage.constants.PhaseStep; @@ -7,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ @@ -23,20 +21,22 @@ public class PsychicIntrusionTest extends CardTestPlayerBase { // Target opponent reveals their hand. You choose a nonland card from that player's // graveyard or hand and exile it. You may cast that card for as long as it remains exiled, // and you may spend mana as though it were mana of any color to cast that spell. - addCard(Zone.HAND, playerA, "Psychic Intrusion", 1); + addCard(Zone.HAND, playerA, "Psychic Intrusion", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Island", 3); - - addCard(Zone.HAND, playerB, "Elspeth, Sun's Champion", 1); + + addCard(Zone.HAND, playerB, "Elspeth, Sun's Champion", 1); // {4}{W}{W} castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Intrusion", playerB); - addTarget(playerA, "Elspeth, Sun's Champion"); - + setChoice(playerA, "Elspeth, Sun's Champion"); + // cast from exile with any mana castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Elspeth, Sun's Champion"); - + + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); assertGraveyardCount(playerA, "Psychic Intrusion", 1); assertHandCount(playerB, "Elspeth, Sun's Champion", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java index 470ea3b23d..ac2e2d6fe6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java @@ -5,7 +5,6 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.game.permanent.Permanent; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -282,7 +281,8 @@ public class AdventureCardsTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Curious Pair"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Intrusion", playerB); - playerA.addChoice("Curious Pair"); + setChoice(playerA, "Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); @@ -462,6 +462,7 @@ public class AdventureCardsTest extends CardTestPlayerBase { removeAllCardsFromLibrary(playerA); addCard(Zone.LIBRARY, playerA, "Curious Pair"); + showAvaileableAbilities("abils", 1, PhaseStep.PRECOMBAT_MAIN, playerA); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); setStopAt(1, PhaseStep.BEGIN_COMBAT); @@ -473,7 +474,7 @@ public class AdventureCardsTest extends CardTestPlayerBase { } @Test - @Ignore("Not yet working correctly.") + //@Ignore("Not yet working correctly.") public void testCastTreatsToShareWithWrennAndSixEmblem() { /* * Wrenn and Six {R}{G} @@ -487,11 +488,16 @@ public class AdventureCardsTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Wrenn and Six"); addCard(Zone.GRAVEYARD, playerA, "Curious Pair"); - addCard(Zone.HAND, playerA, "Forest"); + addCard(Zone.HAND, playerA, "Forest"); // pay for retrace addCounters(1, PhaseStep.UPKEEP, playerA, "Wrenn and Six", CounterType.LOYALTY, 5); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-7: You get an emblem"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + showAvaileableAbilities("abils", 1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // retrace - You may cast this card from your graveyard by discarding a land card as an additional cost to cast it castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); + setChoice(playerA, "Forest"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -523,6 +529,8 @@ public class AdventureCardsTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Curious Pair"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Until your next"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + showAvaileableAbilities("abils", 1, PhaseStep.BEGIN_COMBAT, playerA); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Treats to Share"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/BolassCitadelTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/BolassCitadelTest.java index f5932c7401..ee0d7c27d6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/BolassCitadelTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/BolassCitadelTest.java @@ -32,7 +32,6 @@ public class BolassCitadelTest extends CardTestPlayerBase { } @Test - @Ignore("This is broken for now.") public void testCastAdventure() { /* * Curious Pair {1}{G} @@ -44,7 +43,6 @@ public class BolassCitadelTest extends CardTestPlayerBase { * Create a Food token. */ setStrictChooseMode(true); - addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel"); removeAllCardsFromLibrary(playerA); addCard(Zone.LIBRARY, playerA, "Curious Pair"); @@ -54,7 +52,6 @@ public class BolassCitadelTest extends CardTestPlayerBase { execute(); assertAllCommandsUsed(); - assertTapped("Forest", false); assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 1); assertExileCount(playerA, "Curious Pair", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java index bdde801cd4..4b9330199d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java @@ -6,7 +6,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * also tests regenerate and tests that permanents with protection can be * sacrificed * @@ -46,10 +45,13 @@ public class FiendOfTheShadowsTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Swamp"); attack(1, playerA, "Fiend of the Shadows"); + addTarget(playerB, "Swamp"); playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Swamp"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertLife(playerA, 20); assertLife(playerB, 17); 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 0c1a2bfd5a..14834dc934 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 @@ -2374,20 +2374,25 @@ public class TestPlayer implements Player { } @Override - public UUID getCastSourceIdWithAlternateMana() { + public Set getCastSourceIdWithAlternateMana() { return computerPlayer.getCastSourceIdWithAlternateMana(); } @Override - public ManaCosts getCastSourceIdManaCosts() { + public Map> getCastSourceIdManaCosts() { return computerPlayer.getCastSourceIdManaCosts(); } @Override - public Costs getCastSourceIdCosts() { + public Map> getCastSourceIdCosts() { return computerPlayer.getCastSourceIdCosts(); } + @Override + public void clearCastSourceIdManaCosts() { + computerPlayer.clearCastSourceIdManaCosts(); + } + @Override public boolean isInPayManaMode() { return computerPlayer.isInPayManaMode(); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 97465f1770..d604b41e58 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -19,8 +19,8 @@ import mage.counters.Counter; import mage.counters.Counters; import mage.designations.Designation; import mage.designations.DesignationType; -import mage.filter.FilterPermanent; import mage.filter.FilterMana; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.Graveyard; import mage.game.Table; @@ -1208,7 +1208,7 @@ public class PlayerStub implements Player { } @Override - public UUID getCastSourceIdWithAlternateMana() { + public Set getCastSourceIdWithAlternateMana() { return null; } @@ -1218,15 +1218,20 @@ public class PlayerStub implements Player { } @Override - public ManaCosts getCastSourceIdManaCosts() { + public Map> getCastSourceIdCosts() { return null; } @Override - public Costs getCastSourceIdCosts() { + public Map> getCastSourceIdManaCosts() { return null; } + @Override + public void clearCastSourceIdManaCosts() { + + } + @Override public void addPermissionToShowHandCards(UUID watcherUserId) { @@ -1374,19 +1379,19 @@ public class PlayerStub implements Player { @Override public void addPhyrexianToColors(FilterMana colors) { - + } @Override public void removePhyrexianFromColors(FilterMana colors) { - + } @Override public FilterMana getPhyrexianColors() { return (new FilterMana()); } - + @Override public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { return card.getSpellAbility(); diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 0a76898c13..c5da457462 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -63,7 +63,7 @@ public class SpellAbility extends ActivatedAbilityImpl { */ public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) { MageObject object = game.getObject(sourceId); - if ((Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) { + if (game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) { return (Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability) } return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase @@ -98,7 +98,7 @@ public class SpellAbility extends ActivatedAbilityImpl { if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) { Player player = game.getPlayer(playerId); if (player != null - && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) { + && player.getCastSourceIdWithAlternateMana().contains(getSourceId())) { return ActivationStatus.getFalse(); } } diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java index 03ed5c0b3d..a21b0e3a25 100644 --- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java @@ -1,6 +1,5 @@ package mage.abilities.effects; -import java.util.UUID; import mage.abilities.Ability; import mage.constants.AsThoughEffectType; import mage.constants.Duration; @@ -8,8 +7,9 @@ import mage.constants.EffectType; import mage.constants.Outcome; import mage.game.Game; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect { @@ -29,10 +29,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements @Override public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + // affectedControllerId = player to check if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) { return applies(objectId, source, playerId, game); } else { - return applies(objectId, source, affectedAbility.getControllerId(), game); + return applies(objectId, source, playerId, game); } } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 598a4c2eab..192328bc1d 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -505,12 +505,19 @@ public class ContinuousEffects implements Serializable { UUID idToCheck; if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) { idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId(); + } else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell + && type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE + && type != AsThoughEffectType.CAST_AS_INSTANT) { + // adventure spell uses alternative characteristics for spell/stack + idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId(); } else { Card card = game.getCard(objectId); - if (card != null && card instanceof SplitCardHalf) { + if (card instanceof SplitCardHalf) { idToCheck = ((SplitCardHalf) card).getParentCard().getId(); - } else if (card != null && type == AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE - && card instanceof AdventureCardSpell) { + } else if (card instanceof AdventureCardSpell + && type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE + && type != AsThoughEffectType.CAST_AS_INSTANT) { + // adventure spell uses alternative characteristics for spell/stack idToCheck = ((AdventureCardSpell) card).getParentCard().getId(); } else { idToCheck = objectId; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java index abc8b88b92..89ad63e0ab 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java @@ -51,7 +51,7 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing Spell spell = game.getStack().getSpell(source.getId()); if (spell != null && !spell.isCopy()) { Card spellCard = spell.getCard(); - if (spellCard != null && spellCard instanceof AdventureCardSpell) { + if (spellCard instanceof AdventureCardSpell) { UUID exileId = adventureExileId(controller.getId(), game); game.getExile().createZone(exileId, "On an Adventure"); AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java index 446876b1a5..63078c0d75 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java @@ -1,11 +1,6 @@ - package mage.abilities.effects.common.continuous; -import java.util.UUID; - -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.cards.Card; import mage.constants.AsThoughEffectType; @@ -15,6 +10,8 @@ import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** * @author nantuko */ @@ -55,26 +52,18 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { - Card cardOnTop = game.getCard(objectId); - Card cardToCheckProperties = cardOnTop; + Card cardToCheck = game.getCard(objectId); + objectId = game.getCard(objectId).getMainCard().getId(); // for split cards - // Check each ability individually, as e.g. Adventures and associated creatures may get different results from the filter. - if (affectedAbility != null) { - MageObject sourceObject = affectedAbility.getSourceObject(game); - if (sourceObject != null && sourceObject instanceof Card) { - cardToCheckProperties = (Card) sourceObject; - } - } - - if (cardOnTop != null + if (cardToCheck != null && playerId.equals(source.getControllerId()) - && cardOnTop.isOwnedBy(source.getControllerId()) - && (!cardToCheckProperties.getManaCost().isEmpty() || cardToCheckProperties.isLand()) - && filter.match(cardToCheckProperties, game)) { - Player player = game.getPlayer(cardOnTop.getOwnerId()); - if (player != null && cardOnTop.equals(player.getLibrary().getFromTop(game))) { - return true; - } + && cardToCheck.isOwnedBy(source.getControllerId()) + && (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand()) + && filter.match(cardToCheck, game)) { + Player player = game.getPlayer(cardToCheck.getOwnerId()); + + UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId(); + return objectId.equals(needCardID); } return false; } diff --git a/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java b/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java index 8d1906594f..4ecae76699 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java @@ -100,6 +100,7 @@ class HideawayExileEffect extends OneShotEffect { cards.addAll(controller.getLibrary().getTopCards(game, 4)); if (!cards.isEmpty()) { TargetCard target1 = new TargetCard(Zone.LIBRARY, filter1); + target1.setNotTarget(true); if (controller.choose(Outcome.Detriment, cards, target1, game)) { Card card = cards.get(target1.getFirstTarget(), game); if (card != null) { diff --git a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java index 3f0c3037dd..48de601e55 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java @@ -1,16 +1,10 @@ - package mage.abilities.keyword; -import java.util.Iterator; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.StaticAbility; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.costs.OptionalAdditionalCost; -import mage.abilities.costs.OptionalAdditionalCostImpl; -import mage.abilities.costs.OptionalAdditionalSourceCosts; +import mage.abilities.costs.*; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; @@ -23,8 +17,9 @@ import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; +import java.util.Iterator; + /** - * * @author LevelX2 */ public class ReplicateAbility extends StaticAbility implements OptionalAdditionalSourceCosts { @@ -91,12 +86,12 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona String times = ""; if (additionalCost.isRepeatable()) { int numActivations = additionalCost.getActivateCount(); - times = Integer.toString(numActivations + 1) + (numActivations == 0 ? " time " : " times "); + times = (numActivations + 1) + (numActivations == 0 ? " time " : " times "); } if (additionalCost.canPay(ability, sourceId, controllerId, game) && player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(times).append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) { additionalCost.activate(); - for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) { + for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) { Cost cost = (Cost) it.next(); if (cost instanceof ManaCostsImpl) { ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); @@ -170,7 +165,7 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl { if (card != null) { for (Ability ability : card.getAbilities(game)) { if (ability instanceof ReplicateAbility) { - if (((ReplicateAbility) ability).isActivated()) { + if (ability.isActivated()) { for (Effect effect : this.getEffects()) { effect.setValue("ReplicateSpell", spell); effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount()); @@ -213,7 +208,7 @@ class ReplicateCopyEffect extends OneShotEffect { if (card != null) { for (Ability ability : card.getAbilities(game)) { if (ability instanceof ReplicateAbility) { - if (((ReplicateAbility) ability).isActivated()) { + if (ability.isActivated()) { ((ReplicateAbility) ability).resetReplicate(); } } diff --git a/Mage/src/main/java/mage/cards/AdventureCard.java b/Mage/src/main/java/mage/cards/AdventureCard.java index 3df44282d8..d100ae2770 100644 --- a/Mage/src/main/java/mage/cards/AdventureCard.java +++ b/Mage/src/main/java/mage/cards/AdventureCard.java @@ -4,7 +4,8 @@ import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; import mage.abilities.SpellAbility; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Zone; import mage.game.Game; import java.util.List; @@ -26,7 +27,7 @@ public abstract class AdventureCard extends CardImpl { public AdventureCard(AdventureCard card) { super(card); this.spellCard = card.getSpellCard().copy(); - ((AdventureCardSpell)this.spellCard).setParentCard(this); + ((AdventureCardSpell) this.spellCard).setParentCard(this); } public Card getSpellCard() { @@ -91,6 +92,11 @@ public abstract class AdventureCard extends CardImpl { return allAbilities; } + public Abilities getSharedAbilities() { + // abilities without spellcard + return super.getAbilities(); + } + @Override public void setOwnerId(UUID ownerId) { super.setOwnerId(ownerId); diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java b/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java index 16b94a10db..f14cc4bfb5 100644 --- a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java +++ b/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.UUID; /** - * * @author phulin */ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell { @@ -112,7 +111,7 @@ class AdventureCardSpellAbility extends SpellAbility { public ActivationStatus canActivate(UUID playerId, Game game) { ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game)); Card spellCard = game.getCard(this.getSourceId()); - if (spellCard != null && spellCard instanceof AdventureCardSpell) { + if (spellCard instanceof AdventureCardSpell) { Card card = ((AdventureCardSpell) spellCard).getParentCard(); if (adventureExileZone != null && adventureExileZone.contains(card.getId())) { return ActivationStatus.getFalse(); diff --git a/Mage/src/main/java/mage/constants/AsThoughEffectType.java b/Mage/src/main/java/mage/constants/AsThoughEffectType.java index de81133a05..808a99277d 100644 --- a/Mage/src/main/java/mage/constants/AsThoughEffectType.java +++ b/Mage/src/main/java/mage/constants/AsThoughEffectType.java @@ -1,7 +1,6 @@ package mage.constants; /** - * * @author North */ public enum AsThoughEffectType { @@ -19,15 +18,29 @@ public enum AsThoughEffectType { BLOCK_FORESTWALK, DAMAGE_NOT_BLOCKED, BE_BLOCKED, - PLAY_FROM_NOT_OWN_HAND_ZONE, // do not use dialogs in "applies" method for that type of effect (it calls multiple times) + + // PLAY_FROM_NOT_OWN_HAND_ZONE + CAST_AS_INSTANT: + // 1. Do not use dialogs in "applies" method for that type of effect (it calls multiple times and will freeze the game) + // 2. All effects in "applies" must checks affectedControllerId.equals(source.getControllerId()) (if not then all players will be able to play it) + // 3. Target points to mainCard, but card's characteristics from objectId (split, adventure) + // TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId + PLAY_FROM_NOT_OWN_HAND_ZONE, CAST_AS_INSTANT, + ACTIVATE_AS_INSTANT, DAMAGE, SHROUD, HEXPROOF, PAY_0_ECHO, LOOK_AT_FACE_DOWN, + + // SPEND_OTHER_MANA: + // 1. It's uses for mana calcs at any zone, not stack only + // 2. Compare zone change counter as "objectZCC <= targetZCC + 1" + // 3. Compare zone with original (like exiled) and stack, not stack only + // TODO: search all SPEND_ONLY_MANA effects and improve counters compare as SPEND_OTHER_MANA SPEND_OTHER_MANA, + SPEND_ONLY_MANA, TARGET } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 1e8ddbb897..0bc22dbc94 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -20,8 +20,8 @@ import mage.counters.Counter; import mage.counters.Counters; import mage.designations.Designation; import mage.designations.DesignationType; -import mage.filter.FilterPermanent; import mage.filter.FilterMana; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.Graveyard; import mage.game.Table; @@ -75,7 +75,7 @@ public interface Player extends MageItem, Copyable { void setLife(int life, Game game, UUID sourceId); /** - * @param amount amount of life loss + * @param amount amount of life loss * @param game * @param atCombat was the source combat damage * @return @@ -351,7 +351,7 @@ public interface Player extends MageItem, Copyable { * @param source * @param game * @param targetPlayerId player whose library will be searched - * @param triggerEvents whether searching will trigger any game events + * @param triggerEvents whether searching will trigger any game events * @return true if search was successful */ boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents); @@ -372,23 +372,23 @@ public interface Player extends MageItem, Copyable { /** * Plays a card if possible * - * @param card the card that can be cast + * @param card the card that can be cast * @param game - * @param noMana if it's a spell i can be cast without paying mana + * @param noMana if it's a spell i can be cast without paying mana * @param ignoreTiming if it's cast during the resolution of another spell - * no sorcery or play land timing restriction are checked. For a land it has - * to be the turn of the player playing that card. - * @param reference mage object that allows to play the card + * no sorcery or play land timing restriction are checked. For a land it has + * to be the turn of the player playing that card. + * @param reference mage object that allows to play the card * @return */ boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference); /** - * @param card the land card to play + * @param card the land card to play * @param game * @param ignoreTiming false - it won't be checked if the stack is empty and - * you are able to play a Sorcery. It's still checked, if you are able to - * play a land concerning the number of lands you already played. + * you are able to play a Sorcery. It's still checked, if you are able to + * play a land concerning the number of lands you already played. * @return */ boolean playLand(Card card, Game game, boolean ignoreTiming); @@ -534,11 +534,11 @@ public interface Player extends MageItem, Copyable { /** * Moves the cards from cards to the bottom of the players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * else random order - * @param source - source ability + * else random order + * @param source - source ability * @return */ boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -559,10 +559,10 @@ public interface Player extends MageItem, Copyable { /** * Moves the cards from cards to the top of players library. * - * @param cards - list of cards that have to be moved - * @param game - game + * @param cards - list of cards that have to be moved + * @param game - game * @param anyOrder - true if player can determine the order of the cards - * @param source - source ability + * @param source - source ability * @return */ boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); @@ -593,8 +593,8 @@ public interface Player extends MageItem, Copyable { /** * Choose the order in which blockers get damage assigned to * - * @param blockers list of blockers where to choose the next one from - * @param combatGroup the concerning combat group + * @param blockers list of blockers where to choose the next one from + * @param combatGroup the concerning combat group * @param blockerOrder the already set order of blockers * @param game * @return blocker next to add to the blocker order @@ -664,7 +664,7 @@ public interface Player extends MageItem, Copyable { * @param card * @param game * @param abilitiesToActivate extra info about abilities that can be - * activated on NO option + * activated on NO option * @return player looked at the card */ boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate); @@ -737,11 +737,11 @@ public interface Player extends MageItem, Copyable { * @param toZone * @param source * @param game - * @param tapped the cards are tapped on the battlefield - * @param faceDown the cards are face down in the to zone - * @param byOwner the card is moved (or put onto battlefield) by the owner - * of the card and if target zone is battlefield controls the permanent - * (instead of the controller of the source) + * @param tapped the cards are tapped on the battlefield + * @param faceDown the cards are face down in the to zone + * @param byOwner the card is moved (or put onto battlefield) by the owner + * of the card and if target zone is battlefield controls the permanent + * (instead of the controller of the source) * @param appliedEffects * @return */ @@ -777,7 +777,7 @@ public interface Player extends MageItem, Copyable { * list of applied effects is not saved * * @param card - * @param exileId exile zone id (optional) + * @param exileId exile zone id (optional) * @param exileName name of exile zone (optional) * @param sourceId * @param game @@ -819,7 +819,7 @@ public interface Player extends MageItem, Copyable { * @param sourceId * @param game * @param fromZone if null, this info isn't postet - * @param toTop to the top of the library else to the bottom + * @param toTop to the top of the library else to the bottom * @param withName show the card name in the log * @return */ @@ -844,18 +844,20 @@ public interface Player extends MageItem, Copyable { * without mana (null) or the mana set to manaCosts instead of its normal * mana costs. * - * @param sourceId the source that can be cast without mana + * @param sourceId the source that can be cast without mana * @param manaCosts alternate ManaCost, null if it can be cast without mana - * cost - * @param costs alternate other costs you need to pay + * cost + * @param costs alternate other costs you need to pay */ void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs); - UUID getCastSourceIdWithAlternateMana(); + Set getCastSourceIdWithAlternateMana(); - ManaCosts getCastSourceIdManaCosts(); + Map> getCastSourceIdManaCosts(); - Costs getCastSourceIdCosts(); + Map> getCastSourceIdCosts(); + + void clearCastSourceIdManaCosts(); // permission handling to show hand cards void addPermissionToShowHandCards(UUID watcherUserId); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 33584270c1..fa1e7b6598 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -32,8 +32,8 @@ import mage.counters.Counters; import mage.designations.Designation; import mage.designations.DesignationType; import mage.filter.FilterCard; -import mage.filter.FilterPermanent; import mage.filter.FilterMana; +import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterCreatureForCombatBlock; @@ -160,9 +160,10 @@ public abstract class PlayerImpl implements Player, Serializable { protected boolean reachedNextTurnAfterLeaving = false; // indicates that the spell with the set sourceId can be cast with an alternate mana costs (can also be no mana costs) - protected UUID castSourceIdWithAlternateMana; - protected ManaCosts castSourceIdManaCosts; - protected Costs castSourceIdCosts; + // support multiple cards with alternative mana cost + protected Set castSourceIdWithAlternateMana = new HashSet<>(); + protected Map> castSourceIdManaCosts = new HashMap<>(); + protected Map> castSourceIdCosts = new HashMap<>(); // indicates that the player is in mana payment phase protected boolean payManaMode = false; @@ -271,9 +272,10 @@ public abstract class PlayerImpl implements Player, Serializable { this.priorityTimeLeft = player.getPriorityTimeLeft(); this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving; - this.castSourceIdWithAlternateMana = player.castSourceIdWithAlternateMana; - this.castSourceIdManaCosts = player.castSourceIdManaCosts; - this.castSourceIdCosts = player.castSourceIdCosts; + this.castSourceIdWithAlternateMana.addAll(player.castSourceIdWithAlternateMana); + this.castSourceIdManaCosts.putAll(player.castSourceIdManaCosts); + this.castSourceIdCosts.putAll(player.castSourceIdCosts); + this.payManaMode = player.payManaMode; this.phyrexianColors = player.phyrexianColors.copy(); @@ -340,9 +342,12 @@ public abstract class PlayerImpl implements Player, Serializable { this.turnControllers.clear(); this.turnControllers.addAll(player.getTurnControllers()); this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving(); - this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana(); - this.castSourceIdManaCosts = player.getCastSourceIdManaCosts(); - this.castSourceIdCosts = player.getCastSourceIdCosts(); + + this.clearCastSourceIdManaCosts(); + this.castSourceIdWithAlternateMana.addAll(player.getCastSourceIdWithAlternateMana()); + this.castSourceIdManaCosts.putAll(player.getCastSourceIdManaCosts()); + this.castSourceIdCosts.putAll(player.getCastSourceIdCosts()); + this.phyrexianColors = player.getPhyrexianColors().copy(); this.designations.clear(); @@ -417,9 +422,8 @@ public abstract class PlayerImpl implements Player, Serializable { this.setLife(game.getLife(), game, (UUID) null); this.setReachedNextTurnAfterLeaving(false); - this.castSourceIdWithAlternateMana = null; - this.castSourceIdManaCosts = null; - this.castSourceIdCosts = null; + this.clearCastSourceIdManaCosts(); + this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left this.phyrexianColors = new FilterMana(); @@ -444,9 +448,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.canPlayCardsFromGraveyard = false; this.topCardRevealed = false; this.alternativeSourceCosts.clear(); - this.castSourceIdWithAlternateMana = null; - this.castSourceIdManaCosts = null; - this.castSourceIdCosts = null; + this.clearCastSourceIdManaCosts(); this.getManaPool().clearEmptyManaPoolRules(); this.phyrexianColors = new FilterMana(); } @@ -617,7 +619,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) { + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) { return false; } } @@ -625,7 +627,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromWhiteAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && source.getColor(game).isWhite()) { return false; } @@ -634,7 +636,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromBlueAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && source.getColor(game).isBlue()) { return false; } @@ -643,7 +645,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromBlackAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && source.getColor(game).isBlack()) { return false; } @@ -652,7 +654,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilities.containsKey(HexproofFromMonocoloredAbility.getInstance().getId())) { if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) && null == game.getContinuousEffects().asThough(this.getId(), - AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) + AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) && !source.getColor(game).isColorless() && !source.getColor(game).isMulticolored()) { return false; @@ -695,7 +697,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(getLogName() + " discards down to " + this.maxHandSize + (this.maxHandSize == 1 - ? " hand card" : " hand cards")); + ? " hand card" : " hand cards")); } discard(hand.size() - this.maxHandSize, null, game); } @@ -813,7 +815,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (card != null) { GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, card.getId(), source == null - ? null : source.getSourceId(), playerId); + ? null : source.getSourceId(), playerId); gameEvent.setFlag(source != null); // event from effect or from cost (source == null) if (!game.replaceEvent(gameEvent, source)) { // write info to game log first so game log infos from triggered or replacement effects follow in the game log @@ -828,7 +830,7 @@ public abstract class PlayerImpl implements Player, Serializable { // So discard is also successful if card is moved to another zone by replacement effect! game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, card.getId(), source == null - ? null : source.getSourceId(), playerId)); + ? null : source.getSourceId(), playerId)); return true; } } @@ -1061,26 +1063,33 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs) { - castSourceIdWithAlternateMana = sourceId; - castSourceIdManaCosts = manaCosts; - castSourceIdCosts = costs; + castSourceIdWithAlternateMana.add(sourceId); + castSourceIdManaCosts.put(sourceId, manaCosts); + castSourceIdCosts.put(sourceId, costs); } @Override - public UUID getCastSourceIdWithAlternateMana() { + public Set getCastSourceIdWithAlternateMana() { return castSourceIdWithAlternateMana; } @Override - public Costs getCastSourceIdCosts() { + public Map> getCastSourceIdCosts() { return castSourceIdCosts; } @Override - public ManaCosts getCastSourceIdManaCosts() { + public Map> getCastSourceIdManaCosts() { return castSourceIdManaCosts; } + @Override + public void clearCastSourceIdManaCosts() { + this.castSourceIdCosts.clear(); + this.castSourceIdManaCosts.clear(); + this.castSourceIdWithAlternateMana.clear(); + } + @Override public void setPayManaMode(boolean payManaMode) { this.payManaMode = payManaMode; @@ -1144,11 +1153,13 @@ public abstract class PlayerImpl implements Player, Serializable { } // Update the zcc to the stack ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId())); + + // ALTERNATIVE COST from dynamic effects // some effects set sourceId to cast without paying mana costs or other costs - if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) { + if (getCastSourceIdWithAlternateMana().contains(ability.getSourceId())) { Ability spellAbility = spell.getSpellAbility(); - ManaCosts alternateCosts = getCastSourceIdManaCosts(); - Costs costs = getCastSourceIdCosts(); + ManaCosts alternateCosts = getCastSourceIdManaCosts().get(ability.getSourceId()); + Costs costs = getCastSourceIdCosts().get(ability.getSourceId()); if (alternateCosts == null) { noMana = true; } else { @@ -1162,7 +1173,8 @@ public abstract class PlayerImpl implements Player, Serializable { spellAbility.getCosts().addAll(costs); } } - setCastSourceIdWithAlternateMana(null, null, null); + clearCastSourceIdManaCosts(); // TODO: test multiple alternative cost for different cards as same time + GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject); game.fireEvent(event); @@ -1506,7 +1518,7 @@ public abstract class PlayerImpl implements Player, Serializable { // Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities // as candidates. private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities candidateAbilites, - LinkedHashMap output) { + LinkedHashMap output) { boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); ManaOptions availableMana = null; // ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly @@ -1555,10 +1567,10 @@ public abstract class PlayerImpl implements Player, Serializable { != null // if anyone sees an issue with this code, please report it. Worked in my testing. || game.getContinuousEffects().asThough(object.getId(), - AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, - ability, - this.getId(), - game) + AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, + ability, + this.getId(), + game) != null) { if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { @@ -1913,9 +1925,9 @@ public abstract class PlayerImpl implements Player, Serializable { } private List getPermanentsThatCanBeUntapped(Game game, - List canBeUntapped, - RestrictionUntapNotMoreThanEffect handledEffect, - Map>, Integer> notMoreThanEffectsUsage) { + List canBeUntapped, + RestrictionUntapNotMoreThanEffect handledEffect, + Map>, Integer> notMoreThanEffectsUsage) { List leftForUntap = new ArrayList<>(); // select permanents that can still be untapped for (Permanent permanent : canBeUntapped) { @@ -2623,7 +2635,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, - boolean triggerEvents) { + boolean triggerEvents) { //20091005 - 701.14c Library searchedLibrary = null; String searchInfo = null; @@ -2827,7 +2839,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param game * @param appliedEffects - * @param numSides Number of sides the dice has + * @param numSides Number of sides the dice has * @return the number that the player rolled */ @Override @@ -2864,16 +2876,16 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param game * @param appliedEffects - * @param numberChaosSides The number of chaos sides the planar die - * currently has (normally 1 but can be 5) + * @param numberChaosSides The number of chaos sides the planar die + * currently has (normally 1 but can be 5) * @param numberPlanarSides The number of chaos sides the planar die - * currently has (normally 1) + * currently has (normally 1) * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll * or NilRoll */ @Override public PlanarDieRoll rollPlanarDie(Game game, ArrayList appliedEffects, int numberChaosSides, - int numberPlanarSides) { + int numberPlanarSides) { int result = RandomUtil.nextInt(9) + 1; PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL; if (numberChaosSides + numberPlanarSides > 9) { @@ -3030,7 +3042,7 @@ public abstract class PlayerImpl implements Player, Serializable { /** * @param ability - * @param available if null, it won't be checked if enough mana is available + * @param available if null, it won't be checked if enough mana is available * @param sourceObject * @param game * @return @@ -3046,7 +3058,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.getContinuousEffects().costModification(copy, game); } - Card card = game.getCard(ability.getSourceId()); + Card card = game.getCard(copy.getSourceId()); if (card != null) { for (Ability ability0 : card.getAbilities()) { if (ability0 instanceof AdjustingSourceCosts) { @@ -3072,10 +3084,16 @@ public abstract class PlayerImpl implements Player, Serializable { if (available == null) { return true; } - MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(), - AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game); + MageObjectReference permittingObject = game.getContinuousEffects().asThough(copy.getSourceId(), + AsThoughEffectType.SPEND_OTHER_MANA, copy, copy.getControllerId(), game); for (Mana mana : abilityOptions) { for (Mana avail : available) { + // TODO: SPEND_OTHER_MANA effects with getAsThoughManaType can change mana type to pay, + // but that code processing it as any color, need to test and fix another use cases + // (example: Sunglasses of Urza - may spend white mana as though it were red mana) + + // + // add tests for non any color like Sunglasses of Urza if (permittingObject != null && mana.count() <= avail.count()) { return true; } @@ -3087,14 +3105,35 @@ public abstract class PlayerImpl implements Player, Serializable { } } + // ALTERNATIVE COST from source card (AlternativeCostSourceAbility) for (Ability objectAbility : sourceObject.getAbilities()) { if (objectAbility instanceof AlternativeCostSourceAbility) { - if (objectAbility.getCosts().canPay(ability, ability.getSourceId(), playerId, game)) { + if (objectAbility.getCosts().canPay(copy, copy.getSourceId(), playerId, game)) { return true; } } } - return canPlayCardByAlternateCost(card, available, ability, game); + + // ALTERNATIVE COST FROM dynamic effects + if (getCastSourceIdWithAlternateMana().contains(copy.getSourceId())) { + ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId()); + Costs costs = getCastSourceIdCosts().get(copy.getSourceId()); + + boolean canPutToPlay = true; + if (alternateCosts != null && !alternateCosts.canPay(copy, copy.getSourceId(), playerId, game)) { + canPutToPlay = false; + } + if (costs != null && !costs.canPay(copy, copy.getSourceId(), playerId, game)) { + canPutToPlay = false; + } + + if (canPutToPlay) { + return true; + } + } + + // ALTERNATIVE COST from source card (any AlternativeSourceCosts) + return canPlayCardByAlternateCost(card, available, copy, game); } return false; } @@ -3215,35 +3254,85 @@ public abstract class PlayerImpl implements Player, Serializable { } } - private List cardPlayableAbilities(Game game, Card card, boolean setControllerId) { - List playable = new ArrayList(); - if (card != null) { - for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) { - if (!ability.canActivate(playerId, game).canActivate()) { - continue; - } + private void getPlayableFromNonHandCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List output) { + if (fromZone == null) { + return; + } - UUID savedControllerId = null; - if (setControllerId) { - // For when owner != caster, e.g. with Psychic Intrusion and similar effects. - savedControllerId = getId(); - ability.setControllerId(getId()); - } - if (ability instanceof SpellAbility - && null != game.getContinuousEffects().asThough(card.getId(), - AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, getId(), game)) { - playable.add(ability); - } else if (ability instanceof PlayLandAbility - && null != game.getContinuousEffects().asThough(card.getId(), - AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) { - playable.add(ability); - } - if (setControllerId) { - ability.setControllerId(savedControllerId); - } + // BASIC abilities + if (card instanceof SplitCard) { + SplitCard splitCard = (SplitCard) card; + getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, output); + getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, output); + getPlayableFromNonHandCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(), availableMana, output); + } else if (card instanceof AdventureCard) { + // adventure must use different card characteristics for different spells (main or adventure) + AdventureCard adventureCard = (AdventureCard) card; + getPlayableFromNonHandCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(), availableMana, output); + getPlayableFromNonHandCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(), availableMana, output); + } else { + getPlayableFromNonHandCardSingle(game, fromZone, card, card.getAbilities(), availableMana, output); + } + + // DYNAMIC ADDED abilities + if (fromZone != Zone.ALL) { // TODO: test revealed cards with dynamic added abilities + // Other activated abilities (added dynamic by effects) + LinkedHashMap useable; + if (card instanceof AdventureCard) { + // adventure cards (contains two different cards: main and adventure spell) + useable = new LinkedHashMap<>(); + getOtherUseableActivatedAbilities(((AdventureCard) card).getSpellCard(), fromZone, game, useable); + output.addAll(useable.values()); + + useable = new LinkedHashMap<>(); + getOtherUseableActivatedAbilities(card, fromZone, game, useable); + output.addAll(useable.values()); + } else { + // all other cards (TODO: check split cards with dynamic added abilities) + useable = new LinkedHashMap<>(); + getOtherUseableActivatedAbilities(card, fromZone, game, useable); + output.addAll(useable.values()); + } + } + } + + private void getPlayableFromNonHandCardSingle(Game game, Zone fromZone, Card card, Abilities candidateAbilities, ManaOptions availableMana, List output) { + + // check "can play from hand" condition as original controller (effects checks affected controller with source controller) + // TODO: remove card.getSpellAbility() ? + MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(), + AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), this.getId(), game); + boolean canActivateAsHandZone = permittingObject != null + || (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard()); + + // check "can play" condition as affected controller + for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) { + UUID savedControllerId = ability.getControllerId(); + ability.setControllerId(this.getId()); + try { + boolean possibleToPlay = false; + + // spell/hand abilities (play from all zones) + // need permitingObject or canPlayCardsFromGraveyard + if (canActivateAsHandZone + && ability.getZone().match(Zone.HAND) + && (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) { + possibleToPlay = true; + } + + // zone's abilities (play from specific zone) + // no need in permitingObject + if (fromZone != Zone.ALL && ability.getZone().match(fromZone)) { + possibleToPlay = true; + } + + if (possibleToPlay && canPlay(ability, availableMana, card, game)) { + output.add(ability); + } + } finally { + ability.setControllerId(savedControllerId); } } - return playable; } @Override @@ -3262,18 +3351,16 @@ public abstract class PlayerImpl implements Player, Serializable { } boolean fromAll = fromZone.equals(Zone.ALL); - Collection cards; if (hidden && (fromAll || fromZone == Zone.HAND)) { - cards = hideDuplicatedAbilities ? hand.getUniqueCards(game) : hand.getCards(game); - for (Card card : cards) { + for (Card card : hand.getCards(game)) { for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?) if (ability.getZone().match(Zone.HAND)) { if (ability instanceof ActivatedAbility) { if (!(ability instanceof PlayLandAbility) || !game.getContinuousEffects().preventedByRuleModification( - GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), - ability.getSourceId(), playerId), ability, game, true)) { + GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), + ability.getSourceId(), playerId), ability, game, true)) { if (canPlay((ActivatedAbility) ability, availableMana, card, game)) { playable.add(ability); } @@ -3297,37 +3384,15 @@ public abstract class PlayerImpl implements Player, Serializable { } if (fromAll || fromZone == Zone.GRAVEYARD) { - cards = hideDuplicatedAbilities ? graveyard.getUniqueCards(game) : graveyard.getCards(game); - for (Card card : cards) { - // Handle split cards in graveyard to support Aftermath - if (card instanceof SplitCard) { - SplitCard splitCard = (SplitCard) card; - getPlayableFromGraveyardCard(game, splitCard.getLeftHalfCard(), - splitCard.getLeftHalfCard().getAbilities(), availableMana, playable); - getPlayableFromGraveyardCard(game, splitCard.getRightHalfCard(), - splitCard.getRightHalfCard().getAbilities(), availableMana, playable); - getPlayableFromGraveyardCard(game, splitCard, splitCard.getSharedAbilities(), - availableMana, playable); - } else if (card instanceof AdventureCard) { - AdventureCard adventureCard = (AdventureCard) card; - getPlayableFromGraveyardCard(game, adventureCard.getSpellCard(), - adventureCard.getSpellCard().getAbilities(), availableMana, playable); - getPlayableFromGraveyardCard(game, adventureCard, adventureCard.getAbilities(), availableMana, playable); - } else { - getPlayableFromGraveyardCard(game, card, card.getAbilities(), availableMana, playable); - } - - // Other activated abilities - LinkedHashMap useable = new LinkedHashMap<>(); - getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable); - playable.addAll(useable.values()); + for (Card card : graveyard.getCards(game)) { + getPlayableFromNonHandCardAll(game, Zone.GRAVEYARD, card, availableMana, playable); } } if (fromAll || fromZone == Zone.EXILED) { for (ExileZone exile : game.getExile().getExileZones()) { for (Card card : exile.getCards(game)) { - playable.addAll(cardPlayableAbilities(game, card, true)); + getPlayableFromNonHandCardAll(game, Zone.EXILED, card, availableMana, playable); } } } @@ -3336,7 +3401,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (fromAll) { for (Cards revealedCards : game.getState().getRevealed().values()) { for (Card card : revealedCards.getCards(game)) { - playable.addAll(cardPlayableAbilities(game, card, false)); + // revealed cards can be from any zones + getPlayableFromNonHandCardAll(game, game.getState().getZone(card.getId()), card, availableMana, playable); } } } @@ -3348,7 +3414,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (player != null) { if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) { Card card = player.getLibrary().getFromTop(game); - playable.addAll(cardPlayableAbilities(game, card, false)); + getPlayableFromNonHandCardAll(game, Zone.LIBRARY, card, availableMana, playable); } } } @@ -3615,7 +3681,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId, - UUID controllerId, Game game + UUID controllerId, Game game ) { return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game); } @@ -3769,8 +3835,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCards(Card card, Zone toZone, - Ability source, Game game, - boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects + Ability source, Game game, + boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects ) { Set cardList = new HashSet<>(); if (card != null) { @@ -3781,22 +3847,22 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCards(Cards cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards.getCards(game), toZone, source, game); } @Override public boolean moveCards(Set cards, Zone toZone, - Ability source, Game game + Ability source, Game game ) { return moveCards(cards, toZone, source, game, false, false, false, null); } @Override public boolean moveCards(Set cards, Zone toZone, - Ability source, Game game, - boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects + Ability source, Game game, + boolean tapped, boolean faceDown, boolean byOwner, List appliedEffects ) { if (cards.isEmpty()) { return true; @@ -3891,8 +3957,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardsToExile(Card card, Ability source, - Game game, boolean withName, UUID exileId, - String exileZoneName + Game game, boolean withName, UUID exileId, + String exileZoneName ) { Set cards = new HashSet<>(); cards.add(card); @@ -3901,8 +3967,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardsToExile(Set cards, Ability source, - Game game, boolean withName, UUID exileId, - String exileZoneName + Game game, boolean withName, UUID exileId, + String exileZoneName ) { if (cards.isEmpty()) { return true; @@ -3918,14 +3984,14 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToHandWithInfo(Card card, UUID sourceId, - Game game + Game game ) { return this.moveCardToHandWithInfo(card, sourceId, game, true); } @Override public boolean moveCardToHandWithInfo(Card card, UUID sourceId, - Game game, boolean withName + Game game, boolean withName ) { boolean result = false; Zone fromZone = game.getState().getZone(card.getId()); @@ -3950,7 +4016,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public Set moveCardsToGraveyardWithInfo(Set allCards, Ability source, - Game game, Zone fromZone + Game game, Zone fromZone ) { UUID sourceId = source == null ? null : source.getSourceId(); Set movedCards = new LinkedHashSet<>(); @@ -3958,7 +4024,7 @@ public abstract class PlayerImpl implements Player, Serializable { // identify cards from one owner Cards cards = new CardsImpl(); UUID ownerId = null; - for (Iterator it = allCards.iterator(); it.hasNext();) { + for (Iterator it = allCards.iterator(); it.hasNext(); ) { Card card = it.next(); if (cards.isEmpty()) { ownerId = card.getOwnerId(); @@ -4021,7 +4087,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, - Game game, Zone fromZone + Game game, Zone fromZone ) { if (card == null) { return false; @@ -4050,8 +4116,8 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId, - Game game, Zone fromZone, - boolean toTop, boolean withName + Game game, Zone fromZone, + boolean toTop, boolean withName ) { if (card == null) { return false; @@ -4085,7 +4151,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, - Game game, Zone fromZone, boolean withName) { + Game game, Zone fromZone, boolean withName) { if (card == null) { return false; } @@ -4108,7 +4174,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) - + ' ' : "") + "to the exile zone"); + + ' ' : "") + "to the exile zone"); } result = true;