1
0
Fork 0
mirror of https://github.com/correl/mage.git synced 2025-04-09 01:01:06 -09:00

Merge origin/master

Conflicts:
	Mage/src/main/java/mage/players/PlayerImpl.java
This commit is contained in:
LevelX2 2019-12-14 18:41:12 +01:00
commit b7b3bc4474
51 changed files with 979 additions and 396 deletions

View file

@ -243,6 +243,7 @@ public class ScryfallImageSupportCards {
add("C19");
add("ELD");
add("CELD");
add("THB");
//
add("EURO");
add("GPX");

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

@ -0,0 +1,62 @@
package mage.cards.c;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.permanent.token.HumanSoldierToken;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class CommandingPresence extends CardImpl {
public CommandingPresence(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}");
this.subtype.add(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget.getTargetName());
this.addAbility(ability);
// Enchanted creature gets +2/+2 and has first strike and "Whenever this creature deals combat damage to a player, create a 1/1 white Human Soldier creature token."
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
ability.addEffect(new GainAbilityAttachedEffect(
FirstStrikeAbility.getInstance(), AttachmentType.AURA,
Duration.WhileOnBattlefield, "and has first strike"
));
ability.addEffect(new GainAbilityAttachedEffect(
new DealsCombatDamageToAPlayerTriggeredAbility(
new CreateTokenEffect(new HumanSoldierToken()), false
), AttachmentType.AURA, Duration.WhileOnBattlefield,
"and \"Whenever this creature deals combat damage to a player, " +
"create a 1/1 white Human Soldier creature token.\""
));
this.addAbility(ability);
}
private CommandingPresence(final CommandingPresence card) {
super(card);
}
@Override
public CommandingPresence copy() {
return new CommandingPresence(this);
}
}

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

@ -0,0 +1,89 @@
package mage.cards.d;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.DevotionCount;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.continuous.SetToughnessSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class DaxosBlessedByTheSun extends CardImpl {
private static final DynamicValue xValue = new DevotionCount(ColoredManaSymbol.W);
public DaxosBlessedByTheSun(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{W}{W}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.DEMIGOD);
this.power = new MageInt(2);
this.toughness = new MageInt(0);
// Daxos's toughness is equal to your devotion to white.
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new SetToughnessSourceEffect(xValue, Duration.EndOfGame
).setText("{this}'s toughness is equal to your devotion to white")));
// Whenever another creature you control enters the battlefield or dies, you gain 1 life.
this.addAbility(new DaxosBlessedByTheSunAbility());
}
private DaxosBlessedByTheSun(final DaxosBlessedByTheSun card) {
super(card);
}
@Override
public DaxosBlessedByTheSun copy() {
return new DaxosBlessedByTheSun(this);
}
}
class DaxosBlessedByTheSunAbility extends TriggeredAbilityImpl {
DaxosBlessedByTheSunAbility() {
super(Zone.BATTLEFIELD, new GainLifeEffect(1));
}
private DaxosBlessedByTheSunAbility(DaxosBlessedByTheSunAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD)
|| (event.getType() == GameEvent.EventType.ZONE_CHANGE
&& ((ZoneChangeEvent) event).isDiesEvent());
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getTargetId().equals(this.getSourceId())) {
return false;
}
Permanent creature = game.getPermanentOrLKIBattlefield(event.getTargetId());
return creature != null && creature.isControlledBy(this.getControllerId());
}
@Override
public String getRule() {
return "Whenever another creature you control enters the battlefield or dies, you gain 1 life.";
}
@Override
public DaxosBlessedByTheSunAbility copy() {
return new DaxosBlessedByTheSunAbility(this);
}
}

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

@ -0,0 +1,45 @@
package mage.cards.h;
import mage.MageInt;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HeroicAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class HeroOfTheWinds extends CardImpl {
public HeroOfTheWinds(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SOLDIER);
this.power = new MageInt(1);
this.toughness = new MageInt(4);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever you cast a spell that targets Hero of the Winds, creatures you control get +1/+0 until end of turn.
this.addAbility(new HeroicAbility(new BoostControlledEffect(
1, 0, Duration.EndOfTurn
), false, false));
}
private HeroOfTheWinds(final HeroOfTheWinds card) {
super(card);
}
@Override
public HeroOfTheWinds copy() {
return new HeroOfTheWinds(this);
}
}

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

@ -0,0 +1,52 @@
package mage.cards.i;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.SacrificeControllerEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class InevitableEnd extends CardImpl {
public InevitableEnd(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}");
this.subtype.add(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget.getTargetName());
this.addAbility(ability);
// Enchanted creature has "At the beginning of your upkeep, sacrifice a creature."
this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect(
new BeginningOfUpkeepTriggeredAbility(new SacrificeControllerEffect(
StaticFilters.FILTER_PERMANENT_CREATURE, 1, null
), TargetController.YOU, false), AttachmentType.AURA
)));
}
private InevitableEnd(final InevitableEnd card) {
super(card);
}
@Override
public InevitableEnd copy() {
return new InevitableEnd(this);
}
}

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

