mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
Rework AsThough handling to allow choosing/affecting a specific alternate cast (#11114)
* Rework AsThoughEffect * some cleanup of MageIdentifer * refactor ActivationStatus * fix bolas's citadel * fix a couple of the Alternative Cost being applied too broadly. * fix Risen Executioneer * allow cancellation of AsThough choice. * fix One with the Multiverse * cleanup cards needing their own MageIdentifier * last couple of fixes * apply reviews for cleaner code. * some more cleanup
This commit is contained in:
parent
ba135abc78
commit
7c454fb24c
66 changed files with 1176 additions and 395 deletions
|
|
@ -1513,7 +1513,9 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
|
||||
protected boolean playManaHandling(Ability ability, ManaCost unpaid, final Game game) {
|
||||
// log.info("paying for " + unpaid.getText());
|
||||
ApprovingObject approvingObject = game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
|
||||
Set<ApprovingObject> approvingObjects = game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
|
||||
boolean hasApprovingObject = !approvingObjects.isEmpty();
|
||||
|
||||
ManaCost cost;
|
||||
List<MageObject> producers;
|
||||
if (unpaid instanceof ManaCosts) {
|
||||
|
|
@ -1543,7 +1545,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
if (approvingObject != null && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
if (hasApprovingObject && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
continue;
|
||||
}
|
||||
if (activateAbility(manaAbility, game)) {
|
||||
|
|
@ -1560,11 +1562,11 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||
if (cost instanceof ColoredManaCost) {
|
||||
for (Mana netMana : manaAbility.getNetMana(game)) {
|
||||
if (cost.testPay(netMana) || approvingObject != null) {
|
||||
if (cost.testPay(netMana) || hasApprovingObject) {
|
||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
if (approvingObject != null && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
if (hasApprovingObject && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
continue;
|
||||
}
|
||||
if (activateAbility(manaAbility, game)) {
|
||||
|
|
@ -1578,11 +1580,11 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||
if (cost instanceof SnowManaCost) {
|
||||
for (Mana netMana : manaAbility.getNetMana(game)) {
|
||||
if (cost.testPay(netMana) || approvingObject != null) {
|
||||
if (cost.testPay(netMana) || hasApprovingObject) {
|
||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
if (approvingObject != null && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
if (hasApprovingObject && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
continue;
|
||||
}
|
||||
if (activateAbility(manaAbility, game)) {
|
||||
|
|
@ -1596,11 +1598,11 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||
if (cost instanceof HybridManaCost) {
|
||||
for (Mana netMana : manaAbility.getNetMana(game)) {
|
||||
if (cost.testPay(netMana) || approvingObject != null) {
|
||||
if (cost.testPay(netMana) || hasApprovingObject) {
|
||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
if (approvingObject != null && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
if (hasApprovingObject && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
continue;
|
||||
}
|
||||
if (activateAbility(manaAbility, game)) {
|
||||
|
|
@ -1614,11 +1616,11 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||
if (cost instanceof MonoHybridManaCost) {
|
||||
for (Mana netMana : manaAbility.getNetMana(game)) {
|
||||
if (cost.testPay(netMana) || approvingObject != null) {
|
||||
if (cost.testPay(netMana) || hasApprovingObject) {
|
||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
if (approvingObject != null && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
if (hasApprovingObject && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
continue;
|
||||
}
|
||||
if (activateAbility(manaAbility, game)) {
|
||||
|
|
@ -1632,11 +1634,11 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||
if (cost instanceof ColorlessManaCost) {
|
||||
for (Mana netMana : manaAbility.getNetMana(game)) {
|
||||
if (cost.testPay(netMana) || approvingObject != null) {
|
||||
if (cost.testPay(netMana) || hasApprovingObject) {
|
||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
if (approvingObject != null && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
if (hasApprovingObject && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
continue;
|
||||
}
|
||||
if (activateAbility(manaAbility, game)) {
|
||||
|
|
@ -1650,11 +1652,11 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
for (ActivatedManaAbilityImpl manaAbility : getManaAbilitiesSortedByManaCount(mageObject, game)) {
|
||||
if (cost instanceof GenericManaCost) {
|
||||
for (Mana netMana : manaAbility.getNetMana(game)) {
|
||||
if (cost.testPay(netMana) || approvingObject != null) {
|
||||
if (cost.testPay(netMana) || hasApprovingObject) {
|
||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
if (approvingObject != null && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
if (hasApprovingObject && !canUseAsThoughManaToPayManaCost(cost, ability, netMana, manaAbility, mageObject, game)) {
|
||||
continue;
|
||||
}
|
||||
if (activateAbility(manaAbility, game)) {
|
||||
|
|
@ -1673,7 +1675,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
// pay phyrexian life costs
|
||||
if (cost.isPhyrexian()) {
|
||||
alreadyTryingToPayPhyrexian = true;
|
||||
boolean paidPhyrexian = cost.pay(ability, game, ability, playerId, false, null) || approvingObject != null;
|
||||
boolean paidPhyrexian = cost.pay(ability, game, ability, playerId, false, null) || hasApprovingObject;
|
||||
alreadyTryingToPayPhyrexian = false;
|
||||
return paidPhyrexian;
|
||||
}
|
||||
|
|
@ -1688,7 +1690,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
ManaOptions specialMana = specialAction == null ? null : specialAction.getManaOptions(ability, game, unpaid);
|
||||
if (specialMana != null) {
|
||||
for (Mana netMana : specialMana) {
|
||||
if (cost.testPay(netMana) || approvingObject != null) {
|
||||
if (cost.testPay(netMana) || hasApprovingObject) {
|
||||
if (netMana instanceof ConditionalMana && !((ConditionalMana) netMana).apply(ability, game, getId(), cost)) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.player.human;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.costs.VariableCost;
|
||||
|
|
@ -16,6 +17,8 @@ import mage.cards.decks.Deck;
|
|||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.*;
|
||||
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
|
||||
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterAttackingCreature;
|
||||
import mage.filter.common.FilterBlockingCreature;
|
||||
|
|
@ -51,9 +54,6 @@ import java.util.*;
|
|||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
|
||||
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
|
@ -2260,7 +2260,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
|
||||
// hide on alternative cost activated
|
||||
if (!getCastSourceIdWithAlternateMana().contains(ability.getSourceId())
|
||||
if (!getCastSourceIdWithAlternateMana().getOrDefault(ability.getSourceId(), Collections.emptySet()).contains(MageIdentifier.Default)
|
||||
&& ability.getManaCostsToPay().manaValue() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,13 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.cards.ModalDoubleFacedCard;
|
||||
import mage.cards.ModalDoubleFacedCardHalf;
|
||||
import mage.cards.*;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.ExileZone;
|
||||
import mage.game.Game;
|
||||
|
|
@ -29,6 +16,10 @@ import mage.target.TargetCard;
|
|||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author credman0
|
||||
|
|
@ -218,7 +209,7 @@ class AminatousAuguryCastFromExileEffect extends AsThoughEffectImpl {
|
|||
usedCardTypes.addAll(unusedCardTypes);
|
||||
game.getState().setValue(source.getSourceId().toString() + "cardTypes", usedCardTypes);
|
||||
}
|
||||
player.setCastSourceIdWithAlternateMana(objectId, null, card.getSpellAbility().getCosts());
|
||||
allowCardToPlayWithoutMana(objectId, source, player.getId(), game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
|
@ -44,7 +45,7 @@ public final class BolassCitadel extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
|
||||
|
||||
// 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.
|
||||
this.addAbility(new SimpleStaticAbility(new BolassCitadelPlayTheTopCardEffect()));
|
||||
this.addAbility(new SimpleStaticAbility(new BolassCitadelPlayTheTopCardEffect()).setIdentifier(MageIdentifier.BolassCitadelAlternateCast));
|
||||
|
||||
// {T}, Sacrifice ten nonland permanents: Each opponent loses 10 life.
|
||||
Ability ability = new SimpleActivatedAbility(new LoseLifeOpponentsEffect(10), new TapSourceCost());
|
||||
|
|
@ -118,7 +119,7 @@ class BolassCitadelPlayTheTopCardEffect extends AsThoughEffectImpl {
|
|||
Costs newCosts = new CostsImpl();
|
||||
newCosts.add(lifeCost);
|
||||
newCosts.addAll(cardToCheck.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(cardToCheck.getId(), null, newCosts);
|
||||
player.setCastSourceIdWithAlternateMana(cardToCheck.getId(), null, newCosts, MageIdentifier.BolassCitadelAlternateCast);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class CemeteryIlluminatorExileEffect extends OneShotEffect {
|
|||
class CemeteryIlluminatorPlayTopEffect extends AsThoughEffectImpl {
|
||||
|
||||
public CemeteryIlluminatorPlayTopEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "Once each turn, you may cast a spell from the top of your library if it shares a card type with a card exiled with {this}";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public final class DanithaNewBenaliasLight extends CardImpl {
|
|||
class DanithaNewBenaliasLightCastFromGraveyardEffect extends AsThoughEffectImpl {
|
||||
|
||||
DanithaNewBenaliasLightCastFromGraveyardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCardInPlay, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCardInPlay);
|
||||
staticText = "once during each of your turns, you may cast an Aura or Equipment spell from your graveyard";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.d;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksTriggeredAbility;
|
||||
|
|
@ -17,9 +16,9 @@ import mage.abilities.effects.Effect;
|
|||
import mage.abilities.effects.common.ExileTargetCardCopyAndCastEffect;
|
||||
import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.constants.*;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
|
|
@ -27,6 +26,8 @@ import mage.players.Player;
|
|||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
import mage.watchers.common.SpellsCastWatcher;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author weirddan455
|
||||
|
|
@ -53,7 +54,7 @@ public final class Demilich extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// You may cast Demilich from your graveyard by exiling four instants and/or sorcery cards from your graveyard in addition to paying its other costs.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new DemilichPlayEffect()));
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new DemilichPlayEffect()).setIdentifier(MageIdentifier.DemilichAlternateCast));
|
||||
}
|
||||
|
||||
private Demilich(final Demilich card) {
|
||||
|
|
@ -123,7 +124,7 @@ class DemilichPlayEffect extends AsThoughEffectImpl {
|
|||
if (controller != null) {
|
||||
Costs<Cost> costs = new CostsImpl<>();
|
||||
costs.add(new ExileFromGraveCost(new TargetCardInYourGraveyard(4, StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)));
|
||||
controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{U}{U}{U}{U}"), costs);
|
||||
controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{U}{U}{U}{U}"), costs, MageIdentifier.DemilichAlternateCast);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package mage.cards.d;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.CostsImpl;
|
||||
|
|
@ -9,22 +10,22 @@ import mage.abilities.costs.common.DiscardCardCost;
|
|||
import mage.abilities.costs.common.PayLifeCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.abilities.keyword.EnchantAbility;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author arcox
|
||||
|
|
@ -54,7 +55,7 @@ public final class DemonicEmbrace extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// You may cast Demonic Embrace from your graveyard by paying 3 life and discarding a card in addition to paying its other costs.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new DemonicEmbracePlayEffect()));
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new DemonicEmbracePlayEffect()).setIdentifier(MageIdentifier.DemonicEmbraceAlternateCast));
|
||||
}
|
||||
|
||||
private DemonicEmbrace(final DemonicEmbrace card) {
|
||||
|
|
@ -98,7 +99,10 @@ class DemonicEmbracePlayEffect extends AsThoughEffectImpl {
|
|||
Costs<Cost> costs = new CostsImpl<>();
|
||||
costs.add(new PayLifeCost(3));
|
||||
costs.add(new DiscardCardCost());
|
||||
player.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{1}{B}{B}"), costs);
|
||||
player.setCastSourceIdWithAlternateMana(
|
||||
sourceId, new ManaCostsImpl<>("{1}{B}{B}"), costs,
|
||||
MageIdentifier.DemonicEmbraceAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.f;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldAbility;
|
||||
|
|
@ -18,12 +19,12 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
|
|
@ -56,7 +57,10 @@ public final class FalcoSparaPactweaver extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
|
||||
|
||||
// You may cast spells from the top of your library by removing a counter from a creature you control in addition to paying their other costs.
|
||||
this.addAbility(new SimpleStaticAbility(new FalcoSparaPactweaverEffect()));
|
||||
this.addAbility(
|
||||
new SimpleStaticAbility(new FalcoSparaPactweaverEffect())
|
||||
.setIdentifier(MageIdentifier.FalcoSparaPactweaverAlternateCast)
|
||||
);
|
||||
}
|
||||
|
||||
private FalcoSparaPactweaver(final FalcoSparaPactweaver card) {
|
||||
|
|
@ -113,7 +117,10 @@ class FalcoSparaPactweaverEffect extends AsThoughEffectImpl {
|
|||
Costs<Cost> newCosts = new CostsImpl<>();
|
||||
newCosts.add(new RemoveCounterCost(new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent(), true)));
|
||||
newCosts.addAll(cardToCheck.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(cardToCheck.getId(), cardToCheck.getManaCost(), newCosts);
|
||||
player.setCastSourceIdWithAlternateMana(
|
||||
cardToCheck.getId(), cardToCheck.getManaCost(), newCosts,
|
||||
MageIdentifier.FalcoSparaPactweaverAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public final class GisaAndGeralf extends CardImpl {
|
|||
class GisaAndGeralfCastFromGraveyardEffect extends AsThoughEffectImpl {
|
||||
|
||||
GisaAndGeralfCastFromGraveyardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCreatureInPlay, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCreatureInPlay);
|
||||
staticText = "During each of your turns, you may cast a Zombie creature spell from your graveyard";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
package mage.cards.g;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.decorator.ConditionalAsThoughEffect;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.abilities.effects.ReplacementEffectImpl;
|
||||
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
|
||||
import mage.cards.Card;
|
||||
|
|
@ -15,14 +18,12 @@ import mage.game.Game;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.players.Player;
|
||||
import java.util.UUID;
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.decorator.ConditionalAsThoughEffect;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jeffwadsworth
|
||||
|
|
@ -36,11 +37,16 @@ public class GlimpseTheCosmos extends CardImpl {
|
|||
this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect(3, 1, PutCards.HAND, PutCards.BOTTOM_ANY));
|
||||
|
||||
//As long as you control a Giant, you may cast Glimpse the Cosmos from your graveyard by paying {U} rather than paying its mana cost. If you cast Glimpse the Cosmos this way and it would be put into your graveyard, exile it instead.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD,
|
||||
new ConditionalAsThoughEffect(
|
||||
new GlimpseTheCosmosPlayEffect(),
|
||||
new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.GIANT)))).setIdentifier(MageIdentifier.GlimpseTheCosmosWatcher),
|
||||
new GlimpseTheCosmosWatcher());
|
||||
this.addAbility(
|
||||
new SimpleStaticAbility(
|
||||
Zone.GRAVEYARD,
|
||||
new ConditionalAsThoughEffect(
|
||||
new GlimpseTheCosmosPlayEffect(),
|
||||
new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.GIANT))
|
||||
)
|
||||
).setIdentifier(MageIdentifier.GlimpseTheCosmosWatcher),
|
||||
new GlimpseTheCosmosWatcher()
|
||||
);
|
||||
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new GlimpseTheCosmosReplacementEffect()));
|
||||
|
||||
|
|
@ -85,7 +91,10 @@ class GlimpseTheCosmosPlayEffect extends AsThoughEffectImpl {
|
|||
if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) {
|
||||
Player controller = game.getPlayer(affectedControllerId);
|
||||
if (controller != null) {
|
||||
controller.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{U}"), null);
|
||||
controller.setCastSourceIdWithAlternateMana(
|
||||
sourceId, new ManaCostsImpl<>("{U}"), null,
|
||||
MageIdentifier.GlimpseTheCosmosWatcher
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class HaukensInsightLookEffect extends AsThoughEffectImpl {
|
|||
class HaukensInsightPlayEffect extends AsThoughEffectImpl {
|
||||
|
||||
public HaukensInsightPlayEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PlayForFree, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PlayForFree);
|
||||
staticText = "Once during each of your turns, you may play a land or cast a spell from among the cards exiled with this permanent without paying its mana cost";
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ class HaukensInsightPlayEffect extends AsThoughEffectImpl {
|
|||
UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()));
|
||||
ExileZone exileZone = game.getExile().getExileZone(exileId);
|
||||
if (exileZone != null && exileZone.contains(CardUtil.getMainCardId(game, objectId))) {
|
||||
allowCardToPlayWithoutMana(objectId, source, affectedControllerId, game);
|
||||
allowCardToPlayWithoutMana(objectId, source, affectedControllerId, MageIdentifier.HaukensInsightWatcher, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.h;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
|
@ -37,7 +38,9 @@ public final class Helbrute extends CardImpl {
|
|||
this.addAbility(HasteAbility.getInstance());
|
||||
|
||||
// Sarcophagus — You may cast Helbrute from your graveyard by exiling another creature card from your graveyard in addition to paying its other costs.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new HelbruteEffect()).withFlavorWord("Sarcophagus"));
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new HelbruteEffect())
|
||||
.withFlavorWord("Sarcophagus")
|
||||
.setIdentifier(MageIdentifier.HelbruteAlternateCast));
|
||||
}
|
||||
|
||||
private Helbrute(final Helbrute card) {
|
||||
|
|
@ -91,7 +94,10 @@ class HelbruteEffect extends AsThoughEffectImpl {
|
|||
}
|
||||
Costs<Cost> costs = new CostsImpl<>();
|
||||
costs.add(new ExileFromGraveCost(new TargetCardInYourGraveyard(filter)));
|
||||
controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{3}{B}{R}"), costs);
|
||||
controller.setCastSourceIdWithAlternateMana(
|
||||
objectId, new ManaCostsImpl<>("{3}{B}{R}"), costs,
|
||||
MageIdentifier.HelbruteAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ enum JohannApprenticeSorcererHint implements Hint {
|
|||
class JohannApprenticeSorcererPlayTopEffect extends AsThoughEffectImpl {
|
||||
|
||||
public JohannApprenticeSorcererPlayTopEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "Once each turn, you may cast an instant or sorcery spell from the top of your library";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public final class KaghaShadowArchdruid extends CardImpl {
|
|||
class KaghaShadowArchdruidEffect extends AsThoughEffectImpl {
|
||||
|
||||
KaghaShadowArchdruidEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
this.staticText = "Once during each of your turns, you may play a land or cast a permanent spell from among cards in your graveyard that were put there from your library this turn.";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class KaradorGhostChieftainCostReductionEffect extends CostModificationEffectImp
|
|||
class KaradorGhostChieftainCastFromGraveyardEffect extends AsThoughEffectImpl {
|
||||
|
||||
KaradorGhostChieftainCastFromGraveyardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCreatureInPlay, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCreatureInPlay);
|
||||
staticText = "During each of your turns, you may cast a creature spell from your graveyard";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public final class KessDissidentMage extends CardImpl {
|
|||
class KessDissidentMageCastFromGraveyardEffect extends AsThoughEffectImpl {
|
||||
|
||||
KessDissidentMageCastFromGraveyardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "During each of your turns, you may cast an instant or sorcery card from your graveyard";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.m;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
|
@ -31,7 +32,8 @@ public final class MaestrosAscendancy extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}{B}{R}");
|
||||
|
||||
// Once during each of your turns, you may cast an instant or sorcery spell from your graveyard by sacrificing a creature in addition to paying its other costs. If a spell cast this way would be put into your graveyard, exile it instead.
|
||||
Ability ability = new SimpleStaticAbility(new MaestrosAscendancyCastEffect());
|
||||
Ability ability = new SimpleStaticAbility(new MaestrosAscendancyCastEffect())
|
||||
.setIdentifier(MageIdentifier.MaestrosAscendencyAlternateCast);
|
||||
ability.addEffect(new MaestrosAscendancyExileEffect());
|
||||
this.addAbility(ability, new MaestrosAscendancyWatcher());
|
||||
}
|
||||
|
|
@ -87,7 +89,10 @@ class MaestrosAscendancyCastEffect extends AsThoughEffectImpl {
|
|||
Costs<Cost> newCosts = new CostsImpl<>();
|
||||
newCosts.addAll(card.getSpellAbility().getCosts());
|
||||
newCosts.add(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT));
|
||||
player.setCastSourceIdWithAlternateMana(card.getId(), card.getManaCost(), newCosts);
|
||||
player.setCastSourceIdWithAlternateMana(
|
||||
card.getId(), card.getManaCost(), newCosts,
|
||||
MageIdentifier.MaestrosAscendencyAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public final class MuldrothaTheGravetide extends CardImpl {
|
|||
class MuldrothaTheGravetideCastFromGraveyardEffect extends AsThoughEffectImpl {
|
||||
|
||||
public MuldrothaTheGravetideCastFromGraveyardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "During each of your turns, you may play a land and cast a permanent spell of each permanent type from your graveyard. "
|
||||
+ "<i>(If a card has multiple permanent types, choose one as you play it.)</i>";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.n;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -43,7 +44,7 @@ public final class NashiMoonSagesScion extends CardImpl {
|
|||
// Whenever Nashi, Moon Sage's Scion deals combat damage to a player, exile the top card of each player's library. Until end of turn, you may play one of those cards. If you cast a spell this way, pay life equal to its mana value rather than paying its mana cost.
|
||||
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(
|
||||
new NashiMoonSagesScionEffect(), false
|
||||
), new NashiMoonSagesScionWatcher());
|
||||
).setIdentifier(MageIdentifier.NashiMoonSagesScionAlternateCast), new NashiMoonSagesScionWatcher());
|
||||
}
|
||||
|
||||
private NashiMoonSagesScion(final NashiMoonSagesScion card) {
|
||||
|
|
@ -194,7 +195,10 @@ class NashiMoonSagesScionPlayEffect extends CanPlayCardControllerEffect {
|
|||
Costs<Cost> newCosts = new CostsImpl<>();
|
||||
newCosts.add(lifeCost);
|
||||
newCosts.addAll(cardToCheck.getSpellAbility().getCosts());
|
||||
controller.setCastSourceIdWithAlternateMana(cardToCheck.getId(), null, newCosts);
|
||||
controller.setCastSourceIdWithAlternateMana(
|
||||
cardToCheck.getId(), null, newCosts,
|
||||
MageIdentifier.NashiMoonSagesScionAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,18 @@ import mage.MageIdentifier;
|
|||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect;
|
||||
import mage.abilities.effects.common.continuous.PlayTheTopCardEffect;
|
||||
import mage.abilities.hint.common.ConditionPermanentHint;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
import mage.watchers.Watcher;
|
||||
|
|
@ -22,7 +25,7 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
* @author TheElk801, Susucr
|
||||
*/
|
||||
public final class OneWithTheMultiverse extends CardImpl {
|
||||
|
||||
|
|
@ -36,8 +39,15 @@ public final class OneWithTheMultiverse extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect()));
|
||||
|
||||
// Once during each of your turns, you may cast a spell from your hand or the top of your library without paying its mana cost.
|
||||
this.addAbility(new SimpleStaticAbility(new OneWithTheMultiverseEffect())
|
||||
.setIdentifier(MageIdentifier.OneWithTheMultiverseWatcher), new OneWithTheMultiverseWatcher());
|
||||
this.addAbility(
|
||||
new SimpleStaticAbility(new OneWithTheMultiverseEffect())
|
||||
.setIdentifier(MageIdentifier.OneWithTheMultiverseWatcher)
|
||||
.addHint(new ConditionPermanentHint(
|
||||
OneWithTheMultiverseCondition.instance,
|
||||
"Can cast a spell without paying mana cost this turn"
|
||||
)),
|
||||
new OneWithTheMultiverseWatcher()
|
||||
);
|
||||
}
|
||||
|
||||
private OneWithTheMultiverse(final OneWithTheMultiverse card) {
|
||||
|
|
@ -50,10 +60,24 @@ public final class OneWithTheMultiverse extends CardImpl {
|
|||
}
|
||||
}
|
||||
|
||||
enum OneWithTheMultiverseCondition implements Condition {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
OneWithTheMultiverseWatcher watcher = game.getState().getWatcher(OneWithTheMultiverseWatcher.class);
|
||||
Permanent sourceObject = game.getPermanent(source.getSourceId());
|
||||
return watcher != null
|
||||
&& sourceObject != null
|
||||
&& game.isActivePlayer(source.getControllerId())
|
||||
&& !watcher.isAbilityUsed(new MageObjectReference(sourceObject, game));
|
||||
}
|
||||
}
|
||||
|
||||
class OneWithTheMultiverseEffect extends AsThoughEffectImpl {
|
||||
|
||||
public OneWithTheMultiverseEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PlayForFree, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PlayForFree);
|
||||
staticText = "once during each of your turns, you may cast a spell from your hand " +
|
||||
"or the top of your library without paying its mana cost.";
|
||||
}
|
||||
|
|
@ -78,13 +102,14 @@ class OneWithTheMultiverseEffect extends AsThoughEffectImpl {
|
|||
return false;
|
||||
}
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Permanent sourceObject = game.getPermanent(source.getSourceId());
|
||||
Card card = game.getCard(CardUtil.getMainCardId(game, objectId));
|
||||
OneWithTheMultiverseWatcher watcher = game.getState().getWatcher(OneWithTheMultiverseWatcher.class);
|
||||
if (controller == null
|
||||
|| card == null
|
||||
|| watcher == null
|
||||
|| watcher.isAbilityUsed(new MageObjectReference(source))
|
||||
|| !card.isOwnedBy(source.getControllerId())) {
|
||||
|| sourceObject == null
|
||||
|| watcher.isAbilityUsed(new MageObjectReference(sourceObject, game))) {
|
||||
return false;
|
||||
}
|
||||
Zone zone = game.getState().getZone(card.getId());
|
||||
|
|
@ -92,7 +117,8 @@ class OneWithTheMultiverseEffect extends AsThoughEffectImpl {
|
|||
(!Zone.LIBRARY.match(zone) || !controller.getLibrary().getFromTop(game).getId().equals(card.getId()))) {
|
||||
return false;
|
||||
}
|
||||
allowCardToPlayWithoutMana(objectId, source, affectedControllerId, game);
|
||||
|
||||
allowCardToPlayWithoutMana(objectId, source, affectedControllerId, MageIdentifier.OneWithTheMultiverseWatcher, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
|
|
@ -39,8 +40,9 @@ public final class RaffinesGuidance extends CardImpl {
|
|||
// Enchanted creature gets +1/+1.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 1, Duration.WhileOnBattlefield)));
|
||||
|
||||
// You may cast Raffine’s Guidance from your graveyard by paying {2}{W} instead of its mana cost.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new RafinnesGuidancePlayEffect()));
|
||||
// You may cast Raffine's Guidance from your graveyard by paying {2}{W} instead of its mana cost.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new RafinnesGuidancePlayEffect())
|
||||
.setIdentifier(MageIdentifier.RafinnesGuidanceAlternateCast));
|
||||
}
|
||||
|
||||
private RaffinesGuidance(final RaffinesGuidance card) {
|
||||
|
|
@ -71,7 +73,10 @@ class RafinnesGuidancePlayEffect extends AsThoughEffectImpl {
|
|||
Player player = game.getPlayer(affectedControllerId);
|
||||
if (player != null) {
|
||||
Costs<Cost> costs = new CostsImpl<>();
|
||||
player.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{2}{W}"), costs);
|
||||
player.setCastSourceIdWithAlternateMana(
|
||||
sourceId, new ManaCostsImpl<>("{2}{W}"), costs,
|
||||
MageIdentifier.RafinnesGuidanceAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.CantBlockAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.abilities.effects.common.continuous.BoostControlledEffect;
|
||||
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
|
||||
|
|
@ -23,7 +27,7 @@ import mage.util.CardUtil;
|
|||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
* @author LevelX2, Susucr
|
||||
*/
|
||||
public final class RisenExecutioner extends CardImpl {
|
||||
|
||||
|
|
@ -47,9 +51,8 @@ public final class RisenExecutioner extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, filter, true)));
|
||||
|
||||
// You may cast Risen Executioner from your graveyard if you pay {1} more to cast it for each other creature card in your graveyard.
|
||||
// TODO: cost increase does not happen if Risen Executioner is cast grom graveyard because of other effects
|
||||
Ability ability = new SimpleStaticAbility(Zone.ALL, new RisenExecutionerCastEffect());
|
||||
ability.addEffect(new RisenExecutionerCostIncreasingEffect());
|
||||
Ability ability = new SimpleStaticAbility(Zone.ALL, new RisenExecutionerCastEffect())
|
||||
.setIdentifier(MageIdentifier.RisenExectutionerAlternateCast);
|
||||
this.addAbility(ability);
|
||||
|
||||
}
|
||||
|
|
@ -66,6 +69,12 @@ public final class RisenExecutioner extends CardImpl {
|
|||
|
||||
class RisenExecutionerCastEffect extends AsThoughEffectImpl {
|
||||
|
||||
protected static final FilterCreatureCard filter = new FilterCreatureCard();
|
||||
|
||||
static {
|
||||
filter.add(AnotherPredicate.instance);
|
||||
}
|
||||
|
||||
RisenExecutionerCastEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit);
|
||||
staticText = "You may cast {this} from your graveyard if you pay {1} more to cast it for each other creature card in your graveyard";
|
||||
|
|
@ -87,56 +96,23 @@ class RisenExecutionerCastEffect extends AsThoughEffectImpl {
|
|||
|
||||
@Override
|
||||
public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) {
|
||||
if (sourceId.equals(source.getSourceId())) {
|
||||
Card card = game.getCard(source.getSourceId());
|
||||
if (card != null
|
||||
&& card.isOwnedBy(affectedControllerId)
|
||||
&& game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) {
|
||||
return true;
|
||||
}
|
||||
if (!sourceId.equals(source.getSourceId())) {
|
||||
return false;
|
||||
}
|
||||
Card card = game.getCard(source.getSourceId());
|
||||
if(card == null
|
||||
|| !card.isOwnedBy(affectedControllerId)
|
||||
|| game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class RisenExecutionerCostIncreasingEffect extends CostModificationEffectImpl {
|
||||
|
||||
protected static final FilterCreatureCard filter = new FilterCreatureCard();
|
||||
|
||||
static {
|
||||
filter.add(AnotherPredicate.instance);
|
||||
}
|
||||
|
||||
RisenExecutionerCostIncreasingEffect() {
|
||||
super(Duration.EndOfGame, Outcome.Benefit, CostModificationType.INCREASE_COST);
|
||||
staticText = "";
|
||||
}
|
||||
|
||||
private RisenExecutionerCostIncreasingEffect(final RisenExecutionerCostIncreasingEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source, Ability abilityToModify) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
CardUtil.increaseCost(abilityToModify, controller.getGraveyard().count(filter, source.getControllerId(), source, game));
|
||||
if(controller == null) {
|
||||
return false;
|
||||
}
|
||||
int costIncrease = controller.getGraveyard().count(filter, source.getControllerId(), source, game);
|
||||
ManaCosts<ManaCost> adjustedCost = CardUtil.adjustCost(card.getSpellAbility().getManaCostsToPay(), -costIncrease);
|
||||
controller.setCastSourceIdWithAlternateMana(card.getId(), adjustedCost, null, MageIdentifier.RisenExectutionerAlternateCast);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Ability abilityToModify, Ability source, Game game) {
|
||||
if (abilityToModify.getSourceId().equals(source.getSourceId())) {
|
||||
Spell spell = game.getStack().getSpell(abilityToModify.getSourceId());
|
||||
return spell != null && spell.getFromZone() == Zone.GRAVEYARD;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RisenExecutionerCostIncreasingEffect copy() {
|
||||
return new RisenExecutionerCostIncreasingEffect(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.r;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
|
@ -42,7 +43,8 @@ public final class RonaSheoldredsFaithful extends CardImpl {
|
|||
));
|
||||
|
||||
// You may cast Rona, Sheoldred's Faithful from your graveyard by discarding two cards in addition to paying its other costs.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new RonaSheoldredsFaithfulEffect()));
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new RonaSheoldredsFaithfulEffect())
|
||||
.setIdentifier(MageIdentifier.RonaSheoldredsFaithfulAlternateCast));
|
||||
}
|
||||
|
||||
private RonaSheoldredsFaithful(final RonaSheoldredsFaithful card) {
|
||||
|
|
@ -90,7 +92,10 @@ class RonaSheoldredsFaithfulEffect extends AsThoughEffectImpl {
|
|||
}
|
||||
Costs<Cost> costs = new CostsImpl<>();
|
||||
costs.add(new DiscardTargetCost(new TargetCardInHand(2, StaticFilters.FILTER_CARD_CARDS)));
|
||||
controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{1}{U}{B}{B}"), costs);
|
||||
controller.setCastSourceIdWithAlternateMana(
|
||||
objectId, new ManaCostsImpl<>("{1}{U}{B}{B}"), costs,
|
||||
MageIdentifier.RonaSheoldredsFaithfulAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
package mage.cards.s;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
|
|
@ -14,16 +14,13 @@ import mage.abilities.effects.AsThoughEffectImpl;
|
|||
import mage.abilities.keyword.FlyingAbility;
|
||||
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.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
|
|
@ -40,7 +37,8 @@ public final class ScourgeOfNelToth extends CardImpl {
|
|||
// Flying
|
||||
this.addAbility(FlyingAbility.getInstance());
|
||||
// You may cast Scourge of Nel Toth from your graveyard by paying {B}{B} and sacrificing two creatures rather than paying its mana cost.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new ScourgeOfNelTothPlayEffect()));
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new ScourgeOfNelTothPlayEffect())
|
||||
.setIdentifier(MageIdentifier.ScourgeOfNelTothAlternateCast));
|
||||
}
|
||||
|
||||
private ScourgeOfNelToth(final ScourgeOfNelToth card) {
|
||||
|
|
@ -80,10 +78,12 @@ class ScourgeOfNelTothPlayEffect extends AsThoughEffectImpl {
|
|||
if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) {
|
||||
Player player = game.getPlayer(affectedControllerId);
|
||||
if (player != null) {
|
||||
// can sometimes be cast with base mana cost from grave????
|
||||
Costs<Cost> costs = new CostsImpl<>();
|
||||
costs.add(new SacrificeTargetCost(new TargetControlledCreaturePermanent(2)));
|
||||
player.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{B}{B}"), costs);
|
||||
player.setCastSourceIdWithAlternateMana(
|
||||
sourceId, new ManaCostsImpl<>("{B}{B}"), costs,
|
||||
MageIdentifier.ScourgeOfNelTothAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.s;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksTriggeredAbility;
|
||||
|
|
@ -47,7 +48,8 @@ public final class SqueeDubiousMonarch extends CardImpl {
|
|||
)));
|
||||
|
||||
// You may cast Squee, Dubious Monarch from your graveyard by paying {3}{R} and exiling four other cards from your graveyard rather than paying its mana cost.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SqueeDubiousMonarchEffect()));
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SqueeDubiousMonarchEffect())
|
||||
.setIdentifier(MageIdentifier.SqueeDubiousMonarchAlternateCast));
|
||||
}
|
||||
|
||||
private SqueeDubiousMonarch(final SqueeDubiousMonarch card) {
|
||||
|
|
@ -101,7 +103,10 @@ class SqueeDubiousMonarchEffect extends AsThoughEffectImpl {
|
|||
}
|
||||
Costs<Cost> costs = new CostsImpl<>();
|
||||
costs.add(new ExileFromGraveCost(new TargetCardInYourGraveyard(4, filter)));
|
||||
controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{3}{R}"), costs);
|
||||
controller.setCastSourceIdWithAlternateMana(
|
||||
objectId, new ManaCostsImpl<>("{3}{R}"), costs,
|
||||
MageIdentifier.SqueeDubiousMonarchAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class CantBeBlockedUnlessAllEffect extends RestrictionEffect {
|
|||
// check if all creatures of defender are able to block this permanent
|
||||
// permanent.canBlock() can't be used because causing recursive call
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, blocker.getControllerId(), game)) {
|
||||
if (permanent.isTapped() && null == game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, blocker.getControllerId(), game)) {
|
||||
if (permanent.isTapped() && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, blocker.getControllerId(), game).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// check blocker restrictions
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.w;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.condition.common.IsStepCondition;
|
||||
|
|
@ -63,7 +64,7 @@ class WellOfKnowledgeConditionalActivatedAbility extends ActivatedAbilityImpl {
|
|||
&& getCosts().canPay(this, this, playerId, game)
|
||||
&& game.isActivePlayer(playerId)) {
|
||||
this.activatorId = playerId;
|
||||
return ActivationStatus.getTrue(this, game);
|
||||
return new ActivationStatus(new ApprovingObject(this, game));
|
||||
}
|
||||
return ActivationStatus.getFalse();
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class WishEffect extends OneShotEffect {
|
|||
class WishPlayFromSideboardEffect extends AsThoughEffectImpl {
|
||||
|
||||
public WishPlayFromSideboardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit);
|
||||
}
|
||||
|
||||
private WishPlayFromSideboardEffect(final WishPlayFromSideboardEffect effect) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
package mage.cards.w;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
|
|
@ -13,17 +13,14 @@ import mage.abilities.effects.OneShotEffect;
|
|||
import mage.abilities.keyword.FlyingAbility;
|
||||
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.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
|
|
@ -41,7 +38,8 @@ public final class WorldheartPhoenix extends CardImpl {
|
|||
|
||||
// You may cast Worldheart Phoenix from your graveyard by paying {W}{U}{B}{R}{G} rather than paying its mana cost.
|
||||
// If you do, it enters the battlefield with two +1/+1 counters on it.
|
||||
Ability ability = new SimpleStaticAbility(Zone.ALL, new WorldheartPhoenixPlayEffect());
|
||||
Ability ability = new SimpleStaticAbility(Zone.ALL, new WorldheartPhoenixPlayEffect())
|
||||
.setIdentifier(MageIdentifier.WorldheartPhoenixAlternateCast);
|
||||
ability.addEffect(new EntersBattlefieldEffect(new WorldheartPhoenixEntersBattlefieldEffect(),
|
||||
"If you do, it enters the battlefield with two +1/+1 counters on it"));
|
||||
this.addAbility(ability);
|
||||
|
|
@ -84,8 +82,10 @@ public final class WorldheartPhoenix extends CardImpl {
|
|||
if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) {
|
||||
Player player = game.getPlayer(affectedControllerId);
|
||||
if (player != null) {
|
||||
// can sometimes be cast with base mana cost from grave????
|
||||
player.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), null);
|
||||
player.setCastSourceIdWithAlternateMana(
|
||||
sourceId, new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), null,
|
||||
MageIdentifier.WorldheartPhoenixAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards.x;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
|
|
@ -36,6 +37,7 @@ public final class XandersPact extends CardImpl {
|
|||
|
||||
// Each opponent exiles the top card of their library. You may cast spells from among those cards this turn. If you cast a spell this way, pay life equal to that spell's mana value rather than pay its mana cost.
|
||||
this.getSpellAbility().addEffect(new XandersPactExileEffect());
|
||||
this.getSpellAbility().setIdentifier(MageIdentifier.XandersPactAlternateCast);
|
||||
}
|
||||
|
||||
private XandersPact(final XandersPact card) {
|
||||
|
|
@ -120,7 +122,10 @@ class XandersPactCastEffect extends CanPlayCardControllerEffect {
|
|||
Costs<Cost> newCosts = new CostsImpl<>();
|
||||
newCosts.add(new PayLifeCost(cardToCheck.getManaValue()));
|
||||
newCosts.addAll(cardToCheck.getSpellAbility().getCosts());
|
||||
controller.setCastSourceIdWithAlternateMana(cardToCheck.getId(), null, newCosts);
|
||||
controller.setCastSourceIdWithAlternateMana(
|
||||
cardToCheck.getId(), null, newCosts,
|
||||
MageIdentifier.XandersPactAlternateCast
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package org.mage.test.cards.abilities.other;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author Alex-Vasile, Susucr
|
||||
*/
|
||||
public class MultipleAsThoughEffects extends CardTestPlayerBase {
|
||||
|
||||
/**
|
||||
* Reported bug: https://github.com/magefree/mage/issues/8584
|
||||
*
|
||||
* If there are multiple effects which allow a player to cast a spell,
|
||||
* they should be able to choose which one they whish to use.
|
||||
*/
|
||||
@Test
|
||||
public void ChoosingAlternateCastingMethod() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// You may cast creature spells from the top of your library.
|
||||
addCard(Zone.HAND, playerA, "Vivien, Monsters' Advocate");
|
||||
// You may play lands and cast spells from the top of your library.
|
||||
// If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel");
|
||||
// Random creature card to play with mana value of 3
|
||||
addCard(Zone.LIBRARY, playerA, "Abzan Beastmaster",2);
|
||||
addCard(Zone.LIBRARY, playerA, "Grizzly Bears",1); // This one is drawn.
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest",5);
|
||||
// For the "cast from the top" abilities to work, Vivien or Bolas's Citadel
|
||||
// must be played and not be in battlefield as start. Or else the top of the library will
|
||||
// not be able to be cast during the test.
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vivien, Monsters' Advocate");
|
||||
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Abzan Beastmaster",true);
|
||||
setChoice(playerA, "Vivien");
|
||||
checkLife("Vivien not making pay life", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20);
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Abzan Beastmaster");
|
||||
setChoice(playerA, "Bolas's");
|
||||
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Abzan Beastmaster", 2);
|
||||
assertTappedCount("Forest", true, 3);
|
||||
assertLife(playerA, 20 - 3); // 3 from casting Abzan Beastmaster with Bolas Citadel
|
||||
}
|
||||
|
||||
/**
|
||||
* Reported bug: https://github.com/magefree/mage/issues/2087
|
||||
*
|
||||
* If there are multiple effects which allow a player to cast a spell,
|
||||
* they should be able to choose which one they whish to use, even if one is single-use.
|
||||
*/
|
||||
@Test
|
||||
public void RisenExecutioner() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
// You may cast Risen Executioner from your graveyard if you pay {1} more to cast it for each other creature card in your graveyard.
|
||||
addCard(Zone.GRAVEYARD, playerA, "Risen Executioner", 2);
|
||||
addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears", 1);
|
||||
// During each of your turns, you may cast a Zombie creature spell from your graveyard.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Gisa and Geralf");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp",9); // Only enough mana to cast
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Risen Executioner", true); // Should cost {2}{B}{B} since cast with Gisa
|
||||
setChoice(playerA, "Gisa");
|
||||
checkPermanentTapped("Swamp tapped after cast with Gisa and Geralf", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", true, 4);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Risen Executioner"); // Should cost {3}{B}{B} when cast with own ability, there is another creature in the graveyard
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Risen Executioner", 2);
|
||||
assertTappedCount("Swamp", true, 4 + 5);
|
||||
}
|
||||
}
|
||||
|
|
@ -734,6 +734,7 @@ public class AdventureCardsTest extends CardTestPlayerBase {
|
|||
checkExileCount("after exile 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Curious Pair", 1);
|
||||
// play as adventure spell
|
||||
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Treats to Share");
|
||||
setChoice(playerA, "Hostage Taker"); // Not sure why there is an alternative there. No issue with using either. TODO: investigate?
|
||||
waitStackResolved(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
checkPermanentCount("after play 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Food Token", 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -96,11 +96,12 @@ public class KaradorGhostChieftainTest extends CardTestPlayerBase {
|
|||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
|
||||
//
|
||||
// {1}{B}: Target attacking Zombie gains indestructible until end of turn.
|
||||
addCard(Zone.LIBRARY, playerA, "Accursed Horde", 1); // Creature Zombie {3}{B}
|
||||
addCard(Zone.LIBRARY, playerA, "Carrion Screecher", 1); // Creature Zombie {3}{B}
|
||||
//
|
||||
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 5); // Creature {1}{W}
|
||||
//
|
||||
|
|
@ -119,15 +120,64 @@ public class KaradorGhostChieftainTest extends CardTestPlayerBase {
|
|||
// you play any creatures due to two approve objects
|
||||
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", true);
|
||||
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", true);
|
||||
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Carrion Screecher", true);
|
||||
|
||||
// cast zombie creature and approves by Karagar
|
||||
// cast zombie creature and approves by Karador
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Accursed Horde");
|
||||
setChoice(playerA, "Karador, Ghost Chieftain"); // choose the permitting object
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// you can't cast lion due to approving object (Gisa needs zombie)
|
||||
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false);
|
||||
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", false);
|
||||
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Carrion Screecher", true);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_castFromGraveyardWithDifferentApproversOtherCast() {
|
||||
skipInitShuffling();
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
|
||||
//
|
||||
// {1}{B}: Target attacking Zombie gains indestructible until end of turn.
|
||||
addCard(Zone.LIBRARY, playerA, "Accursed Horde", 1); // Creature Zombie {3}{B}
|
||||
addCard(Zone.LIBRARY, playerA, "Carrion Screecher", 1); // Creature Zombie {3}{B}
|
||||
//
|
||||
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 5); // Creature {1}{W}
|
||||
//
|
||||
// Karador, Ghost Chieftain costs {1} less to cast for each creature card in your graveyard.
|
||||
// During each of your turns, you may cast one creature card from your graveyard.
|
||||
addCard(Zone.HAND, playerA, "Karador, Ghost Chieftain");// {5}{B}{G}{W}
|
||||
//
|
||||
// When Gisa and Geralf enters the battlefield, put the top four cards of your library into your graveyard.
|
||||
// During each of your turns, you may cast a Zombie creature card from your graveyard.
|
||||
addCard(Zone.HAND, playerA, "Gisa and Geralf"); // CREATURE {2}{U}{B} (4/4)
|
||||
|
||||
// prepare spels with same AsThough effects and puts creature to graveyard
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Karador, Ghost Chieftain", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gisa and Geralf");
|
||||
|
||||
// you play any creatures due to two approve objects
|
||||
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", true);
|
||||
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", true);
|
||||
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Carrion Screecher", true);
|
||||
|
||||
// cast zombie creature and approves by Gisa and Geralf
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Accursed Horde");
|
||||
setChoice(playerA, "Gisa and Geralf"); // choose the permitting object
|
||||
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
// you can't cast lion due to approving object (Gisa needs zombie)
|
||||
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", true);
|
||||
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", false);
|
||||
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Carrion Screecher", true);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,370 @@
|
|||
package org.mage.test.cards.single.bro;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public class OneWithTheMultiverseTest extends CardTestPlayerBase {
|
||||
|
||||
/** One with the Multiverse
|
||||
* {6}{U}{U}
|
||||
* Enchantment
|
||||
*
|
||||
* You may look at the top card of your library any time.
|
||||
* You may play lands and cast spells from the top of your library.
|
||||
* Once during each of your turns, you may cast a spell from your hand or the top of your library without paying its mana cost.
|
||||
*/
|
||||
private final String owtm = "One with the Multiverse";
|
||||
private final String ogre = "Gray Ogre"; // 2/2 {2}{R}
|
||||
private final String piker = "Goblin Piker"; // 2/1 {1}{R}
|
||||
|
||||
@Test
|
||||
public void castFromTopForFree() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 8);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 1);
|
||||
assertPermanentCount(playerA, piker, 0);
|
||||
assertLibraryCount(playerA, ogre, 2);
|
||||
assertHandCount(playerA, piker, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castFromHandForFree() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 8);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 0);
|
||||
assertPermanentCount(playerA, piker, 1);
|
||||
assertLibraryCount(playerA, ogre, 3);
|
||||
assertHandCount(playerA, piker, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castFromTopForFreeThenNormalFromTop() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 2);
|
||||
assertPermanentCount(playerA, piker, 0);
|
||||
assertLibraryCount(playerA, ogre, 1);
|
||||
assertHandCount(playerA, piker, 2);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castFromTopForFreeThenNormalFromHand() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 1);
|
||||
assertPermanentCount(playerA, piker, 1);
|
||||
assertLibraryCount(playerA, ogre, 2);
|
||||
assertHandCount(playerA, piker, 1);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castFromHandForFreeThenNormalFromHand() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 0);
|
||||
assertPermanentCount(playerA, piker, 2);
|
||||
assertLibraryCount(playerA, ogre, 3);
|
||||
assertHandCount(playerA, piker, 0);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castFromHandForFreeThenNormalFromTop() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 1);
|
||||
assertPermanentCount(playerA, piker, 1);
|
||||
assertLibraryCount(playerA, ogre, 2);
|
||||
assertHandCount(playerA, piker, 1);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castNormalFromTopThenFreeFromHand() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
setChoice(playerA, owtm);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 1);
|
||||
assertPermanentCount(playerA, piker, 1);
|
||||
assertLibraryCount(playerA, ogre, 2);
|
||||
assertHandCount(playerA, piker, 1);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castNormalFromTopThenFreeFromTop() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
setChoice(playerA, owtm);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 2);
|
||||
assertPermanentCount(playerA, piker, 0);
|
||||
assertLibraryCount(playerA, ogre, 1);
|
||||
assertHandCount(playerA, piker, 2);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castNormalFromHandThenFreeFromHand() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
setChoice(playerA, piker);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 0);
|
||||
assertPermanentCount(playerA, piker, 2);
|
||||
assertLibraryCount(playerA, ogre, 3);
|
||||
assertHandCount(playerA, piker, 0);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castNormalFromHandThenFreeFromTop() {
|
||||
setStrictChooseMode(true);
|
||||
skipInitShuffling();
|
||||
|
||||
// The "You may look at the top card of your library any time."
|
||||
// is not set up properly if starting directly on the battlefield.
|
||||
// So we do cast it in those tests.
|
||||
addCard(Zone.HAND, playerA, owtm);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 11);
|
||||
|
||||
addCard(Zone.LIBRARY, playerA, ogre, 3);
|
||||
addCard(Zone.HAND, playerA, piker, 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, owtm, true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, piker, true);
|
||||
setChoice(playerA, piker);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", true);
|
||||
checkPlayableAbility("can cast for free", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ogre, true);
|
||||
setChoice(playerA, "Without paying manacost");
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Gray Ogre", false);
|
||||
checkPlayableAbility("can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Goblin Piker", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, ogre, 1);
|
||||
assertPermanentCount(playerA, piker, 1);
|
||||
assertLibraryCount(playerA, ogre, 2);
|
||||
assertHandCount(playerA, piker, 1);
|
||||
assertTappedCount("Volcanic Island", true, 8 + 2);
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ public class ValkiGodOfLiesTest extends CardTestPlayerBase {
|
|||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2: Exile the top card of each player's library.");
|
||||
playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Plains");
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Ephemerate", "Grizzly Bears");
|
||||
setChoice(playerA, "Emblem Tibalt");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
|
|
|
|||
|
|
@ -3161,22 +3161,22 @@ public class TestPlayer implements Player {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs) {
|
||||
computerPlayer.setCastSourceIdWithAlternateMana(sourceId, manaCosts, costs);
|
||||
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs, MageIdentifier identifier) {
|
||||
computerPlayer.setCastSourceIdWithAlternateMana(sourceId, manaCosts, costs, identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getCastSourceIdWithAlternateMana() {
|
||||
public Map<UUID, Set<MageIdentifier>> getCastSourceIdWithAlternateMana() {
|
||||
return computerPlayer.getCastSourceIdWithAlternateMana();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() {
|
||||
public Map<UUID, Map<MageIdentifier,ManaCosts<ManaCost>>> getCastSourceIdManaCosts() {
|
||||
return computerPlayer.getCastSourceIdManaCosts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, Costs<Cost>> getCastSourceIdCosts() {
|
||||
public Map<UUID, Map<MageIdentifier,Costs<Cost>>> getCastSourceIdCosts() {
|
||||
return computerPlayer.getCastSourceIdCosts();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.mage.test.stub;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.abilities.*;
|
||||
|
|
@ -1270,22 +1271,22 @@ public class PlayerStub implements Player {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getCastSourceIdWithAlternateMana() {
|
||||
public Map<UUID, Set<MageIdentifier>> getCastSourceIdWithAlternateMana() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) {
|
||||
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs, MageIdentifier identifier) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, Costs<Cost>> getCastSourceIdCosts() {
|
||||
public Map<UUID, Map<MageIdentifier,Costs<Cost>>> getCastSourceIdCosts() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() {
|
||||
public Map<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> getCastSourceIdManaCosts() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,21 @@ package mage;
|
|||
* Used to identify specific actions/events and to be able to assign them to the
|
||||
* correct watcher or other processing.
|
||||
*
|
||||
* @author LevelX2
|
||||
* @author LevelX2, Susucr
|
||||
*/
|
||||
public enum MageIdentifier {
|
||||
// No special behavior. Cleaner than null as a default.
|
||||
Default,
|
||||
|
||||
// -------------------------------- //
|
||||
// spell cast watchers //
|
||||
// -------------------------------- //
|
||||
//
|
||||
// All those are used by a watcher to track spells cast using a matching MageIdentifier way.
|
||||
//
|
||||
// e.g. [[Johann, Apprentice Sorcerer]]
|
||||
// "Once each turn, you may cast an instant or sorcery spell from the top of your library."
|
||||
//
|
||||
CastFromGraveyardOnceWatcher,
|
||||
CemeteryIlluminatorWatcher,
|
||||
GisaAndGeralfWatcher,
|
||||
|
|
@ -19,7 +31,65 @@ public enum MageIdentifier {
|
|||
WishWatcher,
|
||||
GlimpseTheCosmosWatcher,
|
||||
SerraParagonWatcher,
|
||||
OneWithTheMultiverseWatcher,
|
||||
OneWithTheMultiverseWatcher("Without paying manacost"),
|
||||
JohannApprenticeSorcererWatcher,
|
||||
KaghaShadowArchdruidWatcher
|
||||
KaghaShadowArchdruidWatcher,
|
||||
CourtOfLocthwainWatcher("Without paying manacost"),
|
||||
|
||||
// ----------------------------//
|
||||
// alternate casts //
|
||||
// ----------------------------//
|
||||
//
|
||||
// All those are used to link (cost) modification only when cast
|
||||
// using an AsThough with the matching MageIdentifier.
|
||||
//
|
||||
// e.g. [[Bolas's Citadel]]
|
||||
// """
|
||||
// You may look at the top card of your library any time.
|
||||
//
|
||||
// You may play lands and cast spells from the top of your library.
|
||||
// If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost.
|
||||
// """
|
||||
//
|
||||
// If there are other ways to cast from the top of the library, then the MageIdentifier being different
|
||||
// means that the alternate cast won't apply to the other ways to cast.
|
||||
BolassCitadelAlternateCast,
|
||||
RisenExectutionerAlternateCast,
|
||||
DemilichAlternateCast,
|
||||
DemonicEmbraceAlternateCast,
|
||||
FalcoSparaPactweaverAlternateCast,
|
||||
HelbruteAlternateCast,
|
||||
MaestrosAscendencyAlternateCast,
|
||||
NashiMoonSagesScionAlternateCast,
|
||||
RafinnesGuidanceAlternateCast,
|
||||
RonaSheoldredsFaithfulAlternateCast,
|
||||
ScourgeOfNelTothAlternateCast,
|
||||
SqueeDubiousMonarchAlternateCast,
|
||||
WorldheartPhoenixAlternateCast,
|
||||
XandersPactAlternateCast;
|
||||
|
||||
/**
|
||||
* Additional text if there is need to differentiate two very similar effects
|
||||
* from the same source in the UI.
|
||||
* See [[Court of Lochtwain]] for an example.
|
||||
* """
|
||||
* At the beginning of your upkeep, exile the top card of target opponent’s library.
|
||||
* You may play that card for as long as it remains exiled, and mana of any type can be spent to cast it.
|
||||
* If you're the monarch, until end of turn, you may cast a spell from among cards exiled with
|
||||
* Court of Locthwain without paying its mana cost.
|
||||
* """
|
||||
*/
|
||||
private final String additionalText;
|
||||
|
||||
MageIdentifier() {
|
||||
this("");
|
||||
}
|
||||
|
||||
MageIdentifier(String additionalText) {
|
||||
this.additionalText = additionalText;
|
||||
}
|
||||
|
||||
public String getAdditionalText() {
|
||||
return this.additionalText;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
protected List<Hint> hints = new ArrayList<>();
|
||||
protected List<CardIcon> icons = new ArrayList<>();
|
||||
protected Outcome customOutcome = null; // uses for AI decisions instead effects
|
||||
protected MageIdentifier identifier; // used to identify specific ability (e.g. to match with corresponding watcher)
|
||||
protected MageIdentifier identifier = MageIdentifier.Default; // used to identify specific ability (e.g. to match with corresponding watcher)
|
||||
protected String appendToRule = null;
|
||||
protected int sourcePermanentTransformCount = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,41 +7,57 @@ import mage.constants.TargetController;
|
|||
import mage.constants.TimingRule;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @author BetaSteward_at_googlemail.com, Susucr
|
||||
*/
|
||||
public interface ActivatedAbility extends Ability {
|
||||
|
||||
final class ActivationStatus {
|
||||
|
||||
private final boolean canActivate;
|
||||
private final ApprovingObject approvingObject;
|
||||
// Expected to not be modified after creation.
|
||||
private final Set<ApprovingObject> approvingObjects;
|
||||
|
||||
public ActivationStatus(boolean canActivate, ApprovingObject approvingObject) {
|
||||
this.canActivate = canActivate;
|
||||
this.approvingObject = approvingObject;
|
||||
// If true, the Activation Status will not check if there is an approvingObject.
|
||||
private final boolean forcedCanActivate;
|
||||
|
||||
public ActivationStatus(ApprovingObject approvingObject) {
|
||||
this.forcedCanActivate = false;
|
||||
this.approvingObjects = Collections.singleton(approvingObject);
|
||||
}
|
||||
|
||||
public ActivationStatus(Set<ApprovingObject> approvingObjects) {
|
||||
this(false, approvingObjects);
|
||||
}
|
||||
|
||||
private ActivationStatus(boolean forcedCanActivate, Set<ApprovingObject> approvingObjects) {
|
||||
this.forcedCanActivate = forcedCanActivate;
|
||||
this.approvingObjects = new HashSet<>();
|
||||
this.approvingObjects.addAll(approvingObjects);
|
||||
}
|
||||
|
||||
public boolean canActivate() {
|
||||
return canActivate;
|
||||
}
|
||||
|
||||
public ApprovingObject getApprovingObject() {
|
||||
return approvingObject;
|
||||
}
|
||||
|
||||
public static ActivationStatus getFalse() {
|
||||
return new ActivationStatus(false, null);
|
||||
return forcedCanActivate || !approvingObjects.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param approvingObjectAbility ability that allows to activate/use current ability
|
||||
* @return the set of all approving objects for that ActivationStatus.
|
||||
* That Set is readonly in spirit, as there might be different parts
|
||||
* of the engine retrieving info from it.
|
||||
*/
|
||||
public static ActivationStatus getTrue(Ability approvingObjectAbility, Game game) {
|
||||
ApprovingObject approvingObject = approvingObjectAbility == null ? null : new ApprovingObject(approvingObjectAbility, game);
|
||||
return new ActivationStatus(true, approvingObject);
|
||||
public Set<ApprovingObject> getApprovingObjects() {
|
||||
return approvingObjects;
|
||||
}
|
||||
|
||||
private static final ActivationStatus falseInstance = new ActivationStatus(Collections.emptySet());
|
||||
|
||||
public static ActivationStatus getFalse() {
|
||||
return falseInstance;
|
||||
}
|
||||
|
||||
public static ActivationStatus withoutApprovingObject(boolean forcedCanActivate) {
|
||||
return new ActivationStatus(forcedCanActivate, Collections.emptySet());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import mage.game.permanent.Permanent;
|
|||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -179,15 +180,16 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
|
|||
|
||||
// timing check
|
||||
//20091005 - 602.5d/602.5e
|
||||
boolean asInstant;
|
||||
ApprovingObject approvingObject = game.getContinuousEffects()
|
||||
Set<ApprovingObject> approvingObjects = game
|
||||
.getContinuousEffects()
|
||||
.asThough(sourceId,
|
||||
AsThoughEffectType.ACTIVATE_AS_INSTANT,
|
||||
this,
|
||||
controllerId,
|
||||
game);
|
||||
asInstant = approvingObject != null;
|
||||
asInstant |= (timing == TimingRule.INSTANT);
|
||||
game
|
||||
);
|
||||
boolean asInstant = !approvingObjects.isEmpty()
|
||||
|| (timing == TimingRule.INSTANT);
|
||||
if (!asInstant && !game.canPlaySorcery(playerId)) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
|
|
@ -204,7 +206,13 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
|
|||
// game.inCheckPlayableState() can't be a help here cause some cards checking activating status,
|
||||
// activatorId must be removed
|
||||
this.activatorId = playerId;
|
||||
return new ActivationStatus(true, approvingObject);
|
||||
|
||||
if (approvingObjects.isEmpty()) {
|
||||
return ActivationStatus.withoutApprovingObject(true);
|
||||
}
|
||||
else {
|
||||
return new ActivationStatus(approvingObjects);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package mage.abilities;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -33,15 +35,35 @@ public class PlayLandAbility extends ActivatedAbilityImpl {
|
|||
|
||||
// no super.canActivate() call
|
||||
|
||||
ApprovingObject approvingObject = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
if (!controlsAbility(playerId, game) && null == approvingObject) {
|
||||
Set<ApprovingObject> approvingObjects = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
if (!controlsAbility(playerId, game) && approvingObjects.isEmpty()) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
|
||||
//20091005 - 114.2a
|
||||
return new ActivationStatus(game.isActivePlayer(playerId)
|
||||
&& game.getPlayer(playerId).canPlayLand()
|
||||
&& game.canPlaySorcery(playerId),
|
||||
approvingObject);
|
||||
if(!game.isActivePlayer(playerId)
|
||||
|| !game.getPlayer(playerId).canPlayLand()
|
||||
|| !game.canPlaySorcery(playerId)) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
|
||||
// TODO: this check may not be required, but removing it require more investigation.
|
||||
// As of now it is only a way for One with the Multiverse to work.
|
||||
if (!approvingObjects.isEmpty()) {
|
||||
Card card = game.getCard(sourceId);
|
||||
Zone zone = game.getState().getZone(sourceId);
|
||||
if(card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) {
|
||||
// Regular casting, to be an alternative to the AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE from hand (e.g. One with the Multiverse):
|
||||
approvingObjects.add(new ApprovingObject(this, game));
|
||||
}
|
||||
}
|
||||
|
||||
if(approvingObjects.isEmpty()) {
|
||||
return ActivationStatus.withoutApprovingObject(true);
|
||||
}
|
||||
else {
|
||||
return new ActivationStatus(approvingObjects);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.VariableCost;
|
||||
|
|
@ -45,6 +46,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
this.spellAbilityType = spellAbilityType;
|
||||
this.spellAbilityCastMode = spellAbilityCastMode;
|
||||
this.addManaCost(cost);
|
||||
this.setIdentifier(MageIdentifier.Default);
|
||||
setSpellName();
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +99,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
}
|
||||
}
|
||||
|
||||
return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
|
||||
return !game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game).isEmpty() // check this first to allow Offering in main phase
|
||||
|| timing == TimingRule.INSTANT
|
||||
|| object.isInstant(game)
|
||||
|| object.hasAbility(FlashAbility.getInstance(), game)
|
||||
|
|
@ -116,13 +118,13 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
}
|
||||
|
||||
// play from not own hand
|
||||
ApprovingObject approvingObject = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
if (approvingObject == null && getSpellAbilityType().equals(SpellAbilityType.ADVENTURE_SPELL)) {
|
||||
Set<ApprovingObject> approvingObjects = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
if (approvingObjects.isEmpty() && getSpellAbilityType().equals(SpellAbilityType.ADVENTURE_SPELL)) {
|
||||
// allowed to cast adventures from non-hand?
|
||||
approvingObject = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
approvingObjects = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
}
|
||||
|
||||
if (approvingObject == null) {
|
||||
if (approvingObjects.isEmpty()) {
|
||||
Card card = game.getCard(sourceId);
|
||||
if (!(card != null && card.isOwnedBy(playerId))) {
|
||||
return ActivationStatus.getFalse();
|
||||
|
|
@ -141,12 +143,26 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: this check may not be required, but removing it require more investigation.
|
||||
// As of now it is only a way for One with the Multiverse to work.
|
||||
if (!approvingObjects.isEmpty()) {
|
||||
Card card = game.getCard(sourceId);
|
||||
Zone zone = game.getState().getZone(sourceId);
|
||||
if(card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) {
|
||||
// Regular casting, to be an alternative to the AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE from hand (e.g. One with the Multiverse):
|
||||
approvingObjects.add(new ApprovingObject(this, game));
|
||||
}
|
||||
}
|
||||
|
||||
// no mana restrict
|
||||
// Alternate spell abilities (Flashback, Overload) can't be cast with no mana to pay option
|
||||
if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& player.getCastSourceIdWithAlternateMana().contains(getSourceId())) {
|
||||
&& player.getCastSourceIdWithAlternateMana()
|
||||
.getOrDefault(getSourceId(), Collections.emptySet())
|
||||
.contains(MageIdentifier.Default)
|
||||
) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
}
|
||||
|
|
@ -159,13 +175,20 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
// fused can be called from hand only, so not permitting object allows or other zones checks
|
||||
// see https://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/251926-snapcaster-mage-and-fuse
|
||||
if (game.getState().getZone(splitCard.getId()) == Zone.HAND) {
|
||||
return new ActivationStatus(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game, playerId)
|
||||
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game, playerId), null);
|
||||
return ActivationStatus.withoutApprovingObject(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game, playerId)
|
||||
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game, playerId));
|
||||
}
|
||||
}
|
||||
return ActivationStatus.getFalse();
|
||||
} else {
|
||||
return new ActivationStatus(canChooseTarget(game, playerId), approvingObject);
|
||||
if(canChooseTarget(game, playerId)) {
|
||||
if(approvingObjects == null || approvingObjects.isEmpty()) {
|
||||
return ActivationStatus.withoutApprovingObject(true);
|
||||
}
|
||||
else {
|
||||
return new ActivationStatus(approvingObjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class CastFromGraveyardOnceEffect extends AsThoughEffectImpl {
|
|||
private final FilterCard filter;
|
||||
|
||||
CastFromGraveyardOnceEffect(FilterCard filter, String text) {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit, true);
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
this.filter = filter;
|
||||
this.staticText = text;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.effects.common.PassEffect;
|
||||
import mage.constants.Zone;
|
||||
|
|
@ -30,7 +31,7 @@ public class PassAbility extends ActivatedAbilityImpl {
|
|||
|
||||
@Override
|
||||
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||
return ActivationStatus.getTrue(this, game);
|
||||
return new ActivationStatus(new ApprovingObject(this, game));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class TapSourceCost extends CostImpl {
|
|||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent != null) {
|
||||
return !permanent.isTapped()
|
||||
&& (permanent.canTap(game) || null != game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game));
|
||||
&& (permanent.canTap(game) || !game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game).isEmpty());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class UntapSourceCost extends CostImpl {
|
|||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent != null) {
|
||||
return permanent.isTapped() && (permanent.canTap(game) || null != game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game));
|
||||
return permanent.isTapped() && (permanent.canTap(game) || game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game).isEmpty());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,4 @@ public interface AsThoughEffect extends ContinuousEffect {
|
|||
|
||||
@Override
|
||||
AsThoughEffect copy();
|
||||
|
||||
boolean isConsumable();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbility;
|
||||
import mage.cards.Card;
|
||||
|
|
@ -19,23 +20,16 @@ import mage.cards.AdventureCard;
|
|||
public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect {
|
||||
|
||||
protected AsThoughEffectType type;
|
||||
boolean consumable;
|
||||
|
||||
public AsThoughEffectImpl(AsThoughEffectType type, Duration duration, Outcome outcome) {
|
||||
this(type, duration, outcome, false);
|
||||
}
|
||||
|
||||
public AsThoughEffectImpl(AsThoughEffectType type, Duration duration, Outcome outcome, boolean consumable) {
|
||||
super(duration, outcome);
|
||||
this.type = type;
|
||||
this.effectType = EffectType.ASTHOUGH;
|
||||
this.consumable = consumable;
|
||||
}
|
||||
|
||||
protected AsThoughEffectImpl(final AsThoughEffectImpl effect) {
|
||||
super(effect);
|
||||
this.type = effect.type;
|
||||
this.consumable = effect.consumable;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -84,6 +78,10 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
|
|||
* @return
|
||||
*/
|
||||
protected boolean allowCardToPlayWithoutMana(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
|
||||
return allowCardToPlayWithoutMana(objectId, source, affectedControllerId, MageIdentifier.Default, game);
|
||||
}
|
||||
|
||||
protected boolean allowCardToPlayWithoutMana(UUID objectId, Ability source, UUID affectedControllerId, MageIdentifier identifier, Game game){
|
||||
Player player = game.getPlayer(affectedControllerId);
|
||||
Card card = game.getCard(objectId);
|
||||
if (card == null || player == null) {
|
||||
|
|
@ -92,33 +90,27 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
|
|||
if (!card.isLand(game)) {
|
||||
if (card instanceof SplitCard) {
|
||||
Card leftCard = ((SplitCard) card).getLeftHalfCard();
|
||||
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts(), identifier);
|
||||
Card rightCard = ((SplitCard) card).getRightHalfCard();
|
||||
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts(), identifier);
|
||||
} else if (card instanceof ModalDoubleFacedCard) {
|
||||
Card leftCard = ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||
Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard();
|
||||
// some MDFC's are land. IE: sea gate restoration
|
||||
if (!leftCard.isLand(game)) {
|
||||
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts(), identifier);
|
||||
}
|
||||
if (!rightCard.isLand(game)) {
|
||||
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts(), identifier);
|
||||
}
|
||||
} else if (card instanceof AdventureCard) {
|
||||
Card creatureCard = card.getMainCard();
|
||||
Card spellCard = ((AdventureCard) card).getSpellCard();
|
||||
player.setCastSourceIdWithAlternateMana(creatureCard.getId(), null, creatureCard.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(spellCard.getId(), null, spellCard.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(creatureCard.getId(), null, creatureCard.getSpellAbility().getCosts(), identifier);
|
||||
player.setCastSourceIdWithAlternateMana(spellCard.getId(), null, spellCard.getSpellAbility().getCosts(), identifier);
|
||||
}
|
||||
player.setCastSourceIdWithAlternateMana(objectId, null, card.getSpellAbility().getCosts());
|
||||
player.setCastSourceIdWithAlternateMana(objectId, null, card.getSpellAbility().getCosts(), identifier);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsumable() {
|
||||
return consumable;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.MageSingleton;
|
||||
|
|
@ -506,9 +507,10 @@ public class ContinuousEffects implements Serializable {
|
|||
* @param affectedAbility null if check full object or ability if check only one ability from that object
|
||||
* @param controllerId
|
||||
* @param game
|
||||
* @return sourceId of the permitting effect if any exists otherwise returns null
|
||||
* @return Set of all the ApprovingObject related to that asThough.
|
||||
*/
|
||||
public ApprovingObject asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
|
||||
public Set<ApprovingObject> asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
|
||||
Set<ApprovingObject> possibleApprovingObjects = new HashSet<>();
|
||||
|
||||
// usage check: effect must apply for specific ability only, not to full object (example: PLAY_FROM_NOT_OWN_HAND_ZONE)
|
||||
if (type.needAffectedAbility() && affectedAbility == null) {
|
||||
|
|
@ -553,18 +555,13 @@ public class ContinuousEffects implements Serializable {
|
|||
idToCheck = objectId;
|
||||
}
|
||||
|
||||
Set<ApprovingObject> possibleApprovingObjects = new HashSet<>();
|
||||
for (AsThoughEffect effect : asThoughEffectsList) {
|
||||
Set<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
|
||||
for (Ability ability : abilities) {
|
||||
if (affectedAbility == null) {
|
||||
// applies to full object (one effect can be used in multiple abilities)
|
||||
if (effect.applies(idToCheck, ability, controllerId, game)) {
|
||||
if (effect.isConsumable() && !game.inCheckPlayableState()) {
|
||||
possibleApprovingObjects.add(new ApprovingObject(ability, game));
|
||||
} else {
|
||||
return new ApprovingObject(ability, game);
|
||||
}
|
||||
possibleApprovingObjects.add(new ApprovingObject(ability, game));
|
||||
}
|
||||
} else {
|
||||
// applies to one affected ability
|
||||
|
|
@ -575,46 +572,13 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
|
||||
if (effect.applies(idToCheck, affectedAbility, ability, game, controllerId)) {
|
||||
if (effect.isConsumable() && !game.inCheckPlayableState()) {
|
||||
possibleApprovingObjects.add(new ApprovingObject(ability, game));
|
||||
} else {
|
||||
return new ApprovingObject(ability, game);
|
||||
}
|
||||
possibleApprovingObjects.add(new ApprovingObject(ability, game));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (possibleApprovingObjects.size() == 1) {
|
||||
return possibleApprovingObjects.iterator().next();
|
||||
} else if (possibleApprovingObjects.size() > 1) {
|
||||
// Select the ability that you use to permit the action
|
||||
Map<String, String> keyChoices = new HashMap<>();
|
||||
for (ApprovingObject approvingObject : possibleApprovingObjects) {
|
||||
MageObject mageObject = game.getObject(approvingObject.getApprovingAbility().getSourceId());
|
||||
String choiceKey = approvingObject.getApprovingAbility().getId().toString();
|
||||
String choiceValue;
|
||||
if (mageObject == null) {
|
||||
choiceValue = approvingObject.getApprovingAbility().getRule();
|
||||
} else {
|
||||
choiceValue = mageObject.getIdName() + ": " + approvingObject.getApprovingAbility().getRule(mageObject.getName());
|
||||
}
|
||||
keyChoices.put(choiceKey, choiceValue);
|
||||
}
|
||||
Choice choicePermitting = new ChoiceImpl(true);
|
||||
choicePermitting.setMessage("Choose the permitting object");
|
||||
choicePermitting.setKeyChoices(keyChoices);
|
||||
Player player = game.getPlayer(controllerId);
|
||||
player.choose(Outcome.Detriment, choicePermitting, game);
|
||||
for (ApprovingObject approvingObject : possibleApprovingObjects) {
|
||||
if (approvingObject.getApprovingAbility().getId().toString().equals(choicePermitting.getChoiceKey())) {
|
||||
return approvingObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
return possibleApprovingObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
package mage.abilities.effects.common.asthought;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.TargetController;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
|
@ -65,7 +61,7 @@ public class PlayFromNotOwnHandZoneAllEffect extends AsThoughEffectImpl {
|
|||
}
|
||||
break;
|
||||
}
|
||||
return !onlyOwnedCards || card.getOwnerId().equals(source.getControllerId())
|
||||
return (!onlyOwnedCards || card.getOwnerId().equals(source.getControllerId()))
|
||||
&& filter.match(card, game)
|
||||
&& game.getState().getZone(card.getId()).match(fromZone);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public class EchoEffect extends OneShotEffect {
|
|||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null
|
||||
&& source.getSourceObjectIfItStillExists(game) != null) {
|
||||
if (game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.PAY_0_ECHO, source, source.getControllerId(), game) != null) {
|
||||
if (!game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.PAY_0_ECHO, source, source.getControllerId(), game).isEmpty()) {
|
||||
Cost altCost = new GenericManaCost(0);
|
||||
if (controller.chooseUse(Outcome.Benefit, "Pay {0} instead of the echo cost?", source, game)) {
|
||||
altCost.clearPaid();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.Mana;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
|
|
@ -55,7 +56,7 @@ public class EmergeAbility extends SpellAbility {
|
|||
new FilterControlledCreaturePermanent(), this.getControllerId(), this, game)) {
|
||||
ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getManaValue());
|
||||
if (costToPay.canPay(this, this, this.getControllerId(), game)) {
|
||||
return ActivationStatus.getTrue(this, game);
|
||||
return new ActivationStatus(new ApprovingObject(this, game));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class FlyingEffect extends RestrictionEffect implements MageSingleton {
|
|||
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||
return blocker.getAbilities().containsKey(FlyingAbility.getInstance().getId())
|
||||
|| blocker.getAbilities().containsKey(ReachAbility.getInstance().getId())
|
||||
|| (null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_DRAGON, null, blocker.getControllerId(), game)
|
||||
|| (!game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_DRAGON, null, blocker.getControllerId(), game).isEmpty()
|
||||
&& attacker.hasSubtype(SubType.DRAGON, game));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,18 +65,18 @@ class LandwalkEffect extends RestrictionEffect {
|
|||
@Override
|
||||
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||
if (game.getBattlefield().contains(filter, blocker.getControllerId(), source, game, 1)
|
||||
&& null == game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_LANDWALK, null, blocker.getControllerId(), game)) {
|
||||
&& game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_LANDWALK, null, blocker.getControllerId(), game).isEmpty()) {
|
||||
switch (filter.getMessage()) {
|
||||
case "plains":
|
||||
return null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_PLAINSWALK, null, blocker.getControllerId(), game);
|
||||
return !game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_PLAINSWALK, null, blocker.getControllerId(), game).isEmpty();
|
||||
case "island":
|
||||
return null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_ISLANDWALK, null, blocker.getControllerId(), game);
|
||||
return !game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_ISLANDWALK, null, blocker.getControllerId(), game).isEmpty();
|
||||
case "swamp":
|
||||
return null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SWAMPWALK, null, blocker.getControllerId(), game);
|
||||
return !game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SWAMPWALK, null, blocker.getControllerId(), game).isEmpty();
|
||||
case "mountain":
|
||||
return null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_MOUNTAINWALK, null, blocker.getControllerId(), game);
|
||||
return !game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_MOUNTAINWALK, null, blocker.getControllerId(), game).isEmpty();
|
||||
case "forest":
|
||||
return null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_FORESTWALK, null, blocker.getControllerId(), game);
|
||||
return !game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_FORESTWALK, null, blocker.getControllerId(), game).isEmpty();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton {
|
|||
@Override
|
||||
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||
return blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId())
|
||||
|| null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, null, blocker.getControllerId(), game);
|
||||
|| !game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, null, blocker.getControllerId(), game).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.dynamicvalue.common.OpponentsLostLifeCount;
|
||||
|
|
@ -48,7 +49,7 @@ public class SpectacleAbility extends SpellAbility {
|
|||
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||
if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0
|
||||
&& super.canActivate(playerId, game).canActivate()) {
|
||||
return ActivationStatus.getTrue(this, game);
|
||||
return new ActivationStatus(new ApprovingObject(this, game));
|
||||
}
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.cards.Card;
|
||||
|
|
@ -54,7 +55,7 @@ public class SurgeAbility extends SpellAbility {
|
|||
if (!player.hasOpponent(playerToCheckId, game)) {
|
||||
if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(playerToCheckId) > 0
|
||||
&& super.canActivate(playerId, game).canActivate()) {
|
||||
return ActivationStatus.getTrue(this, game);
|
||||
return new ActivationStatus(new ApprovingObject(this, game));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class BlockTappedPredicate implements Predicate<Permanent> {
|
|||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
return !input.isTapped() || null != game.getState().getContinuousEffects().asThough(input.getId(), AsThoughEffectType.BLOCK_TAPPED, null, input.getControllerId(), game);
|
||||
return !input.isTapped() || !game.getState().getContinuousEffects().asThough(input.getId(), AsThoughEffectType.BLOCK_TAPPED, null, input.getControllerId(), game).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -174,8 +174,8 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
Player player = game.getPlayer(defenderAssignsCombatDamage(game) ? defendingPlayerId : attacker.getControllerId());
|
||||
if ((attacker.getAbilities().containsKey(DamageAsThoughNotBlockedAbility.getInstance().getId()) &&
|
||||
player.chooseUse(Outcome.Damage, "Have " + attacker.getLogName() + " assign damage as though it weren't blocked?", null, game)) ||
|
||||
game.getContinuousEffects().asThough(attacker.getId(), AsThoughEffectType.DAMAGE_NOT_BLOCKED,
|
||||
null, attacker.getControllerId(), game) != null) {
|
||||
!game.getContinuousEffects().asThough(attacker.getId(), AsThoughEffectType.DAMAGE_NOT_BLOCKED,
|
||||
null, attacker.getControllerId(), game).isEmpty()) {
|
||||
// for handling creatures like Thorn Elemental
|
||||
blocked = false;
|
||||
unblockedDamage(first, game);
|
||||
|
|
|
|||
|
|
@ -716,7 +716,7 @@ public class GameEvent implements Serializable {
|
|||
if (approvingObject == null) {
|
||||
return false;
|
||||
}
|
||||
if (identifier == null) {
|
||||
if (identifier.equals(MageIdentifier.Default)) {
|
||||
return false;
|
||||
}
|
||||
return identifier.equals(approvingObject.getApprovingAbility().getIdentifier());
|
||||
|
|
|
|||
|
|
@ -1234,13 +1234,13 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
public boolean canBeTargetedBy(MageObject source, UUID sourceControllerId, Game game) {
|
||||
if (source != null) {
|
||||
if (abilities.containsKey(ShroudAbility.getInstance().getId())) {
|
||||
if (null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game)) {
|
||||
if (game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game)
|
||||
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
|
||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game).isEmpty()
|
||||
&& abilities.stream()
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
|
|
@ -1416,10 +1416,10 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
// battles can never attack
|
||||
return false;
|
||||
}
|
||||
ApprovingObject approvingObject = game.getContinuousEffects().asThough(
|
||||
Set<ApprovingObject> approvingObjects = game.getContinuousEffects().asThough(
|
||||
this.objectId, AsThoughEffectType.ATTACK_AS_HASTE, null, defenderId, game
|
||||
);
|
||||
if (hasSummoningSickness() && approvingObject == null) {
|
||||
if (hasSummoningSickness() && approvingObjects.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
//20101001 - 508.1c
|
||||
|
|
@ -1435,7 +1435,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
}
|
||||
|
||||
return !abilities.containsKey(DefenderAbility.getInstance().getId())
|
||||
|| null != game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK, null, this.getControllerId(), game);
|
||||
|| !game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK, null, this.getControllerId(), game).isEmpty();
|
||||
}
|
||||
|
||||
private boolean canAttackCheckRestrictionEffects(UUID defenderId, Game game) {
|
||||
|
|
@ -1455,7 +1455,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
@Override
|
||||
public boolean canBlock(UUID attackerId, Game game) {
|
||||
if (tapped && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game) == null || isBattle(game)) {
|
||||
if (tapped && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game).isEmpty() || isBattle(game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
|
|
@ -1488,7 +1488,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
@Override
|
||||
public boolean canBlockAny(Game game) {
|
||||
if (tapped && null == game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game)) {
|
||||
if (tapped && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package mage.players;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageItem;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.*;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
import mage.abilities.costs.Cost;
|
||||
|
|
@ -1054,13 +1051,28 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
* cost
|
||||
* @param costs alternate other costs you need to pay
|
||||
*/
|
||||
void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs);
|
||||
default void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) {
|
||||
setCastSourceIdWithAlternateMana(sourceId, manaCosts, costs, MageIdentifier.Default);
|
||||
}
|
||||
|
||||
Set<UUID> getCastSourceIdWithAlternateMana();
|
||||
/**
|
||||
* If the next spell cast has the set sourceId, the spell will be cast
|
||||
* 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 manaCosts alternate ManaCost, null if it can be cast without mana
|
||||
* cost
|
||||
* @param costs alternate other costs you need to pay
|
||||
* @param identifier if not using the MageIdentifier.Default, only apply the alternate mana when ApprovingSource if of that kind.
|
||||
*/
|
||||
void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs, MageIdentifier identifier);
|
||||
|
||||
Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts();
|
||||
Map<UUID, Set<MageIdentifier>> getCastSourceIdWithAlternateMana();
|
||||
|
||||
Map<UUID, Costs<Cost>> getCastSourceIdCosts();
|
||||
Map<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> getCastSourceIdManaCosts();
|
||||
|
||||
Map<UUID, Map<MageIdentifier, Costs<Cost>>> getCastSourceIdCosts();
|
||||
|
||||
void clearCastSourceIdManaCosts();
|
||||
|
||||
|
|
|
|||
|
|
@ -163,9 +163,12 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
// indicates that the spell with the set sourceId can be cast with an alternate mana costs (can also be no mana costs)
|
||||
// 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<>();
|
||||
//
|
||||
// A card may be able to cast multiple way with multiple methods.
|
||||
// The specific MageIdentifier should be checked, before checking null as a fallback.
|
||||
protected Map<UUID, Set<MageIdentifier>> castSourceIdWithAlternateMana = new HashMap<>();
|
||||
protected Map<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> castSourceIdManaCosts = new HashMap<>();
|
||||
protected Map<UUID, Map<MageIdentifier, Costs<Cost>>> castSourceIdCosts = new HashMap<>();
|
||||
|
||||
// indicates that the player is in mana payment phase
|
||||
protected boolean payManaMode = false;
|
||||
|
|
@ -279,13 +282,22 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.bufferTimeLeft = player.getBufferTimeLeft();
|
||||
this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving;
|
||||
|
||||
this.castSourceIdWithAlternateMana.addAll(player.getCastSourceIdWithAlternateMana());
|
||||
for (Entry<UUID, ManaCosts<ManaCost>> entry : player.getCastSourceIdManaCosts().entrySet()) {
|
||||
this.castSourceIdManaCosts.put(entry.getKey(), (entry.getValue() == null ? null : entry.getValue().copy()));
|
||||
for (Entry<UUID, Set<MageIdentifier>> entry : player.getCastSourceIdWithAlternateMana().entrySet()) {
|
||||
this.castSourceIdWithAlternateMana.put(entry.getKey(), (entry.getValue() == null ? null : new HashSet<>(entry.getValue())));
|
||||
}
|
||||
for (Entry<UUID, Costs<Cost>> entry : player.getCastSourceIdCosts().entrySet()) {
|
||||
this.castSourceIdCosts.put(entry.getKey(), (entry.getValue() == null ? null : entry.getValue().copy()));
|
||||
for (Entry<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> entry : player.getCastSourceIdManaCosts().entrySet()) {
|
||||
this.castSourceIdManaCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, ManaCosts<ManaCost>> subEntry : entry.getValue().entrySet()) {
|
||||
this.castSourceIdManaCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
||||
}
|
||||
}
|
||||
for (Entry<UUID, Map<MageIdentifier, Costs<Cost>>> entry : player.getCastSourceIdCosts().entrySet()) {
|
||||
this.castSourceIdCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, Costs<Cost>> subEntry : entry.getValue().entrySet()) {
|
||||
this.castSourceIdCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
||||
}
|
||||
}
|
||||
|
||||
this.payManaMode = player.payManaMode;
|
||||
this.phyrexianColors = player.getPhyrexianColors() != null ? player.phyrexianColors.copy() : null;
|
||||
for (Designation object : player.designations) {
|
||||
|
|
@ -364,13 +376,20 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving();
|
||||
|
||||
this.clearCastSourceIdManaCosts();
|
||||
this.castSourceIdWithAlternateMana.clear();
|
||||
this.castSourceIdWithAlternateMana.addAll(player.getCastSourceIdWithAlternateMana());
|
||||
for (Entry<UUID, ManaCosts<ManaCost>> entry : player.getCastSourceIdManaCosts().entrySet()) {
|
||||
this.castSourceIdManaCosts.put(entry.getKey(), (entry.getValue() == null ? null : entry.getValue().copy()));
|
||||
for (Entry<UUID, Set<MageIdentifier>> entry : player.getCastSourceIdWithAlternateMana().entrySet()) {
|
||||
this.castSourceIdWithAlternateMana.put(entry.getKey(), (entry.getValue() == null ? null : new HashSet<>(entry.getValue())));
|
||||
}
|
||||
for (Entry<UUID, Costs<Cost>> entry : player.getCastSourceIdCosts().entrySet()) {
|
||||
this.castSourceIdCosts.put(entry.getKey(), (entry.getValue() == null ? null : entry.getValue().copy()));
|
||||
for (Entry<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> entry : player.getCastSourceIdManaCosts().entrySet()) {
|
||||
this.castSourceIdManaCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, ManaCosts<ManaCost>> subEntry : entry.getValue().entrySet()) {
|
||||
this.castSourceIdManaCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
||||
}
|
||||
}
|
||||
for (Entry<UUID, Map<MageIdentifier, Costs<Cost>>> entry : player.getCastSourceIdCosts().entrySet()) {
|
||||
this.castSourceIdCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, Costs<Cost>> subEntry : entry.getValue().entrySet()) {
|
||||
this.castSourceIdCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
||||
}
|
||||
}
|
||||
|
||||
this.phyrexianColors = player.getPhyrexianColors() != null ? player.getPhyrexianColors().copy() : null;
|
||||
|
|
@ -636,13 +655,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
if (source != null) {
|
||||
if (abilities.containsKey(ShroudAbility.getInstance().getId())
|
||||
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game)) {
|
||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceControllerId != null
|
||||
&& this.hasOpponent(sourceControllerId, game)
|
||||
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
|
||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game).isEmpty()
|
||||
&& abilities.stream()
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
|
|
@ -1080,25 +1099,37 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) {
|
||||
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs, MageIdentifier identifier) {
|
||||
// cost must be copied for data consistence between game simulations
|
||||
castSourceIdWithAlternateMana.add(sourceId);
|
||||
castSourceIdManaCosts.put(sourceId, manaCosts != null ? manaCosts.copy() : null);
|
||||
castSourceIdCosts.put(sourceId, costs != null ? costs.copy() : null);
|
||||
castSourceIdWithAlternateMana
|
||||
.computeIfAbsent(sourceId, k -> new HashSet<>())
|
||||
.add(identifier);
|
||||
|
||||
castSourceIdManaCosts
|
||||
.computeIfAbsent(sourceId, k -> new HashMap<>())
|
||||
.put(identifier, manaCosts != null ? manaCosts.copy() : null);
|
||||
|
||||
castSourceIdCosts
|
||||
.computeIfAbsent(sourceId, k -> new HashMap<>())
|
||||
.put(identifier, costs != null ? costs.copy() : null);
|
||||
|
||||
if (identifier == null) {
|
||||
boolean a = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getCastSourceIdWithAlternateMana() {
|
||||
public Map<UUID, Set<MageIdentifier>> getCastSourceIdWithAlternateMana() {
|
||||
return castSourceIdWithAlternateMana;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, Costs<Cost>> getCastSourceIdCosts() {
|
||||
public Map<UUID, Map<MageIdentifier, Costs<Cost>>> getCastSourceIdCosts() {
|
||||
return castSourceIdCosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() {
|
||||
public Map<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> getCastSourceIdManaCosts() {
|
||||
return castSourceIdManaCosts;
|
||||
}
|
||||
|
||||
|
|
@ -1187,10 +1218,19 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
// ALTERNATIVE COST from dynamic effects
|
||||
// some effects set sourceId to cast without paying mana costs or other costs
|
||||
if (getCastSourceIdWithAlternateMana().contains(ability.getSourceId())) {
|
||||
MageIdentifier identifier = approvingObject == null
|
||||
? MageIdentifier.Default
|
||||
: approvingObject.getApprovingAbility().getIdentifier();
|
||||
|
||||
if (!getCastSourceIdWithAlternateMana().getOrDefault(ability.getSourceId(), Collections.emptySet()).contains(identifier)) {
|
||||
// identifier has no alternate cast entry for that sourceId, using Default instead.
|
||||
identifier = MageIdentifier.Default;
|
||||
}
|
||||
|
||||
if (getCastSourceIdWithAlternateMana().getOrDefault(ability.getSourceId(), Collections.emptySet()).contains(identifier)) {
|
||||
Ability spellAbility = spell.getSpellAbility();
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(ability.getSourceId());
|
||||
Costs<Cost> costs = getCastSourceIdCosts().get(ability.getSourceId());
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(ability.getSourceId()).get(identifier);
|
||||
Costs<Cost> costs = getCastSourceIdCosts().get(ability.getSourceId()).get(identifier);
|
||||
if (alternateCosts == null) {
|
||||
noMana = true;
|
||||
} else {
|
||||
|
|
@ -1273,21 +1313,30 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
ApprovingObjectResult approvingResult = chooseApprovingObject(
|
||||
game,
|
||||
activationStatus.getApprovingObjects().stream().collect(Collectors.toList()),
|
||||
false
|
||||
);
|
||||
if (approvingResult.status.equals(ApprovingObjectResultStatus.NOT_REQUIRED_NO_CHOICE)) {
|
||||
return false; // canceled choice of approving object.
|
||||
}
|
||||
|
||||
//20091005 - 305.1
|
||||
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND,
|
||||
card.getId(), playLandAbility, playerId, activationStatus.getApprovingObject()))) {
|
||||
card.getId(), playLandAbility, playerId, approvingResult.approvingObject))) {
|
||||
// int bookmark = game.bookmarkState();
|
||||
// land events must return original zone (uses for commander watcher)
|
||||
Zone cardZoneBefore = game.getState().getZone(card.getId());
|
||||
GameEvent landEventBefore = GameEvent.getEvent(GameEvent.EventType.PLAY_LAND,
|
||||
card.getId(), playLandAbility, playerId, activationStatus.getApprovingObject());
|
||||
card.getId(), playLandAbility, playerId, approvingResult.approvingObject);
|
||||
landEventBefore.setZone(cardZoneBefore);
|
||||
game.fireEvent(landEventBefore);
|
||||
|
||||
if (moveCards(card, Zone.BATTLEFIELD, playLandAbility, game, false, false, false, null)) {
|
||||
incrementLandsPlayed();
|
||||
GameEvent landEventAfter = GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED,
|
||||
card.getId(), playLandAbility, playerId, activationStatus.getApprovingObject());
|
||||
card.getId(), playLandAbility, playerId, approvingResult.approvingObject);
|
||||
landEventAfter.setZone(cardZoneBefore);
|
||||
game.fireEvent(landEventAfter);
|
||||
|
||||
|
|
@ -1311,6 +1360,68 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return true;
|
||||
}
|
||||
|
||||
private enum ApprovingObjectResultStatus {
|
||||
CHOSEN,
|
||||
NO_POSSIBLE_CHOICE,
|
||||
NOT_REQUIRED_NO_CHOICE,
|
||||
}
|
||||
|
||||
private class ApprovingObjectResult {
|
||||
public final ApprovingObjectResultStatus status;
|
||||
public final ApprovingObject approvingObject; // not null iff status is CHOSEN
|
||||
|
||||
private ApprovingObjectResult(ApprovingObjectResultStatus status, ApprovingObject approvingObject) {
|
||||
this.status = status;
|
||||
this.approvingObject = approvingObject;
|
||||
}
|
||||
}
|
||||
|
||||
private ApprovingObjectResult chooseApprovingObject(Game game, List<ApprovingObject> possibleApprovingObjects, boolean required) {
|
||||
// Choosing
|
||||
if (possibleApprovingObjects.isEmpty()) {
|
||||
return new ApprovingObjectResult(ApprovingObjectResultStatus.NO_POSSIBLE_CHOICE, null);
|
||||
} else {
|
||||
// Select the ability that you use to permit the action
|
||||
Map<String, String> keyChoices = new HashMap<>();
|
||||
int i = 0;
|
||||
for (ApprovingObject possibleApprovingObject : possibleApprovingObjects) {
|
||||
MageObject mageObject = game.getObject(possibleApprovingObject.getApprovingAbility().getSourceId());
|
||||
String choiceValue = "";
|
||||
MageIdentifier identifier = possibleApprovingObject.getApprovingAbility().getIdentifier();
|
||||
if (!identifier.getAdditionalText().isEmpty()) {
|
||||
choiceValue += identifier.getAdditionalText() + ": ";
|
||||
}
|
||||
if (mageObject == null) {
|
||||
choiceValue += possibleApprovingObject.getApprovingAbility().getRule();
|
||||
} else {
|
||||
choiceValue += mageObject.getIdName() + ": ";
|
||||
String moreDetails = possibleApprovingObject.getApprovingAbility().getRule(mageObject.getName());
|
||||
choiceValue += moreDetails.isEmpty() ? "Cast normally" : moreDetails;
|
||||
}
|
||||
keyChoices.put((i++) + "", choiceValue);
|
||||
}
|
||||
|
||||
int choice = 0;
|
||||
if (!game.inCheckPlayableState() && keyChoices.size() > 1) {
|
||||
Choice choicePermitting = new ChoiceImpl(required);
|
||||
choicePermitting.setMessage("Choose the permitting object");
|
||||
choicePermitting.setKeyChoices(keyChoices);
|
||||
if (canRespond()) {
|
||||
if (choose(Outcome.Neutral, choicePermitting, game)) {
|
||||
String choiceKey = choicePermitting.getChoiceKey();
|
||||
if (choiceKey != null) {
|
||||
choice = Integer.parseInt(choiceKey);
|
||||
}
|
||||
} else {
|
||||
return new ApprovingObjectResult(ApprovingObjectResultStatus.NOT_REQUIRED_NO_CHOICE, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ApprovingObjectResult(ApprovingObjectResultStatus.CHOSEN, possibleApprovingObjects.get(choice));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected boolean playManaAbility(ActivatedManaAbilityImpl ability, Game game) {
|
||||
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATE_ABILITY,
|
||||
ability.getId(), ability, playerId))) {
|
||||
|
|
@ -1463,7 +1574,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
result = playManaAbility((ActivatedManaAbilityImpl) ability.copy(), game);
|
||||
break;
|
||||
case SPELL:
|
||||
result = cast((SpellAbility) ability, game, false, activationStatus.getApprovingObject());
|
||||
ApprovingObjectResult approvingResult = chooseApprovingObject(
|
||||
game,
|
||||
activationStatus.getApprovingObjects().stream().collect(Collectors.toList()),
|
||||
false
|
||||
);
|
||||
if (approvingResult.status.equals(ApprovingObjectResultStatus.NOT_REQUIRED_NO_CHOICE)) {
|
||||
return false; // chosen to not approve any AsThough.
|
||||
}
|
||||
|
||||
result = cast((SpellAbility) ability, game, false, approvingResult.approvingObject);
|
||||
break;
|
||||
default:
|
||||
result = playAbility(ability.copy(), game);
|
||||
|
|
@ -3452,9 +3572,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// ALTERNATIVE COST FROM dynamic effects
|
||||
if (getCastSourceIdWithAlternateMana().contains(copy.getSourceId())) {
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId());
|
||||
Costs<Cost> costs = getCastSourceIdCosts().get(copy.getSourceId());
|
||||
for(MageIdentifier identifier : getCastSourceIdWithAlternateMana().getOrDefault(copy.getSourceId(), new HashSet<>())) {
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId()).get(identifier);
|
||||
Costs<Cost> costs = getCastSourceIdCosts().get(copy.getSourceId()).get(identifier);
|
||||
|
||||
boolean canPutToPlay = true;
|
||||
if (alternateCosts != null && !alternateCosts.canPay(copy, copy, playerId, game)) {
|
||||
|
|
@ -3499,9 +3619,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
// Get the ability, if any, which allows for spending many as if it were another color.
|
||||
// TODO: This needs to be improved to handle multiple approving objects.
|
||||
// See https://github.com/magefree/mage/issues/8584
|
||||
ApprovingObject approvingObject = game.getContinuousEffects().asThough(ability.getSourceId(),
|
||||
Set<ApprovingObject> approvingObjects = game.getContinuousEffects().asThough(ability.getSourceId(),
|
||||
AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
|
||||
for (Mana mana : abilityOptions) {
|
||||
if (mana.count() == 0) {
|
||||
|
|
@ -3517,7 +3635,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// TODO: Describe this
|
||||
// Abilities that let us spend mana as if it were any (or other colors/types) must be handled separately
|
||||
// and can't be incorporated into calculating availableMana since the number of combinations would explode.
|
||||
if (approvingObject != null && mana.count() <= avail.count()) {
|
||||
if (!approvingObjects.isEmpty() && mana.count() <= avail.count()) {
|
||||
// TODO: I think this is wrong for spell that require colorless
|
||||
return true;
|
||||
}
|
||||
|
|
@ -3764,7 +3882,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// So make it available all the time
|
||||
boolean canUse;
|
||||
if (ability instanceof MorphAbility && object instanceof Card && (game.canPlaySorcery(getId())
|
||||
|| (null != game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.CAST_AS_INSTANT, playAbility, this.getId(), game)))) {
|
||||
|| (!game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.CAST_AS_INSTANT, playAbility, this.getId(), game).isEmpty()))) {
|
||||
canUse = canPlayCardByAlternateCost((Card) object, availableMana, playAbility, game);
|
||||
} else {
|
||||
canUse = canPlay(playAbility, availableMana, object, game); // canPlay already checks alternative source costs and all conditions
|
||||
|
|
@ -3843,23 +3961,23 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
continue;
|
||||
}
|
||||
|
||||
ApprovingObject approvingObject;
|
||||
Set<ApprovingObject> approvingObjects;
|
||||
if ((isPlaySpell || isPlayLand) && (fromZone != Zone.BATTLEFIELD)) {
|
||||
// play hand from non hand zone (except battlefield - you can't play already played permanents)
|
||||
approvingObject = game.getContinuousEffects().asThough(object.getId(),
|
||||
approvingObjects = game.getContinuousEffects().asThough(object.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game);
|
||||
|
||||
if (approvingObject == null && isPlaySpell
|
||||
if (approvingObjects.isEmpty() && isPlaySpell
|
||||
&& ((SpellAbility) ability).getSpellAbilityType().equals(SpellAbilityType.ADVENTURE_SPELL)) {
|
||||
approvingObject = game.getContinuousEffects().asThough(object.getId(),
|
||||
approvingObjects = game.getContinuousEffects().asThough(object.getId(),
|
||||
AsThoughEffectType.CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game);
|
||||
}
|
||||
} else {
|
||||
// other abilities from direct zones
|
||||
approvingObject = null;
|
||||
approvingObjects = new HashSet<>();
|
||||
}
|
||||
|
||||
boolean canActivateAsHandZone = approvingObject != null
|
||||
boolean canActivateAsHandZone = !approvingObjects.isEmpty()
|
||||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||
boolean possibleToPlay = canActivateAsHandZone
|
||||
&& ability.getZone().match(Zone.HAND)
|
||||
|
|
@ -4434,8 +4552,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate) {
|
||||
if (null != game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.LOOK_AT_FACE_DOWN, null, this.getId(), game)) {
|
||||
if (!game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.LOOK_AT_FACE_DOWN, null, this.getId(), game).isEmpty()) {
|
||||
// two modes: look at the card or do not look and activate other abilities
|
||||
String lookMessage = "Look at " + card.getIdName();
|
||||
String lookYes = "Yes, look at the card";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package mage.util;
|
|||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.abilities.*;
|
||||
|
|
@ -147,7 +148,7 @@ public final class CardUtil {
|
|||
ability.addManaCostsToPay(adjustedCost);
|
||||
}
|
||||
|
||||
private static ManaCosts<ManaCost> adjustCost(ManaCosts<ManaCost> manaCosts, int reduceCount) {
|
||||
public static ManaCosts<ManaCost> adjustCost(ManaCosts<ManaCost> manaCosts, int reduceCount) {
|
||||
ManaCosts<ManaCost> newCost = new ManaCostsImpl<>();
|
||||
|
||||
// nothing to change
|
||||
|
|
@ -1447,8 +1448,8 @@ public final class CardUtil {
|
|||
Costs<Cost> additionalCostsLeft = leftHalfCard.getSpellAbility().getCosts();
|
||||
Costs<Cost> additionalCostsRight = rightHalfCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), manaCost, additionalCostsLeft);
|
||||
player.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), manaCost, additionalCostsRight);
|
||||
player.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), manaCost, additionalCostsLeft, MageIdentifier.Default);
|
||||
player.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), manaCost, additionalCostsRight, MageIdentifier.Default);
|
||||
}
|
||||
// allow the card to be cast
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), Boolean.TRUE);
|
||||
|
|
@ -1465,13 +1466,13 @@ public final class CardUtil {
|
|||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsMDFCLeft = leftHalfCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), manaCost, additionalCostsMDFCLeft);
|
||||
player.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), manaCost, additionalCostsMDFCLeft, MageIdentifier.Default);
|
||||
}
|
||||
if (!rightHalfCard.isLand(game)) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsMDFCRight = rightHalfCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), manaCost, additionalCostsMDFCRight);
|
||||
player.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), manaCost, additionalCostsMDFCRight, MageIdentifier.Default);
|
||||
}
|
||||
}
|
||||
// allow the card to be cast
|
||||
|
|
@ -1488,8 +1489,8 @@ public final class CardUtil {
|
|||
Costs<Cost> additionalCostsCreature = creatureCard.getSpellAbility().getCosts();
|
||||
Costs<Cost> additionalCostsSpellCard = spellCard.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(creatureCard.getId(), manaCost, additionalCostsCreature);
|
||||
player.setCastSourceIdWithAlternateMana(spellCard.getId(), manaCost, additionalCostsSpellCard);
|
||||
player.setCastSourceIdWithAlternateMana(creatureCard.getId(), manaCost, additionalCostsCreature, MageIdentifier.Default);
|
||||
player.setCastSourceIdWithAlternateMana(spellCard.getId(), manaCost, additionalCostsSpellCard, MageIdentifier.Default);
|
||||
}
|
||||
// allow the card to be cast
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), Boolean.TRUE);
|
||||
|
|
@ -1500,7 +1501,7 @@ public final class CardUtil {
|
|||
if (manaCost != null) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsNormalCard = card.getSpellAbility().getCosts();
|
||||
player.setCastSourceIdWithAlternateMana(card.getMainCard().getId(), manaCost, additionalCostsNormalCard);
|
||||
player.setCastSourceIdWithAlternateMana(card.getMainCard().getId(), manaCost, additionalCostsNormalCard, MageIdentifier.Default);
|
||||
}
|
||||
|
||||
// cast it
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue