Merge pull request #6093 from magefree/asThough_and_non_hand_plays_rework

As though and non hand plays rework
This commit is contained in:
Oleg Agafonov 2019-12-14 16:54:02 +01:00 committed by GitHub
commit 71cd4b73b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 663 additions and 442 deletions

View file

@ -1742,7 +1742,7 @@ public class HumanPlayer extends PlayerImpl {
if (ability instanceof PlayLandAbility) { if (ability instanceof PlayLandAbility) {
return true; return true;
} }
if (!ability.getSourceId().equals(getCastSourceIdWithAlternateMana()) if (!getCastSourceIdWithAlternateMana().contains(ability.getSourceId())
&& ability.getManaCostsToPay().convertedManaCost() > 0) { && ability.getManaCostsToPay().convertedManaCost() > 0) {
return true; return true;
} }

View file

@ -96,15 +96,14 @@ class BolassCitadelPlayTheTopCardEffect extends AsThoughEffectImpl {
@Override @Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
Card cardOnTop = game.getCard(objectId); Card cardToCheck = game.getCard(objectId);
if (cardOnTop == null) { objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
return false;
} if (playerId.equals(source.getControllerId()) && cardToCheck.isOwnedBy(source.getControllerId())) {
if (playerId.equals(source.getControllerId()) Player controller = game.getPlayer(cardToCheck.getOwnerId());
&& cardOnTop.isOwnedBy(source.getControllerId())) {
Player controller = game.getPlayer(cardOnTop.getOwnerId());
if (controller != null 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) { if (affectedAbility instanceof ActivatedAbility) {
ActivatedAbility activatedAbility = (ActivatedAbility) affectedAbility; ActivatedAbility activatedAbility = (ActivatedAbility) affectedAbility;
// add the life cost first // add the life cost first

View file

@ -156,11 +156,12 @@ class CovetousUrgeSpendAnyManaEffect extends AsThoughEffectImpl implements AsTho
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); FixedTarget fixedTarget = ((FixedTarget) getTargetPointer());
return source.isControlledBy(affectedControllerId) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, fixedTarget.getTarget()) && Objects.equals(objectId, fixedTarget.getTarget())
&& fixedTarget.getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& game.getState().getZone(objectId) == Zone.STACK; && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
} }
@Override @Override

View file

@ -1,6 +1,5 @@
package mage.cards.c; package mage.cards.c;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughEffectImpl;
@ -21,8 +20,9 @@ import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.UUID;
/** /**
*
* @author Styxo * @author Styxo
*/ */
public final class CunningAbduction extends CardImpl { 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 objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget())
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { && 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 (effect must applies before and on stack, use isCheckPlayableMode?)
// if the card moved from exile to spell the zone change counter is increased by 1 return affectedControllerId.equals(source.getControllerId());
if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
return true;
}
}
} else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted // object has moved zone so effect can be discarted
this.discard(); this.discard();

View file

@ -1,7 +1,5 @@
package mage.cards.d; package mage.cards.d;
import java.util.Objects;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
@ -26,8 +24,10 @@ import mage.players.Player;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.Objects;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class DaxosOfMeletis extends CardImpl { public final class DaxosOfMeletis extends CardImpl {
@ -178,11 +178,11 @@ class DaxosOfMeletisSpendAnyManaEffect extends AsThoughEffectImpl implements AsT
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
FixedTarget fixedTarget = ((FixedTarget) getTargetPointer());
return source.isControlledBy(affectedControllerId) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) && Objects.equals(objectId, fixedTarget.getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
&& game.getState().getZone(objectId) == Zone.STACK;
} }
@Override @Override

View file

@ -1,7 +1,5 @@
package mage.cards.d; package mage.cards.d;
import java.util.Set;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.DiesAttachedTriggeredAbility; import mage.abilities.common.DiesAttachedTriggeredAbility;
@ -15,14 +13,7 @@ import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AsThoughEffectType; import mage.constants.*;
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.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.ControllerPredicate; import mage.filter.predicate.permanent.ControllerPredicate;
import mage.game.Game; import mage.game.Game;
@ -32,8 +23,10 @@ import mage.players.Player;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.Set;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class DeadMansChest extends CardImpl { public final class DeadMansChest extends CardImpl {
@ -142,9 +135,7 @@ class DeadMansChestCastFromExileEffect extends AsThoughEffectImpl {
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
if (objectId.equals(getTargetPointer().getFirst(game, source))) { if (objectId.equals(getTargetPointer().getFirst(game, source))) {
if (affectedControllerId.equals(source.getControllerId())) { return affectedControllerId.equals(source.getControllerId());
return true;
}
} else { } else {
if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted // 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 objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget())
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { && 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 (effect must applies before and on stack, use isCheckPlayableMode?)
// if the card moved from exile to spell the zone change counter is increased by 1 return affectedControllerId.equals(source.getControllerId());
if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
return true;
}
}
} else { } else {
if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted // object has moved zone so effect can be discarted

View file

@ -1,27 +1,15 @@
package mage.cards.d; package mage.cards.d;
import java.util.Objects;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.*;
import mage.abilities.effects.AsThoughManaEffect;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect;
import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AsThoughEffectType; import mage.constants.*;
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.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.mageobject.CardTypePredicate;
@ -34,8 +22,10 @@ import mage.players.Player;
import mage.target.common.TargetCardInOpponentsGraveyard; import mage.target.common.TargetCardInOpponentsGraveyard;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.Objects;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class DireFleetDaredevil extends CardImpl { public final class DireFleetDaredevil extends CardImpl {
@ -144,10 +134,11 @@ class DireFleetDaredevilSpendAnyManaEffect extends AsThoughEffectImpl implements
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
FixedTarget fixedTarget = ((FixedTarget) getTargetPointer());
return source.isControlledBy(affectedControllerId) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) && Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& game.getState().getZone(objectId) == Zone.STACK; && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
} }
@Override @Override
@ -192,7 +183,7 @@ class DireFleetDaredevilReplacementEffect extends ReplacementEffectImpl {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event; ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getToZone() == Zone.GRAVEYARD return zEvent.getToZone() == Zone.GRAVEYARD
&& event.getTargetId().equals(((FixedTarget) getTargetPointer()).getTarget()) && event.getTargetId().equals(((FixedTarget) getTargetPointer()).getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1
== game.getState().getZoneChangeCounter(event.getTargetId()); == game.getState().getZoneChangeCounter(event.getTargetId());
} }
} }

View file

@ -1,8 +1,5 @@
package mage.cards.g; package mage.cards.g;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
@ -25,8 +22,11 @@ import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class GontiLordOfLuxury extends CardImpl { public final class GontiLordOfLuxury extends CardImpl {
@ -185,22 +185,18 @@ class GontiLordOfLuxurySpendAnyManaEffect extends AsThoughEffectImpl implements
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Card theCard = game.getCard(objectId); Card theCard = game.getCard(objectId);
if(theCard == null){ if (theCard == null) {
return false; return false;
} }
Card mainCard = theCard.getMainCard(); Card mainCard = theCard.getMainCard();
if(mainCard == null){ if (mainCard == null) {
return false; return false;
} }
objectId = mainCard.getId(); // for split cards objectId = mainCard.getId(); // for split cards
if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget())
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { && 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 (effect must applies before and on stack, use isCheckPlayableMode?)
// if the card moved from exile to spell the zone change counter is increased by 1 return affectedControllerId.equals(source.getControllerId());
if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
return true;
}
}
} else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted // object has moved zone so effect can be discarted
this.discard(); this.discard();
@ -238,11 +234,11 @@ class GontiLordOfLuxuryLookEffect extends AsThoughEffectImpl {
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Card theCard = game.getCard(objectId); Card theCard = game.getCard(objectId);
if(theCard == null){ if (theCard == null) {
return false; return false;
} }
Card mainCard = theCard.getMainCard(); Card mainCard = theCard.getMainCard();
if(mainCard == null){ if (mainCard == null) {
return false; return false;
} }
objectId = mainCard.getId(); // for split cards objectId = mainCard.getId(); // for split cards

View file

@ -233,11 +233,12 @@ class GrenzoHavocRaiserSpendAnyManaEffect extends AsThoughEffectImpl implements
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { 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) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) && Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
&& game.getState().getZone(objectId) == Zone.STACK;
} }
@Override @Override

View file

