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) {
return true;
}
if (!ability.getSourceId().equals(getCastSourceIdWithAlternateMana())
if (!getCastSourceIdWithAlternateMana().contains(ability.getSourceId())
&& ability.getManaCostsToPay().convertedManaCost() > 0) {
return true;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,5 @@
package mage.cards.t;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
@ -12,18 +9,8 @@ import mage.abilities.effects.AsThoughManaEffect;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.AsThoughEffectType;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.ManaPoolItem;
@ -32,8 +19,11 @@ import mage.target.TargetCard;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class ThiefOfSanity extends CardImpl {
@ -200,12 +190,8 @@ class ThiefOfSanitySpendAnyManaEffect extends AsThoughEffectImpl implements AsTh
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget())
&& game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
if (affectedControllerId.equals(authorizedPlayerId)) {
// if the card moved from exile to stack the zone change counter is increased by 1
if (game.getState().getZoneChangeCounter(objectId) == ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) {
return true;
}
}
// if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?)
return affectedControllerId.equals(authorizedPlayerId);
} else if (((FixedTarget) getTargetPointer()).getTarget().equals(objectId)) {
// object has moved zone so effect can be discarted
this.discard();

View file

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

View file

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

View file

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

View file

@ -1,4 +1,3 @@
package org.mage.test.AI.basic;
import mage.constants.PhaseStep;
@ -8,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI;
/**
*
* @author LevelX2
*/
public class CastCreaturesTest extends CardTestPlayerBaseAI {
@ -173,7 +171,7 @@ public class CastCreaturesTest extends CardTestPlayerBaseAI {
/**
* Tests that the creature is cast if enough mana is available.
*
* <p>
* Once Ammit Eternal is cast against a computer AI opponent, the AI just
* decides to sit there and only play basic lands. I've sat there and decked
* it because it just plays lands. It's like it views giving the Ammit
@ -194,8 +192,11 @@ public class CastCreaturesTest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ammit Eternal");
setStrictChooseMode(true);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerB, "Ammit Eternal", 1);

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.abilities.activated;
import mage.constants.PhaseStep;
@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class LightningStormTest extends CardTestPlayerBase {
@ -16,10 +14,9 @@ public class LightningStormTest extends CardTestPlayerBase {
* So, this just happened to me. My opponent cast Lightning Storm and while
* it was on the stack I couldn't use the ability despite having land in
* hand which isn't something I've had an issue with before.
*
* <p>
* My opponent had a Leyline of Sanctity in play, so perhaps that was
* causing the issue somehow? Does anyone want to try and replicate it?
*
*/
@Test
public void ActivateByBothPlayersTest() {
@ -31,22 +28,32 @@ public class LightningStormTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Mountain");
addCard(Zone.HAND, playerB, "Mountain");
// A activate
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Storm", playerB);
// B discard and re-target
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Discard");
setChoice(playerB, "playerA");
setChoice(playerB, "Mountain");
setChoice(playerB, "Yes");
addTarget(playerB, playerA);
// A discard and re-target
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard");
setChoice(playerA, "playerB");
setChoice(playerA, "Mountain");
setChoice(playerA, "Yes");
addTarget(playerA, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Lightning Storm", 1);
assertGraveyardCount(playerB, "Mountain", 1);
assertGraveyardCount(playerA, "Mountain", 1);
assertLife(playerA, 20);
assertLife(playerB, 13);
assertLife(playerB, 20 - 3 - 2 - 2);
}
}

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;
import mage.constants.PhaseStep;
@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class SpendOtherManaTest extends CardTestPlayerBase {
@ -47,7 +45,7 @@ public class SpendOtherManaTest extends CardTestPlayerBase {
/**
* Tron mana doesn't work with Oath of Nissa. (e.g. can't cast Chandra,
* Flamecaller with Urza's Tower, Power Plant, and Mine.)
*
* <p>
* AI don't get the Planeswalker as playable card (probably because of the
* as thought effect)
*/
@ -76,7 +74,7 @@ public class SpendOtherManaTest extends CardTestPlayerBase {
* I was unable to cast Nissa, Voice of Zendikar using black mana with Oath
* of Nissa in play. Pretty sure Oath is working usually, so here were the
* conditions in my game:
*
* <p>
* -Cast Dark Petition with spell mastery -Attempt to cast Nissa, Voice of
* Zendikar using the triple black mana from Dark Petition
*/
@ -122,20 +120,84 @@ public class SpendOtherManaTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Hostage Taker"); // {2}{U}{B}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hostage Taker");
setChoice(playerA, "Silvercoat Lion");
addTarget(playerA, "Silvercoat Lion");
// red mana must be used as any mana
activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {R}."); // red mana to pool
activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {R}."); // red mana to pool
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); // cast it from exile with red mana from pool
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Hostage Taker", 1);
assertTappedCount("Mountain", true, 4);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
}
@Test
public void test_QuicksilverElemental_Normal() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
// {U}: Quicksilver Elemental gains all activated abilities of target creature until end of turn.
// You may spend blue mana as though it were mana of any color to pay the activation costs of Quicksilver 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;
import mage.constants.PhaseStep;
@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
@ -23,20 +21,22 @@ public class PsychicIntrusionTest extends CardTestPlayerBase {
// Target opponent reveals their hand. You choose a nonland card from that player's
// graveyard or hand and exile it. You may cast that card for as long as it remains exiled,
// and you may spend mana as though it were mana of any color to cast that spell.
addCard(Zone.HAND, playerA, "Psychic Intrusion", 1);
addCard(Zone.HAND, playerA, "Psychic Intrusion", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.HAND, playerB, "Elspeth, Sun's Champion", 1);
addCard(Zone.HAND, playerB, "Elspeth, Sun's Champion", 1); // {4}{W}{W}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Intrusion", playerB);
addTarget(playerA, "Elspeth, Sun's Champion");
setChoice(playerA, "Elspeth, Sun's Champion");
// cast from exile with any mana
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Elspeth, Sun's Champion");
setStrictChooseMode(true);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Psychic Intrusion", 1);
assertHandCount(playerB, "Elspeth, Sun's Champion", 0);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -63,7 +63,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
*/
public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) {
MageObject object = game.getObject(sourceId);
if ((Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) {
if (game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) {
return (Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability)
}
return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
@ -98,7 +98,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
Player player = game.getPlayer(playerId);
if (player != null
&& getSourceId().equals(player.getCastSourceIdWithAlternateMana())) {
&& player.getCastSourceIdWithAlternateMana().contains(getSourceId())) {
return ActivationStatus.getFalse();
}
}

View file

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

View file

@ -505,12 +505,19 @@ public class ContinuousEffects implements Serializable {
UUID idToCheck;
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else {
Card card = game.getCard(objectId);
if (card != null && card instanceof SplitCardHalf) {
if (card instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
} else if (card != null && type == AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
&& card instanceof AdventureCardSpell) {
} else if (card instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
} else {
idToCheck = objectId;

View file

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

View file

@ -1,11 +1,6 @@
package mage.abilities.effects.common.continuous;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
@ -15,6 +10,8 @@ import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
* @author nantuko
*/
@ -55,26 +52,18 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
Card cardOnTop = game.getCard(objectId);
Card cardToCheckProperties = cardOnTop;
Card cardToCheck = game.getCard(objectId);
objectId = game.getCard(objectId).getMainCard().getId(); // for split cards
// Check each ability individually, as e.g. Adventures and associated creatures may get different results from the filter.
if (affectedAbility != null) {
MageObject sourceObject = affectedAbility.getSourceObject(game);
if (sourceObject != null && sourceObject instanceof Card) {
cardToCheckProperties = (Card) sourceObject;
}
}
if (cardOnTop != null
if (cardToCheck != null
&& playerId.equals(source.getControllerId())
&& cardOnTop.isOwnedBy(source.getControllerId())
&& (!cardToCheckProperties.getManaCost().isEmpty() || cardToCheckProperties.isLand())
&& filter.match(cardToCheckProperties, game)) {
Player player = game.getPlayer(cardOnTop.getOwnerId());
if (player != null && cardOnTop.equals(player.getLibrary().getFromTop(game))) {
return true;
}
&& cardToCheck.isOwnedBy(source.getControllerId())
&& (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
&& filter.match(cardToCheck, game)) {
Player player = game.getPlayer(cardToCheck.getOwnerId());
UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId();
return objectId.equals(needCardID);
}
return false;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package mage.constants;
/**
*
* @author North
*/
public enum AsThoughEffectType {
@ -19,15 +18,29 @@ public enum AsThoughEffectType {
BLOCK_FORESTWALK,
DAMAGE_NOT_BLOCKED,
BE_BLOCKED,
PLAY_FROM_NOT_OWN_HAND_ZONE, // do not use dialogs in "applies" method for that type of effect (it calls multiple times)
// PLAY_FROM_NOT_OWN_HAND_ZONE + CAST_AS_INSTANT:
// 1. Do not use dialogs in "applies" method for that type of effect (it calls multiple times and will freeze the game)
// 2. All effects in "applies" must checks affectedControllerId.equals(source.getControllerId()) (if not then all players will be able to play it)
// 3. Target points to mainCard, but card's characteristics from objectId (split, adventure)
// TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId
PLAY_FROM_NOT_OWN_HAND_ZONE,
CAST_AS_INSTANT,
ACTIVATE_AS_INSTANT,
DAMAGE,
SHROUD,
HEXPROOF,
PAY_0_ECHO,
LOOK_AT_FACE_DOWN,
// SPEND_OTHER_MANA:
// 1. It's uses for mana calcs at any zone, not stack only
// 2. Compare zone change counter as "objectZCC <= targetZCC + 1"
// 3. Compare zone with original (like exiled) and stack, not stack only
// TODO: search all SPEND_ONLY_MANA effects and improve counters compare as SPEND_OTHER_MANA
SPEND_OTHER_MANA,
SPEND_ONLY_MANA,
TARGET
}

View file

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

View file

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