forked from External/mage
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
|
|
@ -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