@ -106,7 +106,7 @@ class HostageTakerExileEffect extends OneShotEffect {
ContinuousEffect effect = new HostageTakerSpendAnyManaEffect(); ContinuousEffect effect = new HostageTakerSpendAnyManaEffect();
effect.setTargetPointer(new FixedTarget(card.getId(), game)); effect.setTargetPointer(new FixedTarget(card.getId(), game));
game.addEffect(effect, source); game.addEffect(effect, source);
return false; return true;
} }
} }
@ -173,11 +173,12 @@ class HostageTakerSpendAnyManaEffect extends AsThoughEffectImpl implements AsTho
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); FixedTarget fixedTarget = ((FixedTarget) getTargetPointer());
return source.isControlledBy(affectedControllerId) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, fixedTarget.getTarget()) && Objects.equals(objectId, fixedTarget.getTarget())
&& fixedTarget.getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& game.getState().getZone(objectId) == Zone.STACK; && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
} }
@Override @Override

View file

@ -1,7 +1,5 @@
package mage.cards.o; package mage.cards.o;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
@ -19,8 +17,9 @@ import mage.players.ManaPoolItem;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard; import mage.target.TargetCard;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class OathOfNissa extends CardImpl { public final class OathOfNissa extends CardImpl {
@ -128,13 +127,10 @@ class OathOfNissaSpendAnyManaEffect extends AsThoughEffectImpl implements AsThou
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
if (source.isControlledBy(affectedControllerId)) { if (source.isControlledBy(affectedControllerId)) {
MageObject mageObject = game.getObject(objectId); MageObject mageObject = game.getObject(objectId);
if (mageObject != null) { return mageObject != null && mageObject.isPlaneswalker();
if (mageObject.isPlaneswalker()) {
return true;
}
}
} }
return false; return false;
} }

View file

@ -1,6 +1,5 @@
package mage.cards.p; package mage.cards.p;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughEffectImpl;
@ -10,12 +9,7 @@ import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AsThoughEffectType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterNonlandCard; import mage.filter.common.FilterNonlandCard;
import mage.game.Game; import mage.game.Game;
import mage.players.ManaPoolItem; import mage.players.ManaPoolItem;
@ -25,8 +19,9 @@ import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class PsychicIntrusion extends CardImpl { public final class PsychicIntrusion extends CardImpl {
@ -149,10 +144,9 @@ class PsychicIntrusionCastFromExileEffect extends AsThoughEffectImpl {
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { 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 (objectId.equals(getTargetPointer().getFirst(game, source))) {
if (affectedControllerId.equals(source.getControllerId())) { return affectedControllerId.equals(source.getControllerId());
return true;
}
} else { } else {
if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted // 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 objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget())
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { && 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 (effect must applies before and on stack, use isCheckPlayableMode?)
// if the card moved from exile to spell the zone change counter is increased by 1 return affectedControllerId.equals(source.getControllerId());
if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
return true;
}
}
} else { } else {
if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted // object has moved zone so effect can be discarted

View file

@ -1,7 +1,5 @@
package mage.cards.q; package mage.cards.q;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.ActivatedAbility; import mage.abilities.ActivatedAbility;
@ -10,28 +8,20 @@ import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.AsThoughManaEffect; import mage.abilities.effects.AsThoughManaEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AsThoughEffectType; import mage.constants.*;
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.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.ManaPoolItem; import mage.players.ManaPoolItem;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public final class QuicksilverElemental extends CardImpl { public final class QuicksilverElemental extends CardImpl {
@ -151,10 +141,9 @@ class QuickSilverElementalBlueManaEffect extends AsThoughEffectImpl implements A
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { 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 (objectId.equals(getTargetPointer().getFirst(game, source))) {
if (affectedControllerId.equals(source.getControllerId())) { return affectedControllerId.equals(source.getControllerId());
return true;
}
} }
return false; return false;

View file

@ -194,11 +194,12 @@ class RobberOfTheRichSpendAnyManaEffect extends AsThoughEffectImpl implements As
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); FixedTarget fixedTarget = ((FixedTarget) getTargetPointer());
return source.isControlledBy(affectedControllerId) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, fixedTarget.getTarget()) && Objects.equals(objectId, fixedTarget.getTarget())
&& fixedTarget.getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& game.getState().getZone(objectId) == Zone.STACK; && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
} }
@Override @Override

View file

@ -1,8 +1,5 @@
package mage.cards.s; package mage.cards.s;
import java.util.Objects;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
@ -13,13 +10,7 @@ import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AsThoughEffectType; import mage.constants.*;
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.game.ExileZone; import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
import mage.players.ManaPoolItem; import mage.players.ManaPoolItem;
@ -27,8 +18,10 @@ import mage.players.Player;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.Objects;
import java.util.UUID;
/** /**
*
* @author TheElk801 * @author TheElk801
*/ */
public final class StolenStrategy extends CardImpl { public final class StolenStrategy extends CardImpl {
@ -165,11 +158,11 @@ class StolenStrategySpendAnyManaEffect extends AsThoughEffectImpl implements AsT
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
FixedTarget fixedTarget = ((FixedTarget) getTargetPointer());
return source.isControlledBy(affectedControllerId) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) && Objects.equals(objectId, fixedTarget.getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
&& game.getState().getZone(objectId) == Zone.STACK;
} }
@Override @Override

View file

@ -1,8 +1,5 @@
package mage.cards.t; package mage.cards.t;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
@ -12,18 +9,8 @@ import mage.abilities.effects.AsThoughManaEffect;
import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.cards.Card; import mage.cards.*;
import mage.cards.CardImpl; import mage.constants.*;
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.filter.FilterCard; import mage.filter.FilterCard;
import mage.game.Game; import mage.game.Game;
import mage.players.ManaPoolItem; import mage.players.ManaPoolItem;
@ -32,8 +19,11 @@ import mage.target.TargetCard;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class ThiefOfSanity extends CardImpl { 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 objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget())
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
if (affectedControllerId.equals(authorizedPlayerId)) { // 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?)
// if the card moved from exile to stack the zone change counter is increased by 1 return affectedControllerId.equals(authorizedPlayerId);
if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
return true;
}
}
} else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) { } else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted // object has moved zone so effect can be discarted
this.discard(); this.discard();

View file

@ -1,19 +1,19 @@
package mage.cards.t; package mage.cards.t;
import java.util.Objects;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.*; import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.common.ExileCardsFromTopOfLibraryTargetEffect; 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.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.BountyAbility; import mage.abilities.keyword.BountyAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.*;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.ExileZone; import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
@ -24,15 +24,17 @@ import mage.target.common.TargetOpponentsCreaturePermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.Objects;
import java.util.UUID;
/** /**
*
* @author NinthWorld * @author NinthWorld
*/ */
public final class TobiasBeckett extends CardImpl { public final class TobiasBeckett extends CardImpl {
public TobiasBeckett(UUID ownerId, CardSetInfo setInfo) { public TobiasBeckett(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}");
this.addSuperType(SuperType.LEGENDARY); this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.HUNTER); this.subtype.add(SubType.HUNTER);
@ -75,7 +77,7 @@ class TobiasBeckettEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null) { if (controller != null) {
Permanent bountyTriggered = game.getPermanent(this.getTargetPointer().getFirst(game, source)); Permanent bountyTriggered = game.getPermanent(this.getTargetPointer().getFirst(game, source));
if(bountyTriggered != null) { if (bountyTriggered != null) {
Player opponent = game.getPlayer(bountyTriggered.getControllerId()); Player opponent = game.getPlayer(bountyTriggered.getControllerId());
if (opponent != null) { if (opponent != null) {
MageObject sourceObject = game.getObject(source.getSourceId()); MageObject sourceObject = game.getObject(source.getSourceId());
@ -171,11 +173,11 @@ class TobiasBeckettSpendAnyManaEffect extends AsThoughEffectImpl implements AsTh
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
FixedTarget fixedTarget = ((FixedTarget) getTargetPointer());
return source.isControlledBy(affectedControllerId) return source.isControlledBy(affectedControllerId)
&& Objects.equals(objectId, ((FixedTarget) getTargetPointer()).getTarget()) && Objects.equals(objectId, fixedTarget.getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1
&& (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(objectId)) && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED);
&& game.getState().getZone(objectId) == Zone.STACK;
} }
@Override @Override

View file

@ -73,6 +73,7 @@ class ViviensInvocationEffect extends OneShotEffect {
Zone.LIBRARY, Zone.LIBRARY,
new FilterCreatureCard("creature card to put on the battlefield") new FilterCreatureCard("creature card to put on the battlefield")
); );
target.setNotTarget(true);
if (controller.choose(Outcome.PutCreatureInPlay, cards, target, game)) { if (controller.choose(Outcome.PutCreatureInPlay, cards, target, game)) {
Card card = cards.get(target.getFirstTarget(), game); Card card = cards.get(target.getFirstTarget(), game);
if (card != null) { if (card != null) {

View file

@ -1,4 +1,3 @@
package mage.cards.v; package mage.cards.v;
import mage.MageInt; import mage.MageInt;
@ -83,12 +82,10 @@ class VizierOfTheMenagerieTopCardCastEffect extends AsThoughEffectImpl {
MageObject vizierOfTheMenagerie = game.getObject(source.getSourceId()); MageObject vizierOfTheMenagerie = game.getObject(source.getSourceId());
if (vizierOfTheMenagerie != null if (vizierOfTheMenagerie != null
&& topCard != null) { && topCard != null) {
if (topCard == card return topCard == card
&& topCard.isCreature() && topCard.isCreature()
&& topCard.getSpellAbility() != null && topCard.getSpellAbility() != null
&& topCard.getSpellAbility().spellCanBeActivatedRegularlyNow(controller.getId(), game)) { && topCard.getSpellAbility().spellCanBeActivatedRegularlyNow(controller.getId(), game);
return true;
}
} }
} }
} }
@ -120,10 +117,10 @@ class VizierOfTheMenagerieManaEffect extends AsThoughEffectImpl implements AsTho
@Override @Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
if (source.isControlledBy(affectedControllerId)) { if (source.isControlledBy(affectedControllerId)) {
MageObject mageObject = game.getObject(objectId); MageObject mageObject = game.getObject(objectId);
return mageObject != null return mageObject != null && mageObject.isCreature();
&& mageObject.isCreature();
} }
return false; return false;
} }

View file

@ -1,4 +1,3 @@
package org.mage.test.AI.basic; package org.mage.test.AI.basic;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -8,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI; import org.mage.test.serverside.base.CardTestPlayerBaseAI;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class CastCreaturesTest extends CardTestPlayerBaseAI { 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. * Tests that the creature is cast if enough mana is available.
* * <p>
* Once Ammit Eternal is cast against a computer AI opponent, the AI just * 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 * 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 * 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); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ammit Eternal"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ammit Eternal");
setStrictChooseMode(true);
setStopAt(3, PhaseStep.END_TURN); setStopAt(3, PhaseStep.END_TURN);
execute(); execute();
assertAllCommandsUsed();
assertPermanentCount(playerB, "Ammit Eternal", 1); assertPermanentCount(playerB, "Ammit Eternal", 1);

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.abilities.activated; package org.mage.test.cards.abilities.activated;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class LightningStormTest extends CardTestPlayerBase { 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 * 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 * 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. * hand which isn't something I've had an issue with before.
* * <p>
* My opponent had a Leyline of Sanctity in play, so perhaps that was * 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? * causing the issue somehow? Does anyone want to try and replicate it?
*
*/ */
@Test @Test
public void ActivateByBothPlayersTest() { public void ActivateByBothPlayersTest() {
@ -31,22 +28,32 @@ public class LightningStormTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Mountain"); addCard(Zone.HAND, playerA, "Mountain");
addCard(Zone.HAND, playerB, "Mountain"); addCard(Zone.HAND, playerB, "Mountain");
// A activate
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Storm", playerB); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Storm", playerB);
// B discard and re-target
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Discard"); 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"); 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); setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute(); execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Lightning Storm", 1); assertGraveyardCount(playerA, "Lightning Storm", 1);
assertGraveyardCount(playerB, "Mountain", 1); assertGraveyardCount(playerB, "Mountain", 1);
assertGraveyardCount(playerA, "Mountain", 1); assertGraveyardCount(playerA, "Mountain", 1);
assertLife(playerA, 20); assertLife(playerA, 20);
assertLife(playerB, 13); assertLife(playerB, 20 - 3 - 2 - 2);
} }
} }

View file

@ -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);
}
}

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.asthough; package org.mage.test.cards.asthough;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class SpendOtherManaTest extends CardTestPlayerBase { 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, * Tron mana doesn't work with Oath of Nissa. (e.g. can't cast Chandra,
* Flamecaller with Urza's Tower, Power Plant, and Mine.) * Flamecaller with Urza's Tower, Power Plant, and Mine.)
* * <p>
* AI don't get the Planeswalker as playable card (probably because of the * AI don't get the Planeswalker as playable card (probably because of the
* as thought effect) * 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 * 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 * of Nissa in play. Pretty sure Oath is working usually, so here were the
* conditions in my game: * conditions in my game:
* * <p>
* -Cast Dark Petition with spell mastery -Attempt to cast Nissa, Voice of * -Cast Dark Petition with spell mastery -Attempt to cast Nissa, Voice of
* Zendikar using the triple black mana from Dark Petition * 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} addCard(Zone.HAND, playerA, "Hostage Taker"); // {2}{U}{B}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hostage Taker"); 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
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 castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); // cast it from exile with red mana from pool
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN); setStopAt(1, PhaseStep.END_TURN);
execute(); execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Hostage Taker", 1); assertPermanentCount(playerA, "Hostage Taker", 1);
assertTappedCount("Mountain", true, 4); assertTappedCount("Mountain", true, 4);
assertPermanentCount(playerA, "Silvercoat Lion", 1); 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 Elementals 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 Elementals 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 owners 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);
}
} }

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.continuous; package org.mage.test.cards.continuous;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* @author LevelX2 * @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 // 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, // 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. // 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, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, "Island", 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); 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 // cast from exile with any mana
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Elspeth, Sun's Champion"); castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Elspeth, Sun's Champion");
setStrictChooseMode(true);
setStopAt(3, PhaseStep.BEGIN_COMBAT); setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute(); execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Psychic Intrusion", 1); assertGraveyardCount(playerA, "Psychic Intrusion", 1);
assertHandCount(playerB, "Elspeth, Sun's Champion", 0); assertHandCount(playerB, "Elspeth, Sun's Champion", 0);

View file

@ -5,7 +5,6 @@ import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
@ -282,7 +281,8 @@ public class AdventureCardsTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Curious Pair"); addCard(Zone.HAND, playerB, "Curious Pair");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Intrusion", playerB); 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, "Treats to Share");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
@ -462,6 +462,7 @@ public class AdventureCardsTest extends CardTestPlayerBase {
removeAllCardsFromLibrary(playerA); removeAllCardsFromLibrary(playerA);
addCard(Zone.LIBRARY, playerA, "Curious Pair"); addCard(Zone.LIBRARY, playerA, "Curious Pair");
showAvaileableAbilities("abils", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
@ -473,7 +474,7 @@ public class AdventureCardsTest extends CardTestPlayerBase {
} }
@Test @Test
@Ignore("Not yet working correctly.") //@Ignore("Not yet working correctly.")
public void testCastTreatsToShareWithWrennAndSixEmblem() { public void testCastTreatsToShareWithWrennAndSixEmblem() {
/* /*
* Wrenn and Six {R}{G} * Wrenn and Six {R}{G}
@ -487,11 +488,16 @@ public class AdventureCardsTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest");
addCard(Zone.BATTLEFIELD, playerA, "Wrenn and Six"); addCard(Zone.BATTLEFIELD, playerA, "Wrenn and Six");
addCard(Zone.GRAVEYARD, playerA, "Curious Pair"); 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); addCounters(1, PhaseStep.UPKEEP, playerA, "Wrenn and Six", CounterType.LOYALTY, 5);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-7: You get an emblem"); 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"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
setChoice(playerA, "Forest");
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();
@ -523,6 +529,8 @@ public class AdventureCardsTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Curious Pair"); addCard(Zone.HAND, playerA, "Curious Pair");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Until your next"); 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"); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Treats to Share");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);

View file

@ -32,7 +32,6 @@ public class BolassCitadelTest extends CardTestPlayerBase {
} }
@Test @Test
@Ignore("This is broken for now.")
public void testCastAdventure() { public void testCastAdventure() {
/* /*
* Curious Pair {1}{G} * Curious Pair {1}{G}
@ -44,7 +43,6 @@ public class BolassCitadelTest extends CardTestPlayerBase {
* Create a Food token. * Create a Food token.
*/ */
setStrictChooseMode(true); setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Forest");
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel"); addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel");
removeAllCardsFromLibrary(playerA); removeAllCardsFromLibrary(playerA);
addCard(Zone.LIBRARY, playerA, "Curious Pair"); addCard(Zone.LIBRARY, playerA, "Curious Pair");
@ -54,7 +52,6 @@ public class BolassCitadelTest extends CardTestPlayerBase {
execute(); execute();
assertAllCommandsUsed(); assertAllCommandsUsed();
assertTapped("Forest", false);
assertHandCount(playerA, 0); assertHandCount(playerA, 0);
assertPermanentCount(playerA, "Food", 1); assertPermanentCount(playerA, "Food", 1);
assertExileCount(playerA, "Curious Pair", 1); assertExileCount(playerA, "Curious Pair", 1);

View file

@ -6,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* also tests regenerate and tests that permanents with protection can be * also tests regenerate and tests that permanents with protection can be
* sacrificed * sacrificed
* *
@ -46,10 +45,13 @@ public class FiendOfTheShadowsTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Swamp"); addCard(Zone.HAND, playerB, "Swamp");
attack(1, playerA, "Fiend of the Shadows"); attack(1, playerA, "Fiend of the Shadows");
addTarget(playerB, "Swamp");
playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Swamp"); playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Swamp");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN); setStopAt(1, PhaseStep.END_TURN);
execute(); execute();
assertAllCommandsUsed();
assertLife(playerA, 20); assertLife(playerA, 20);
assertLife(playerB, 17); assertLife(playerB, 17);

View file

@ -2374,20 +2374,25 @@ public class TestPlayer implements Player {
} }
@Override @Override
public UUID getCastSourceIdWithAlternateMana() { public Set<UUID> getCastSourceIdWithAlternateMana() {
return computerPlayer.getCastSourceIdWithAlternateMana(); return computerPlayer.getCastSourceIdWithAlternateMana();
} }
@Override @Override
public ManaCosts getCastSourceIdManaCosts() { public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() {
return computerPlayer.getCastSourceIdManaCosts(); return computerPlayer.getCastSourceIdManaCosts();
} }
@Override @Override
public Costs<Cost> getCastSourceIdCosts() { public Map<UUID, Costs<Cost>> getCastSourceIdCosts() {
return computerPlayer.getCastSourceIdCosts(); return computerPlayer.getCastSourceIdCosts();
} }
@Override
public void clearCastSourceIdManaCosts() {
computerPlayer.clearCastSourceIdManaCosts();
}
@Override @Override
public boolean isInPayManaMode() { public boolean isInPayManaMode() {
return computerPlayer.isInPayManaMode(); return computerPlayer.isInPayManaMode();

View file

@ -19,8 +19,8 @@ import mage.counters.Counter;
import mage.counters.Counters; import mage.counters.Counters;
import mage.designations.Designation; import mage.designations.Designation;
import mage.designations.DesignationType; import mage.designations.DesignationType;
import mage.filter.FilterPermanent;
import mage.filter.FilterMana; import mage.filter.FilterMana;
import mage.filter.FilterPermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.Graveyard; import mage.game.Graveyard;
import mage.game.Table; import mage.game.Table;
@ -1208,7 +1208,7 @@ public class PlayerStub implements Player {
} }
@Override @Override
public UUID getCastSourceIdWithAlternateMana() { public Set<UUID> getCastSourceIdWithAlternateMana() {
return null; return null;
} }
@ -1218,15 +1218,20 @@ public class PlayerStub implements Player {
} }
@Override @Override
public ManaCosts getCastSourceIdManaCosts() { public Map<UUID, Costs<Cost>> getCastSourceIdCosts() {
return null; return null;
} }
@Override @Override
public Costs<Cost> getCastSourceIdCosts() { public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() {
return null; return null;
} }
@Override
public void clearCastSourceIdManaCosts() {
}
@Override @Override
public void addPermissionToShowHandCards(UUID watcherUserId) { public void addPermissionToShowHandCards(UUID watcherUserId) {
@ -1374,19 +1379,19 @@ public class PlayerStub implements Player {
@Override @Override
public void addPhyrexianToColors(FilterMana colors) { public void addPhyrexianToColors(FilterMana colors) {
} }
@Override @Override
public void removePhyrexianFromColors(FilterMana colors) { public void removePhyrexianFromColors(FilterMana colors) {
} }
@Override @Override
public FilterMana getPhyrexianColors() { public FilterMana getPhyrexianColors() {
return (new FilterMana()); return (new FilterMana());
} }
@Override @Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
return card.getSpellAbility(); return card.getSpellAbility();

View file

@ -63,7 +63,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
*/ */
public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) { public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) {
MageObject object = game.getObject(sourceId); 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 (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 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) { if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
Player player = game.getPlayer(playerId); Player player = game.getPlayer(playerId);
if (player != null if (player != null
&& getSourceId().equals(player.getCastSourceIdWithAlternateMana())) { && player.getCastSourceIdWithAlternateMana().contains(getSourceId())) {
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();
} }
} }

View file

@ -1,6 +1,5 @@
package mage.abilities.effects; package mage.abilities.effects;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.constants.AsThoughEffectType; import mage.constants.AsThoughEffectType;
import mage.constants.Duration; import mage.constants.Duration;
@ -8,8 +7,9 @@ import mage.constants.EffectType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.game.Game; import mage.game.Game;
import java.util.UUID;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect { public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect {
@ -29,10 +29,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
@Override @Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { 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)) { if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) {
return applies(objectId, source, playerId, game); return applies(objectId, source, playerId, game);
} else { } else {
return applies(objectId, source, affectedAbility.getControllerId(), game); return applies(objectId, source, playerId, game);
} }
} }

View file