@ -0,0 +1,60 @@
package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class StaggeringInsight extends CardImpl {
public StaggeringInsight(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}{U}");
this.subtype.add(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget.getTargetName());
this.addAbility(ability);
// Enchanted creature gets +1/+1 and has lifelink and "Whenever this creature deals combat damage to a player, draw a card."
ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 1));
ability.addEffect(new GainAbilityAttachedEffect(
LifelinkAbility.getInstance(), AttachmentType.AURA,
Duration.WhileOnBattlefield, "and has lifelink"
));
ability.addEffect(new GainAbilityAttachedEffect(
new DealsCombatDamageToAPlayerTriggeredAbility(
new DrawCardSourceControllerEffect(1), false
), AttachmentType.AURA, Duration.WhileOnBattlefield,
"and \"Whenever this creature deals combat damage to a player, draw a card.\""
));
this.addAbility(ability);
}
private StaggeringInsight(final StaggeringInsight card) {
super(card);
}
@Override
public StaggeringInsight copy() {
return new StaggeringInsight(this);
}
}

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

@ -26,10 +26,14 @@ public final class TherosBeyondDeath extends ExpansionSet {
this.ratioBoosterMythic = 8;
this.maxCardNumberInBooster = 254;
cards.add(new SetCardInfo("Commanding Presence", 7, Rarity.UNCOMMON, mage.cards.c.CommandingPresence.class));
cards.add(new SetCardInfo("Daxos, Blessed by the Sun", 9, Rarity.UNCOMMON, mage.cards.d.DaxosBlessedByTheSun.class));
cards.add(new SetCardInfo("Eidolon of Philosophy", 48, Rarity.COMMON, mage.cards.e.EidolonOfPhilosophy.class));
cards.add(new SetCardInfo("Elspeth, Sun's Nemesis", 14, Rarity.MYTHIC, mage.cards.e.ElspethSunsNemesis.class));
cards.add(new SetCardInfo("Forest", 254, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Hero of the Winds", 23, Rarity.UNCOMMON, mage.cards.h.HeroOfTheWinds.class));
cards.add(new SetCardInfo("Indomitable Will", 25, Rarity.COMMON, mage.cards.i.IndomitableWill.class));
cards.add(new SetCardInfo("Inevitable End", 102, Rarity.UNCOMMON, mage.cards.i.InevitableEnd.class));
cards.add(new SetCardInfo("Island", 251, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Klothys's Design", 176, Rarity.UNCOMMON, mage.cards.k.KlothyssDesign.class));
cards.add(new SetCardInfo("Leonin of the Lost Pride", 28, Rarity.COMMON, mage.cards.l.LeoninOfTheLostPride.class));
@ -41,6 +45,7 @@ public final class TherosBeyondDeath extends ExpansionSet {
cards.add(new SetCardInfo("Plains", 250, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS));
cards.add(new SetCardInfo("Revoke Existence", 34, Rarity.COMMON, mage.cards.r.RevokeExistence.class));
cards.add(new SetCardInfo("Setessan Champion", 198, Rarity.RARE, mage.cards.s.SetessanChampion.class));
cards.add(new SetCardInfo("Staggering Insight", 228, Rarity.UNCOMMON, mage.cards.s.StaggeringInsight.class));
cards.add(new SetCardInfo("Swamp", 252, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
}
}

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

@ -7,6 +7,7 @@ import mage.constants.Zone;
import mage.filter.Filter;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -117,4 +118,48 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase {
Assert.assertFalse(emrakul.getAbilities().contains(FlyingAbility.getInstance())); // loses flying though
}
/**
* So Starfield of Nyx in play. Detention Sphere with a Song of the Dryads
* under it. Wrath of god on the stack. In response I Parallax Wave Honden
* of life's Web, Mirrari's Wake, Sphere of Safety, Aura Shards, and
* Enchantress's Presence to save them. Wrath resolves and Song of the
* Dryads come back along with the 5 named enchantments. Opp targets
* Starfield of Nyx with Song of the Dryads. When song of the dryads
* attaches all my enchantments stay creatures but as 1/1's instead of cmc.
* I untap draw and cast Humility. All my 1/1 enchantments die while opp's
* bruna, light of alabaster keeps her abilities. After mirrari's wake dies
* due to this I still have double mana. So yea, something broke big time
* there.
*/
@Test
@Ignore
public void testStarfieldOfNyxAndSongOfTheDryads() {
// Nontoken creatures you control get +1/+1 and have vigilance.
addCard(Zone.BATTLEFIELD, playerA, "Always Watching", 5);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
// At the beginning of your upkeep, you may return target enchantment card from your graveyard to the battlefield.
// As long as you control five or more enchantments, each other non-Aura enchantment you control is a creature in
// addition to its other types and has base power and base toughness each equal to its converted mana cost.
addCard(Zone.HAND, playerA, "Starfield of Nyx"); // "{4}{W}"
addCard(Zone.BATTLEFIELD, playerB, "Forest", 3);
// Enchanted permanent is a colorless Forest land.
addCard(Zone.HAND, playerB, "Song of the Dryads"); // Enchant Permanent {2}{G}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Starfield of Nyx");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Song of the Dryads", "Starfield of Nyx");
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Always Watching", 5);
assertPermanentCount(playerB, "Song of the Dryads", 1);
assertPowerToughness(playerA, "Always Watching", 0, 0, Filter.ComparisonScope.All);
assertPermanentCount(playerA, "Forest", 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

@ -110,6 +110,7 @@ public enum SubType {
// D
DATHOMIRIAN("Dathomirian", SubTypeSet.CreatureType, true), // Star Wars
DAUTHI("Dauthi", SubTypeSet.CreatureType),
DEMIGOD("Demigod", SubTypeSet.CreatureType),
DEMON("Demon", SubTypeSet.CreatureType),
DESERTER("Deserter", SubTypeSet.CreatureType),
DEVIL("Devil", SubTypeSet.CreatureType),

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

@ -159,9 +159,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;
@ -270,9 +271,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();
@ -339,9 +341,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();
@ -416,9 +421,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();
@ -443,9 +447,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();
}
@ -1060,26 +1062,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;
@ -1143,11 +1152,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 {
@ -1161,7 +1172,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);
@ -3059,10 +3071,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;
}
@ -3074,14 +3092,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(game.getCard(ability.getSourceId()), 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(game.getCard(ability.getSourceId()), available, copy, game);
}
return false;
}
@ -3202,35 +3241,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
@ -3249,11 +3338,9 @@ 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) {
@ -3284,37 +3371,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);
}
}
}
@ -3323,7 +3388,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);
}
}
}
@ -3335,7 +3401,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);
}
}
}