@ -505,12 +505,19 @@ public class ContinuousEffects implements Serializable {
UUID idToCheck; UUID idToCheck;
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) { if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId(); 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 { } else {
Card card = game.getCard(objectId); Card card = game.getCard(objectId);
if (card != null && card instanceof SplitCardHalf) { if (card instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) card).getParentCard().getId(); idToCheck = ((SplitCardHalf) card).getParentCard().getId();
} else if (card != null && type == AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE } else if (card instanceof AdventureCardSpell
&& 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(); idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
} else { } else {
idToCheck = objectId; idToCheck = objectId;

View file

@ -51,7 +51,7 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing
Spell spell = game.getStack().getSpell(source.getId()); Spell spell = game.getStack().getSpell(source.getId());
if (spell != null && !spell.isCopy()) { if (spell != null && !spell.isCopy()) {
Card spellCard = spell.getCard(); Card spellCard = spell.getCard();
if (spellCard != null && spellCard instanceof AdventureCardSpell) { if (spellCard instanceof AdventureCardSpell) {
UUID exileId = adventureExileId(controller.getId(), game); UUID exileId = adventureExileId(controller.getId(), game);
game.getExile().createZone(exileId, "On an Adventure"); game.getExile().createZone(exileId, "On an Adventure");
AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard; AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard;

View file

@ -1,11 +1,6 @@
package mage.abilities.effects.common.continuous; package mage.abilities.effects.common.continuous;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.AsThoughEffectType; import mage.constants.AsThoughEffectType;
@ -15,6 +10,8 @@ import mage.filter.FilterCard;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import java.util.UUID;
/** /**
* @author nantuko * @author nantuko
*/ */
@ -55,26 +52,18 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
@Override @Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
Card cardOnTop = game.getCard(objectId); Card cardToCheck = game.getCard(objectId);
Card cardToCheckProperties = cardOnTop; 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 (cardToCheck != null
if (affectedAbility != null) {
MageObject sourceObject = affectedAbility.getSourceObject(game);
if (sourceObject != null && sourceObject instanceof Card) {
cardToCheckProperties = (Card) sourceObject;
}
}
if (cardOnTop != null
&& playerId.equals(source.getControllerId()) && playerId.equals(source.getControllerId())
&& cardOnTop.isOwnedBy(source.getControllerId()) && cardToCheck.isOwnedBy(source.getControllerId())
&& (!cardToCheckProperties.getManaCost().isEmpty() || cardToCheckProperties.isLand()) && (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
&& filter.match(cardToCheckProperties, game)) { && filter.match(cardToCheck, game)) {
Player player = game.getPlayer(cardOnTop.getOwnerId()); Player player = game.getPlayer(cardToCheck.getOwnerId());
if (player != null && cardOnTop.equals(player.getLibrary().getFromTop(game))) {
return true; UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId();
} return objectId.equals(needCardID);
} }
return false; return false;
} }

View file

@ -100,6 +100,7 @@ class HideawayExileEffect extends OneShotEffect {
cards.addAll(controller.getLibrary().getTopCards(game, 4)); cards.addAll(controller.getLibrary().getTopCards(game, 4));
if (!cards.isEmpty()) { if (!cards.isEmpty()) {
TargetCard target1 = new TargetCard(Zone.LIBRARY, filter1); TargetCard target1 = new TargetCard(Zone.LIBRARY, filter1);
target1.setNotTarget(true);
if (controller.choose(Outcome.Detriment, cards, target1, game)) { if (controller.choose(Outcome.Detriment, cards, target1, game)) {
Card card = cards.get(target1.getFirstTarget(), game); Card card = cards.get(target1.getFirstTarget(), game);
if (card != null) { if (card != null) {

View file

@ -1,16 +1,10 @@
package mage.abilities.keyword; package mage.abilities.keyword;
import java.util.Iterator;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility; import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.Cost; import mage.abilities.costs.*;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
@ -23,8 +17,9 @@ import mage.game.stack.Spell;
import mage.game.stack.StackObject; import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import java.util.Iterator;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class ReplicateAbility extends StaticAbility implements OptionalAdditionalSourceCosts { public class ReplicateAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
@ -91,12 +86,12 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
String times = ""; String times = "";
if (additionalCost.isRepeatable()) { if (additionalCost.isRepeatable()) {
int numActivations = additionalCost.getActivateCount(); 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) if (additionalCost.canPay(ability, sourceId, controllerId, game)
&& player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(times).append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) { && player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(times).append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
additionalCost.activate(); additionalCost.activate();
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) { for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next(); Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) { if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
@ -170,7 +165,7 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl {
if (card != null) { if (card != null) {
for (Ability ability : card.getAbilities(game)) { for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) { if (ability instanceof ReplicateAbility) {
if (((ReplicateAbility) ability).isActivated()) { if (ability.isActivated()) {
for (Effect effect : this.getEffects()) { for (Effect effect : this.getEffects()) {
effect.setValue("ReplicateSpell", spell); effect.setValue("ReplicateSpell", spell);
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount()); effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
@ -213,7 +208,7 @@ class ReplicateCopyEffect extends OneShotEffect {
if (card != null) { if (card != null) {
for (Ability ability : card.getAbilities(game)) { for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) { if (ability instanceof ReplicateAbility) {
if (((ReplicateAbility) ability).isActivated()) { if (ability.isActivated()) {
((ReplicateAbility) ability).resetReplicate(); ((ReplicateAbility) ability).resetReplicate();
} }
} }

View file

@ -4,7 +4,8 @@ import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl; import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.constants.*; import mage.constants.CardType;
import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import java.util.List; import java.util.List;
@ -26,7 +27,7 @@ public abstract class AdventureCard extends CardImpl {
public AdventureCard(AdventureCard card) { public AdventureCard(AdventureCard card) {
super(card); super(card);
this.spellCard = card.getSpellCard().copy(); this.spellCard = card.getSpellCard().copy();
((AdventureCardSpell)this.spellCard).setParentCard(this); ((AdventureCardSpell) this.spellCard).setParentCard(this);
} }
public Card getSpellCard() { public Card getSpellCard() {
@ -91,6 +92,11 @@ public abstract class AdventureCard extends CardImpl {
return allAbilities; return allAbilities;
} }
public Abilities<Ability> getSharedAbilities() {
// abilities without spellcard
return super.getAbilities();
}
@Override @Override
public void setOwnerId(UUID ownerId) { public void setOwnerId(UUID ownerId) {
super.setOwnerId(ownerId); super.setOwnerId(ownerId);

View file

@ -19,7 +19,6 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
*
* @author phulin * @author phulin
*/ */
public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell { public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell {
@ -112,7 +111,7 @@ class AdventureCardSpellAbility extends SpellAbility {
public ActivationStatus canActivate(UUID playerId, Game game) { public ActivationStatus canActivate(UUID playerId, Game game) {
ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game)); ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game));
Card spellCard = game.getCard(this.getSourceId()); Card spellCard = game.getCard(this.getSourceId());
if (spellCard != null && spellCard instanceof AdventureCardSpell) { if (spellCard instanceof AdventureCardSpell) {
Card card = ((AdventureCardSpell) spellCard).getParentCard(); Card card = ((AdventureCardSpell) spellCard).getParentCard();
if (adventureExileZone != null && adventureExileZone.contains(card.getId())) { if (adventureExileZone != null && adventureExileZone.contains(card.getId())) {
return ActivationStatus.getFalse(); return ActivationStatus.getFalse();

View file

@ -1,7 +1,6 @@
package mage.constants; package mage.constants;
/** /**
*
* @author North * @author North
*/ */
public enum AsThoughEffectType { public enum AsThoughEffectType {
@ -19,15 +18,29 @@ public enum AsThoughEffectType {
BLOCK_FORESTWALK, BLOCK_FORESTWALK,
DAMAGE_NOT_BLOCKED, DAMAGE_NOT_BLOCKED,
BE_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, CAST_AS_INSTANT,
ACTIVATE_AS_INSTANT, ACTIVATE_AS_INSTANT,
DAMAGE, DAMAGE,
SHROUD, SHROUD,
HEXPROOF, HEXPROOF,
PAY_0_ECHO, PAY_0_ECHO,
LOOK_AT_FACE_DOWN, 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_OTHER_MANA,
SPEND_ONLY_MANA, SPEND_ONLY_MANA,
TARGET TARGET
} }

View file

@ -20,8 +20,8 @@ import mage.counters.Counter;
import mage.counters.Counters; import mage.counters.Counters;
import mage.designations.Designation; import mage.designations.Designation;
import mage.designations.DesignationType; import mage.designations.DesignationType;
import mage.filter.FilterPermanent;
import mage.filter.FilterMana; import mage.filter.FilterMana;
import mage.filter.FilterPermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.Graveyard; import mage.game.Graveyard;
import mage.game.Table; import mage.game.Table;
@ -75,7 +75,7 @@ public interface Player extends MageItem, Copyable<Player> {
void setLife(int life, Game game, UUID sourceId); void setLife(int life, Game game, UUID sourceId);
/** /**
* @param amount amount of life loss * @param amount amount of life loss
* @param game * @param game
* @param atCombat was the source combat damage * @param atCombat was the source combat damage
* @return * @return
@ -351,7 +351,7 @@ public interface Player extends MageItem, Copyable<Player> {
* @param source * @param source
* @param game * @param game
* @param targetPlayerId player whose library will be searched * @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 * @return true if search was successful
*/ */
boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents); boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents);
@ -372,23 +372,23 @@ public interface Player extends MageItem, Copyable<Player> {
/** /**
* Plays a card if possible * Plays a card if possible
* *
* @param card the card that can be cast * @param card the card that can be cast
* @param game * @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 * @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 * no sorcery or play land timing restriction are checked. For a land it has
* to be the turn of the player playing that card. * to be the turn of the player playing that card.
* @param reference mage object that allows to play the card * @param reference mage object that allows to play the card
* @return * @return
*/ */
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference); 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 game
* @param ignoreTiming false - it won't be checked if the stack is empty and * @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 * 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. * play a land concerning the number of lands you already played.
* @return * @return
*/ */
boolean playLand(Card card, Game game, boolean ignoreTiming); boolean playLand(Card card, Game game, boolean ignoreTiming);
@ -534,11 +534,11 @@ public interface Player extends MageItem, Copyable<Player> {
/** /**
* Moves the cards from cards to the bottom of the players library. * Moves the cards from cards to the bottom of the players library.
* *
* @param cards - list of cards that have to be moved * @param cards - list of cards that have to be moved
* @param game - game * @param game - game
* @param anyOrder - true if player can determine the order of the cards * @param anyOrder - true if player can determine the order of the cards
* else random order * else random order
* @param source - source ability * @param source - source ability
* @return * @return
*/ */
boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@ -559,10 +559,10 @@ public interface Player extends MageItem, Copyable<Player> {
/** /**
* Moves the cards from cards to the top of players library. * Moves the cards from cards to the top of players library.
* *
* @param cards - list of cards that have to be moved * @param cards - list of cards that have to be moved
* @param game - game * @param game - game
* @param anyOrder - true if player can determine the order of the cards * @param anyOrder - true if player can determine the order of the cards
* @param source - source ability * @param source - source ability
* @return * @return
*/ */
boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder); boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@ -593,8 +593,8 @@ public interface Player extends MageItem, Copyable<Player> {
/** /**
* Choose the order in which blockers get damage assigned to * Choose the order in which blockers get damage assigned to
* *
* @param blockers list of blockers where to choose the next one from * @param blockers list of blockers where to choose the next one from
* @param combatGroup the concerning combat group * @param combatGroup the concerning combat group
* @param blockerOrder the already set order of blockers * @param blockerOrder the already set order of blockers
* @param game * @param game
* @return blocker next to add to the blocker order * @return blocker next to add to the blocker order
@ -664,7 +664,7 @@ public interface Player extends MageItem, Copyable<Player> {
* @param card * @param card
* @param game * @param game
* @param abilitiesToActivate extra info about abilities that can be * @param abilitiesToActivate extra info about abilities that can be
* activated on NO option * activated on NO option
* @return player looked at the card * @return player looked at the card
*/ */
boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate); boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate);
@ -737,11 +737,11 @@ public interface Player extends MageItem, Copyable<Player> {
* @param toZone * @param toZone
* @param source * @param source
* @param game * @param game
* @param tapped the cards are tapped on the battlefield * @param tapped the cards are tapped on the battlefield
* @param faceDown the cards are face down in the to zone * @param faceDown the cards are face down in the to zone
* @param byOwner the card is moved (or put onto battlefield) by the owner * @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 * of the card and if target zone is battlefield controls the permanent
* (instead of the controller of the source) * (instead of the controller of the source)
* @param appliedEffects * @param appliedEffects
* @return * @return
*/ */
@ -777,7 +777,7 @@ public interface Player extends MageItem, Copyable<Player> {
* list of applied effects is not saved * list of applied effects is not saved
* *
* @param card * @param card
* @param exileId exile zone id (optional) * @param exileId exile zone id (optional)
* @param exileName name of exile zone (optional) * @param exileName name of exile zone (optional)
* @param sourceId * @param sourceId
* @param game * @param game
@ -819,7 +819,7 @@ public interface Player extends MageItem, Copyable<Player> {
* @param sourceId * @param sourceId
* @param game * @param game
* @param fromZone if null, this info isn't postet * @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 * @param withName show the card name in the log
* @return * @return
*/ */
@ -844,18 +844,20 @@ public interface Player extends MageItem, Copyable<Player> {
* without mana (null) or the mana set to manaCosts instead of its normal * without mana (null) or the mana set to manaCosts instead of its normal
* mana costs. * 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 * @param manaCosts alternate ManaCost, null if it can be cast without mana
* cost * cost
* @param costs alternate other costs you need to pay * @param costs alternate other costs you need to pay
*/ */
void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs); void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs);
UUID getCastSourceIdWithAlternateMana(); Set<UUID> getCastSourceIdWithAlternateMana();
ManaCosts<ManaCost> getCastSourceIdManaCosts(); Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts();
Costs<Cost> getCastSourceIdCosts(); Map<UUID, Costs<Cost>> getCastSourceIdCosts();
void clearCastSourceIdManaCosts();
// permission handling to show hand cards // permission handling to show hand cards
void addPermissionToShowHandCards(UUID watcherUserId); void addPermissionToShowHandCards(UUID watcherUserId);

View file

@ -32,8 +32,8 @@ import mage.counters.Counters;
import mage.designations.Designation; import mage.designations.Designation;
import mage.designations.DesignationType; import mage.designations.DesignationType;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.FilterPermanent;
import mage.filter.FilterMana; import mage.filter.FilterMana;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterCreatureForCombat;
import mage.filter.common.FilterCreatureForCombatBlock; import mage.filter.common.FilterCreatureForCombatBlock;
@ -160,9 +160,10 @@ public abstract class PlayerImpl implements Player, Serializable {
protected boolean reachedNextTurnAfterLeaving = false; 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) // 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; // support multiple cards with alternative mana cost
protected ManaCosts<ManaCost> castSourceIdManaCosts; protected Set<UUID> castSourceIdWithAlternateMana = new HashSet<>();
protected Costs<Cost> castSourceIdCosts; protected Map<UUID, ManaCosts<ManaCost>> castSourceIdManaCosts = new HashMap<>();
protected Map<UUID, Costs<Cost>> castSourceIdCosts = new HashMap<>();
// indicates that the player is in mana payment phase // indicates that the player is in mana payment phase
protected boolean payManaMode = false; protected boolean payManaMode = false;
@ -271,9 +272,10 @@ public abstract class PlayerImpl implements Player, Serializable {
this.priorityTimeLeft = player.getPriorityTimeLeft(); this.priorityTimeLeft = player.getPriorityTimeLeft();
this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving; this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving;
this.castSourceIdWithAlternateMana = player.castSourceIdWithAlternateMana; this.castSourceIdWithAlternateMana.addAll(player.castSourceIdWithAlternateMana);
this.castSourceIdManaCosts = player.castSourceIdManaCosts; this.castSourceIdManaCosts.putAll(player.castSourceIdManaCosts);
this.castSourceIdCosts = player.castSourceIdCosts; this.castSourceIdCosts.putAll(player.castSourceIdCosts);
this.payManaMode = player.payManaMode; this.payManaMode = player.payManaMode;
this.phyrexianColors = player.phyrexianColors.copy(); this.phyrexianColors = player.phyrexianColors.copy();
@ -340,9 +342,12 @@ public abstract class PlayerImpl implements Player, Serializable {
this.turnControllers.clear(); this.turnControllers.clear();
this.turnControllers.addAll(player.getTurnControllers()); this.turnControllers.addAll(player.getTurnControllers());
this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving(); this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving();
this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana();
this.castSourceIdManaCosts = player.getCastSourceIdManaCosts(); this.clearCastSourceIdManaCosts();
this.castSourceIdCosts = player.getCastSourceIdCosts(); this.castSourceIdWithAlternateMana.addAll(player.getCastSourceIdWithAlternateMana());
this.castSourceIdManaCosts.putAll(player.getCastSourceIdManaCosts());
this.castSourceIdCosts.putAll(player.getCastSourceIdCosts());
this.phyrexianColors = player.getPhyrexianColors().copy(); this.phyrexianColors = player.getPhyrexianColors().copy();
this.designations.clear(); this.designations.clear();
@ -417,9 +422,8 @@ public abstract class PlayerImpl implements Player, Serializable {
this.setLife(game.getLife(), game, (UUID) null); this.setLife(game.getLife(), game, (UUID) null);
this.setReachedNextTurnAfterLeaving(false); this.setReachedNextTurnAfterLeaving(false);
this.castSourceIdWithAlternateMana = null; this.clearCastSourceIdManaCosts();
this.castSourceIdManaCosts = null;
this.castSourceIdCosts = null;
this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left
this.phyrexianColors = new FilterMana(); this.phyrexianColors = new FilterMana();
@ -444,9 +448,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.canPlayCardsFromGraveyard = false; this.canPlayCardsFromGraveyard = false;
this.topCardRevealed = false; this.topCardRevealed = false;
this.alternativeSourceCosts.clear(); this.alternativeSourceCosts.clear();
this.castSourceIdWithAlternateMana = null; this.clearCastSourceIdManaCosts();
this.castSourceIdManaCosts = null;
this.castSourceIdCosts = null;
this.getManaPool().clearEmptyManaPoolRules(); this.getManaPool().clearEmptyManaPoolRules();
this.phyrexianColors = new FilterMana(); this.phyrexianColors = new FilterMana();
} }
@ -617,7 +619,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (abilities.containsKey(HexproofAbility.getInstance().getId())) { if (abilities.containsKey(HexproofAbility.getInstance().getId())) {
if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), && null == game.getContinuousEffects().asThough(this.getId(),
AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) { AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) {
return false; return false;
} }
} }
@ -625,7 +627,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (abilities.containsKey(HexproofFromWhiteAbility.getInstance().getId())) { if (abilities.containsKey(HexproofFromWhiteAbility.getInstance().getId())) {
if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), && null == game.getContinuousEffects().asThough(this.getId(),
AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& source.getColor(game).isWhite()) { && source.getColor(game).isWhite()) {
return false; return false;
} }
@ -634,7 +636,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (abilities.containsKey(HexproofFromBlueAbility.getInstance().getId())) { if (abilities.containsKey(HexproofFromBlueAbility.getInstance().getId())) {
if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), && null == game.getContinuousEffects().asThough(this.getId(),
AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& source.getColor(game).isBlue()) { && source.getColor(game).isBlue()) {
return false; return false;
} }
@ -643,7 +645,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (abilities.containsKey(HexproofFromBlackAbility.getInstance().getId())) { if (abilities.containsKey(HexproofFromBlackAbility.getInstance().getId())) {
if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), && null == game.getContinuousEffects().asThough(this.getId(),
AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& source.getColor(game).isBlack()) { && source.getColor(game).isBlack()) {
return false; return false;
} }
@ -652,7 +654,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (abilities.containsKey(HexproofFromMonocoloredAbility.getInstance().getId())) { if (abilities.containsKey(HexproofFromMonocoloredAbility.getInstance().getId())) {
if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game) if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), && null == game.getContinuousEffects().asThough(this.getId(),
AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& !source.getColor(game).isColorless() && !source.getColor(game).isColorless()
&& !source.getColor(game).isMulticolored()) { && !source.getColor(game).isMulticolored()) {
return false; return false;
@ -695,7 +697,7 @@ public abstract class PlayerImpl implements Player, Serializable {
game.informPlayers(getLogName() + " discards down to " game.informPlayers(getLogName() + " discards down to "
+ this.maxHandSize + this.maxHandSize
+ (this.maxHandSize == 1 + (this.maxHandSize == 1
? " hand card" : " hand cards")); ? " hand card" : " hand cards"));
} }
discard(hand.size() - this.maxHandSize, null, game); discard(hand.size() - this.maxHandSize, null, game);
} }
@ -813,7 +815,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (card != null) { if (card != null) {
GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD, GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD,
card.getId(), source == null card.getId(), source == null
? null : source.getSourceId(), playerId); ? null : source.getSourceId(), playerId);
gameEvent.setFlag(source != null); // event from effect or from cost (source == null) gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
if (!game.replaceEvent(gameEvent, source)) { 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 // 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! // So discard is also successful if card is moved to another zone by replacement effect!
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD, game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DISCARDED_CARD,
card.getId(), source == null card.getId(), source == null
? null : source.getSourceId(), playerId)); ? null : source.getSourceId(), playerId));
return true; return true;
} }
} }
@ -1061,26 +1063,33 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) { public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) {
castSourceIdWithAlternateMana = sourceId; castSourceIdWithAlternateMana.add(sourceId);
castSourceIdManaCosts = manaCosts; castSourceIdManaCosts.put(sourceId, manaCosts);
castSourceIdCosts = costs; castSourceIdCosts.put(sourceId, costs);
} }
@Override @Override
public UUID getCastSourceIdWithAlternateMana() { public Set<UUID> getCastSourceIdWithAlternateMana() {
return castSourceIdWithAlternateMana; return castSourceIdWithAlternateMana;
} }
@Override @Override
public Costs<Cost> getCastSourceIdCosts() { public Map<UUID, Costs<Cost>> getCastSourceIdCosts() {
return castSourceIdCosts; return castSourceIdCosts;
} }
@Override @Override
public ManaCosts getCastSourceIdManaCosts() { public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() {
return castSourceIdManaCosts; return castSourceIdManaCosts;
} }
@Override
public void clearCastSourceIdManaCosts() {
this.castSourceIdCosts.clear();
this.castSourceIdManaCosts.clear();
this.castSourceIdWithAlternateMana.clear();
}
@Override @Override
public void setPayManaMode(boolean payManaMode) { public void setPayManaMode(boolean payManaMode) {
this.payManaMode = payManaMode; this.payManaMode = payManaMode;
@ -1144,11 +1153,13 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
// Update the zcc to the stack // Update the zcc to the stack
ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId())); 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 // 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(); Ability spellAbility = spell.getSpellAbility();
ManaCosts alternateCosts = getCastSourceIdManaCosts(); ManaCosts alternateCosts = getCastSourceIdManaCosts().get(ability.getSourceId());
Costs<Cost> costs = getCastSourceIdCosts(); Costs<Cost> costs = getCastSourceIdCosts().get(ability.getSourceId());
if (alternateCosts == null) { if (alternateCosts == null) {
noMana = true; noMana = true;
} else { } else {
@ -1162,7 +1173,8 @@ public abstract class PlayerImpl implements Player, Serializable {
spellAbility.getCosts().addAll(costs); 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, GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL,
spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject); spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject);
game.fireEvent(event); 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 // Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities
// as candidates. // as candidates.
private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities<Ability> candidateAbilites, private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities<Ability> candidateAbilites,
LinkedHashMap<UUID, ActivatedAbility> output) { LinkedHashMap<UUID, ActivatedAbility> output) {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
ManaOptions availableMana = null; 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 // 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 != null
// if anyone sees an issue with this code, please report it. Worked in my testing. // if anyone sees an issue with this code, please report it. Worked in my testing.
|| game.getContinuousEffects().asThough(object.getId(), || game.getContinuousEffects().asThough(object.getId(),
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE,
ability, ability,
this.getId(), this.getId(),
game) game)
!= null) { != null) {
if (canUse if (canUse
|| ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
@ -1913,9 +1925,9 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
private List<Permanent> getPermanentsThatCanBeUntapped(Game game, private List<Permanent> getPermanentsThatCanBeUntapped(Game game,
List<Permanent> canBeUntapped, List<Permanent> canBeUntapped,
RestrictionUntapNotMoreThanEffect handledEffect, RestrictionUntapNotMoreThanEffect handledEffect,
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) { Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
List<Permanent> leftForUntap = new ArrayList<>(); List<Permanent> leftForUntap = new ArrayList<>();
// select permanents that can still be untapped // select permanents that can still be untapped
for (Permanent permanent : canBeUntapped) { for (Permanent permanent : canBeUntapped) {
@ -2623,7 +2635,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
boolean triggerEvents) { boolean triggerEvents) {
//20091005 - 701.14c //20091005 - 701.14c
Library searchedLibrary = null; Library searchedLibrary = null;
String searchInfo = null; String searchInfo = null;
@ -2827,7 +2839,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/** /**
* @param game * @param game
* @param appliedEffects * @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 * @return the number that the player rolled
*/ */
@Override @Override
@ -2864,16 +2876,16 @@ public abstract class PlayerImpl implements Player, Serializable {
/** /**
* @param game * @param game
* @param appliedEffects * @param appliedEffects
* @param numberChaosSides The number of chaos sides the planar die * @param numberChaosSides The number of chaos sides the planar die
* currently has (normally 1 but can be 5) * currently has (normally 1 but can be 5)
* @param numberPlanarSides The number of chaos sides the planar die * @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 * @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
* or NilRoll * or NilRoll
*/ */
@Override @Override
public PlanarDieRoll rollPlanarDie(Game game, ArrayList<UUID> appliedEffects, int numberChaosSides, public PlanarDieRoll rollPlanarDie(Game game, ArrayList<UUID> appliedEffects, int numberChaosSides,
int numberPlanarSides) { int numberPlanarSides) {
int result = RandomUtil.nextInt(9) + 1; int result = RandomUtil.nextInt(9) + 1;
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL; PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
if (numberChaosSides + numberPlanarSides > 9) { if (numberChaosSides + numberPlanarSides > 9) {
@ -3030,7 +3042,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/** /**
* @param ability * @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 sourceObject
* @param game * @param game
* @return * @return
@ -3046,7 +3058,7 @@ public abstract class PlayerImpl implements Player, Serializable {
game.getContinuousEffects().costModification(copy, game); game.getContinuousEffects().costModification(copy, game);
} }
Card card = game.getCard(ability.getSourceId()); Card card = game.getCard(copy.getSourceId());
if (card != null) { if (card != null) {
for (Ability ability0 : card.getAbilities()) { for (Ability ability0 : card.getAbilities()) {
if (ability0 instanceof AdjustingSourceCosts) { if (ability0 instanceof AdjustingSourceCosts) {
@ -3072,10 +3084,16 @@ public abstract class PlayerImpl implements Player, Serializable {
if (available == null) { if (available == null) {
return true; return true;
} }
MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(), MageObjectReference permittingObject = game.getContinuousEffects().asThough(copy.getSourceId(),
AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game); AsThoughEffectType.SPEND_OTHER_MANA, copy, copy.getControllerId(), game);
for (Mana mana : abilityOptions) { for (Mana mana : abilityOptions) {
for (Mana avail : available) { 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()) { if (permittingObject != null && mana.count() <= avail.count()) {
return true; return true;
} }
@ -3087,14 +3105,35 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
} }
// ALTERNATIVE COST from source card (AlternativeCostSourceAbility)
for (Ability objectAbility : sourceObject.getAbilities()) { for (Ability objectAbility : sourceObject.getAbilities()) {
if (objectAbility instanceof AlternativeCostSourceAbility) { 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 true;
} }
} }
} }
return canPlayCardByAlternateCost(card, available, ability, game);
// ALTERNATIVE COST FROM dynamic effects
if (getCastSourceIdWithAlternateMana().contains(copy.getSourceId())) {
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId());
Costs<Cost> 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; return false;
} }
@ -3215,35 +3254,85 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
} }
private List<Ability> cardPlayableAbilities(Game game, Card card, boolean setControllerId) { private void getPlayableFromNonHandCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<Ability> output) {
List<Ability> playable = new ArrayList(); if (fromZone == null) {
if (card != null) { return;
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) { }
if (!ability.canActivate(playerId, game).canActivate()) {
continue;
}
UUID savedControllerId = null; // BASIC abilities
if (setControllerId) { if (card instanceof SplitCard) {
// For when owner != caster, e.g. with Psychic Intrusion and similar effects. SplitCard splitCard = (SplitCard) card;
savedControllerId = getId(); getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, output);
ability.setControllerId(getId()); getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, output);
} getPlayableFromNonHandCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(), availableMana, output);
if (ability instanceof SpellAbility } else if (card instanceof AdventureCard) {
&& null != game.getContinuousEffects().asThough(card.getId(), // adventure must use different card characteristics for different spells (main or adventure)
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, getId(), game)) { AdventureCard adventureCard = (AdventureCard) card;
playable.add(ability); getPlayableFromNonHandCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(), availableMana, output);
} else if (ability instanceof PlayLandAbility getPlayableFromNonHandCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(), availableMana, output);
&& null != game.getContinuousEffects().asThough(card.getId(), } else {
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) { getPlayableFromNonHandCardSingle(game, fromZone, card, card.getAbilities(), availableMana, output);
playable.add(ability); }
}
if (setControllerId) { // DYNAMIC ADDED abilities
ability.setControllerId(savedControllerId); if (fromZone != Zone.ALL) { // TODO: test revealed cards with dynamic added abilities
} // Other activated abilities (added dynamic by effects)
LinkedHashMap<UUID, ActivatedAbility> 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<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> 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 @Override
@ -3262,18 +3351,16 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
boolean fromAll = fromZone.equals(Zone.ALL); boolean fromAll = fromZone.equals(Zone.ALL);
Collection<Card> cards;
if (hidden && (fromAll || fromZone == Zone.HAND)) { if (hidden && (fromAll || fromZone == Zone.HAND)) {
cards = hideDuplicatedAbilities ? hand.getUniqueCards(game) : hand.getCards(game); for (Card card : hand.getCards(game)) {
for (Card card : cards) {
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?) for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
if (ability.getZone().match(Zone.HAND)) { if (ability.getZone().match(Zone.HAND)) {
if (ability instanceof ActivatedAbility) { if (ability instanceof ActivatedAbility) {
if (!(ability instanceof PlayLandAbility) if (!(ability instanceof PlayLandAbility)
|| !game.getContinuousEffects().preventedByRuleModification( || !game.getContinuousEffects().preventedByRuleModification(
GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(),
ability.getSourceId(), playerId), ability, game, true)) { ability.getSourceId(), playerId), ability, game, true)) {
if (canPlay((ActivatedAbility) ability, availableMana, card, game)) { if (canPlay((ActivatedAbility) ability, availableMana, card, game)) {
playable.add(ability); playable.add(ability);
} }
@ -3297,37 +3384,15 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
if (fromAll || fromZone == Zone.GRAVEYARD) { if (fromAll || fromZone == Zone.GRAVEYARD) {
cards = hideDuplicatedAbilities ? graveyard.getUniqueCards(game) : graveyard.getCards(game); for (Card card : graveyard.getCards(game)) {
for (Card card : cards) { getPlayableFromNonHandCardAll(game, Zone.GRAVEYARD, card, availableMana, playable);
// 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<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable);
playable.addAll(useable.values());
} }
} }
if (fromAll || fromZone == Zone.EXILED) { if (fromAll || fromZone == Zone.EXILED) {
for (ExileZone exile : game.getExile().getExileZones()) { for (ExileZone exile : game.getExile().getExileZones()) {
for (Card card : exile.getCards(game)) { 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) { if (fromAll) {
for (Cards revealedCards : game.getState().getRevealed().values()) { for (Cards revealedCards : game.getState().getRevealed().values()) {
for (Card card : revealedCards.getCards(game)) { 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 != null) {
if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) { if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) {
Card card = player.getLibrary().getFromTop(game); 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 @Override
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId, public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
UUID controllerId, Game game UUID controllerId, Game game
) { ) {
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game); return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
} }
@ -3769,8 +3835,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCards(Card card, Zone toZone, public boolean moveCards(Card card, Zone toZone,
Ability source, Game game, Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) { ) {
Set<Card> cardList = new HashSet<>(); Set<Card> cardList = new HashSet<>();
if (card != null) { if (card != null) {
@ -3781,22 +3847,22 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCards(Cards cards, Zone toZone, public boolean moveCards(Cards cards, Zone toZone,
Ability source, Game game Ability source, Game game
) { ) {
return moveCards(cards.getCards(game), toZone, source, game); return moveCards(cards.getCards(game), toZone, source, game);
} }
@Override @Override
public boolean moveCards(Set<Card> cards, Zone toZone, public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game Ability source, Game game
) { ) {
return moveCards(cards, toZone, source, game, false, false, false, null); return moveCards(cards, toZone, source, game, false, false, false, null);
} }
@Override @Override
public boolean moveCards(Set<Card> cards, Zone toZone, public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game, Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) { ) {
if (cards.isEmpty()) { if (cards.isEmpty()) {
return true; return true;
@ -3891,8 +3957,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardsToExile(Card card, Ability source, public boolean moveCardsToExile(Card card, Ability source,
Game game, boolean withName, UUID exileId, Game game, boolean withName, UUID exileId,
String exileZoneName String exileZoneName
) { ) {
Set<Card> cards = new HashSet<>(); Set<Card> cards = new HashSet<>();
cards.add(card); cards.add(card);
@ -3901,8 +3967,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardsToExile(Set<Card> cards, Ability source, public boolean moveCardsToExile(Set<Card> cards, Ability source,
Game game, boolean withName, UUID exileId, Game game, boolean withName, UUID exileId,
String exileZoneName String exileZoneName
) { ) {
if (cards.isEmpty()) { if (cards.isEmpty()) {
return true; return true;
@ -3918,14 +3984,14 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId, public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game Game game
) { ) {
return this.moveCardToHandWithInfo(card, sourceId, game, true); return this.moveCardToHandWithInfo(card, sourceId, game, true);
} }
@Override @Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId, public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game, boolean withName Game game, boolean withName
) { ) {
boolean result = false; boolean result = false;
Zone fromZone = game.getState().getZone(card.getId()); Zone fromZone = game.getState().getZone(card.getId());
@ -3950,7 +4016,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source, public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
Game game, Zone fromZone Game game, Zone fromZone
) { ) {
UUID sourceId = source == null ? null : source.getSourceId(); UUID sourceId = source == null ? null : source.getSourceId();
Set<Card> movedCards = new LinkedHashSet<>(); Set<Card> movedCards = new LinkedHashSet<>();
@ -3958,7 +4024,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// identify cards from one owner // identify cards from one owner
Cards cards = new CardsImpl(); Cards cards = new CardsImpl();
UUID ownerId = null; UUID ownerId = null;
for (Iterator<Card> it = allCards.iterator(); it.hasNext();) { for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) {
Card card = it.next(); Card card = it.next();
if (cards.isEmpty()) { if (cards.isEmpty()) {
ownerId = card.getOwnerId(); ownerId = card.getOwnerId();
@ -4021,7 +4087,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId, public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone Game game, Zone fromZone
) { ) {
if (card == null) { if (card == null) {
return false; return false;
@ -4050,8 +4116,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId, public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone, Game game, Zone fromZone,
boolean toTop, boolean withName boolean toTop, boolean withName
) { ) {
if (card == null) { if (card == null) {
return false; return false;
@ -4085,7 +4151,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId, 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) { if (card == null) {
return false; return false;
} }
@ -4108,7 +4174,7 @@ public abstract class PlayerImpl implements Player, Serializable {
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' ' + (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
+ ' ' : "") + "to the exile zone"); + ' ' : "") + "to the exile zone");
} }
result = true; result = true;