View file

@ -36444,7 +36444,7 @@ Nyxborn Courser|Theros: Beyond Death|29|C|{1}{W}{W}|Enchantment Creature - Centa
Revoke Existence|Theros: Beyond Death|34|C|{1}{W}|Sorcery|||Exile target artifact or enchantment.|
Eidolon of Philosophy|Theros: Beyond Death|48|C|{U}|Enchantment Creature - Spirit|1|2|{6}{U}, Sacrifice Eidolon of Philosophy: Draw three cards.|
Memory Drain|Theros: Beyond Death|54|C|{2}{U}{U}|Instant|||Counter target spell. Scry 2.|
Inevitable End|Theros: Beyond Death|102|U|{2}{B}|Enchantment - Aura|||Enchant creature$Enchanted creature has "At the beginning of your upkeep, sacrifice this creature."|
Inevitable End|Theros: Beyond Death|102|U|{2}{B}|Enchantment - Aura|||Enchant creature$Enchanted creature has "At the beginning of your upkeep, sacrifice a creature."|
Mire's Grasp|Theros: Beyond Death|106|C|{1}{B}|Enchantment - Aura|||Enchant creature$Enchanted creature gets -3/-3.|
The Akroan War|Theros: Beyond Death|124|R|{3}{R}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I — Gain control of target creature for as long as The Akroan War remains on the battlefield.$II — Until your next turn, creatures your opponents control attack each combat if able.$III — Each tapped creature deals damage to itself equal to its power.|
Underworld Rage-Hound|Theros: Beyond Death|163|C|{1}{R}|Creature - Elemental Hound|3|1|Underworld Rage-Hound attacks each combat if able.$Escape—{3}{R}, Exile three other cards from your graveyard.$Underworld Rage-Hound escapes with a +1/+1 counter on it.|
@ -36459,3 +36459,5 @@ Swamp|Theros: Beyond Death|252|C||Basic Land - Swamp|||({T}: Add {B}.)|
Mountain|Theros: Beyond Death|253|C||Basic Land - Mountain|||({T}: Add {R}.)|
Forest|Theros: Beyond Death|254|C||Basic Land - Forest|||({T}: Add {G}.)|
Athreos, Shroud-Veiled|Theros: Beyond Death|269|M|{4}{W}{B}|Legendary Enchantment Creature - God|4|7|Indestructible$As long as your devotion to white and black is less than seven, Athreos isn't a creature.$At the beginning of your end step, put a coin counter on another target creature.$Whenever a creature with a coin counter on it dies or is put into exile, return that card to the battlefield under your control.|
Elspeth, Undaunted Hero|Theros: Beyond Death|270|M|{2}{W}{W}{W}|Legendary Planeswalker - Elspeth|5|+2: Put a +1/+1 counter on each of up to two target creatures.$2: Search your library and/or graveyard for a card named Sunlit Hoplite and put it onto the battlefield. If you search your library this way, shuffle it.$8: Until end of turn, creatures you control gain flying and get +X/+X, where X is your devotion to white.|
Ashiok, Sculptor of Fears|Theros: Beyond Death|274|M|{4}{U}{B}|Legendary Planeswalker - Ashiok|4|+2: Draw a card. Each player puts the top two cards of their library into their graveyard.$5: Put target creature card from a graveyard onto the battlefield under you control.$11: Gain control of all creatures target opponent controls.|