* Improved handling of asThoughtAs approval by abilities that allows a clear and easy assignment of the approving effect.

This commit is contained in:
LevelX2 2020-08-21 14:58:22 +02:00
parent 0565d32f55
commit 8105d8b26c
117 changed files with 523 additions and 442 deletions

View file

@ -0,0 +1,33 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage;
import mage.abilities.Ability;
import mage.game.Game;
/**
*
* @author LevelX2
*/
public class ApprovingObject {
private final Ability approvingAbility;
private final MageObjectReference approvingMageObjectReference;
public ApprovingObject(Ability source, Game game) {
this.approvingAbility = source;
this.approvingMageObjectReference = new MageObjectReference(source.getSourceId(), game);
}
public Ability getApprovingAbility() {
return approvingAbility;
}
public MageObjectReference getApprovingMageObjectReference() {
return approvingMageObjectReference;
}
}

View file

@ -0,0 +1,15 @@
package mage;
/**
* Used to identify specific actions/events and to be able to assign them to the
* correct watcher or other processing.
*
* @author LevelX2
*/
public enum MageIdentifier {
GisaAndGeralfWatcher,
KaradorGhostChieftainWatcher,
KessDissidentMageWatcher,
LurrusOfTheDreamDenWatcher,
MuldrothaTheGravetideWatcher
}

View file

@ -23,6 +23,7 @@ import mage.watchers.Watcher;
import java.io.Serializable;
import java.util.List;
import java.util.UUID;
import mage.MageIdentifier;
import mage.abilities.costs.common.TapSourceCost;
/**
@ -550,4 +551,8 @@ public interface Ability extends Controllable, Serializable {
* @return
*/
boolean isSameInstance(Ability ability);
MageIdentifier getIdentifier();
AbilityImpl setIdentifier(MageIdentifier mageIdentifier);
}

View file

@ -35,7 +35,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.abilities.costs.common.TapSourceCost;
import mage.MageIdentifier;
/**
* @author BetaSteward_at_googlemail.com
@ -73,7 +73,8 @@ public abstract class AbilityImpl implements Ability {
protected CostAdjuster costAdjuster = null;
protected List<Hint> hints = 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)
public AbilityImpl(AbilityType abilityType, Zone zone) {
this.id = UUID.randomUUID();
this.originalId = id;
@ -123,6 +124,7 @@ public abstract class AbilityImpl implements Ability {
this.hints.add(hint.copy());
}
this.customOutcome = ability.customOutcome;
this.identifier = ability.identifier;
}
@Override
@ -1306,4 +1308,15 @@ public abstract class AbilityImpl implements Ability {
|| (this.getOriginalId().equals(ability.getOriginalId()))
|| (this.getClass() == ability.getClass() && this.getRule(true).equals(ability.getRule(true)));
}
@Override
public MageIdentifier getIdentifier() {
return identifier;
}
@Override
public AbilityImpl setIdentifier(MageIdentifier identifier) {
this.identifier = identifier;
return this;
}
}

View file

@ -7,6 +7,7 @@ import mage.constants.TargetController;
import mage.game.Game;
import java.util.UUID;
import mage.ApprovingObject;
/**
* @author BetaSteward_at_googlemail.com
@ -16,19 +17,19 @@ public interface ActivatedAbility extends Ability {
final class ActivationStatus {
private final boolean canActivate;
private final MageObjectReference permittingObject;
private final ApprovingObject approvingObject;
public ActivationStatus(boolean canActivate, MageObjectReference permittingObject) {
public ActivationStatus(boolean canActivate, ApprovingObject approvingObject) {
this.canActivate = canActivate;
this.permittingObject = permittingObject;
this.approvingObject = approvingObject;
}
public boolean canActivate() {
return canActivate;
}
public MageObjectReference getPermittingObject() {
return permittingObject;
public ApprovingObject getApprovingObject() {
return approvingObject;
}
public static ActivationStatus getFalse() {
@ -36,12 +37,11 @@ public interface ActivatedAbility extends Ability {
}
/**
* @param permittingObjectAbility card or permanent that allows to activate current ability
* @param approvingObjectAbility ability that allows to activate/use current ability
*/
public static ActivationStatus getTrue(Ability permittingObjectAbility, Game game) {
MageObject object = permittingObjectAbility == null ? null : permittingObjectAbility.getSourceObject(game);
MageObjectReference ref = object == null ? null : new MageObjectReference(object, game);
return new ActivationStatus(true, ref);
public static ActivationStatus getTrue(Ability approvingObjectAbility, Game game) {
ApprovingObject approvingObject = approvingObjectAbility == null ? null : new ApprovingObject(approvingObjectAbility, game);
return new ActivationStatus(true, approvingObject);
}
}

View file

@ -1,7 +1,6 @@
package mage.abilities;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
@ -20,6 +19,7 @@ import mage.game.permanent.Permanent;
import mage.util.CardUtil;
import java.util.UUID;
import mage.ApprovingObject;
/**
* @author BetaSteward_at_googlemail.com
@ -184,7 +184,7 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
return ActivationStatus.getFalse();
}
//20091005 - 602.5d/602.5e
MageObjectReference permittingObject = game.getContinuousEffects()
ApprovingObject approvingObject = game.getContinuousEffects()
.asThough(sourceId,
AsThoughEffectType.ACTIVATE_AS_INSTANT,
this,
@ -192,11 +192,11 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
game);
if (timing == TimingRule.INSTANT
|| game.canPlaySorcery(playerId)
|| null != permittingObject) {
|| null != approvingObject) {
if (costs.canPay(this, sourceId, playerId, game)
&& canChooseTarget(game)) {
this.activatorId = playerId;
return new ActivationStatus(true, permittingObject);
return new ActivationStatus(true, approvingObject);
}
}
return ActivationStatus.getFalse();

View file

@ -1,7 +1,7 @@
package mage.abilities;
import java.util.UUID;
import mage.MageObjectReference;
import mage.ApprovingObject;
import mage.constants.AbilityType;
import mage.constants.AsThoughEffectType;
import mage.constants.Zone;
@ -25,15 +25,15 @@ public class PlayLandAbility extends ActivatedAbilityImpl {
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
MageObjectReference permittingObject = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, playerId, game);
if (!controlsAbility(playerId, game) && null == permittingObject) {
ApprovingObject approvingObject = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, playerId, game);
if (!controlsAbility(playerId, game) && null == approvingObject) {
return ActivationStatus.getFalse();
}
//20091005 - 114.2a
return new ActivationStatus(game.isActivePlayer(playerId)
&& game.getPlayer(playerId).canPlayLand()
&& game.canPlaySorcery(playerId),
permittingObject);
approvingObject);
}
@Override

View file

@ -1,7 +1,6 @@
package mage.abilities;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.ManaCost;
@ -16,6 +15,7 @@ import mage.players.Player;
import java.util.Optional;
import java.util.UUID;
import mage.ApprovingObject;
/**
* @author BetaSteward_at_googlemail.com
@ -82,8 +82,8 @@ public class SpellAbility extends ActivatedAbilityImpl {
return ActivationStatus.getFalse();
}
// fix for Gitaxian Probe and casting opponent's spells
MageObjectReference permittingSource = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, playerId, game);
if (permittingSource == null) {
ApprovingObject approvingObject = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, playerId, game);
if (approvingObject == null) {
Card card = game.getCard(sourceId);
if (!(card != null && card.isOwnedBy(playerId))) {
return ActivationStatus.getFalse();
@ -118,7 +118,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
return ActivationStatus.getFalse();
} else {
return new ActivationStatus(canChooseTarget(game), permittingSource);
return new ActivationStatus(canChooseTarget(game), approvingObject);
}
}
}

View file

@ -30,6 +30,7 @@ import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.ApprovingObject;
/**
* @author BetaSteward_at_googlemail.com
@ -507,7 +508,7 @@ public class ContinuousEffects implements Serializable {
* @return sourceId of the permitting effect if any exists otherwise returns
* null
*/
public MageObjectReference asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
public ApprovingObject asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);
if (!asThoughEffectsList.isEmpty()) {
UUID idToCheck;
@ -536,7 +537,7 @@ public class ContinuousEffects implements Serializable {
if (affectedAbility == null) {
// applies to own ability (one effect can be used in multiple abilities)
if (effect.applies(idToCheck, ability, controllerId, game)) {
return new MageObjectReference(ability.getSourceObject(game), game);
return new ApprovingObject(ability, game);
}
} else {
// applies to affected ability
@ -547,7 +548,7 @@ public class ContinuousEffects implements Serializable {
}
if (effect.applies(idToCheck, affectedAbility, ability, game, controllerId)) {
return new MageObjectReference(ability.getSourceObject(game), game);
return new ApprovingObject(ability, game);
}
}
}

View file

@ -2,6 +2,7 @@
package mage.abilities.effects.common;
import java.util.Set;
import mage.ApprovingObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
@ -73,7 +74,7 @@ public class CastCardFromOutsideTheGameEffect extends OneShotEffect {
if (player.choose(Outcome.Benefit, filteredCards, target, game)) {
Card card = player.getSideboard().get(target.getFirstTarget(), game);
if (card != null) {
player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game));
}
}
}

View file

@ -20,6 +20,7 @@ import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
import mage.ApprovingObject;
/**
* FAQ 2013/01/11
@ -120,7 +121,7 @@ class CipherStoreEffect extends OneShotEffect {
SpellAbility ability = copyCard.getSpellAbility();
// remove the cipher effect from the copy
ability.getEffects().removeIf(effect -> effect instanceof CipherEffect);
controller.cast(ability, game, true, new MageObjectReference(source.getSourceObject(game), game));
controller.cast(ability, game, true, new ApprovingObject(source, game));
}

View file

@ -13,6 +13,7 @@ import mage.util.CardUtil;
import java.util.Set;
import java.util.UUID;
import mage.ApprovingObject;
/**
* @author LevelX2
@ -67,7 +68,7 @@ public class HideawayPlayEffect extends OneShotEffect {
}
}
if (!controller.playCard(card, game, true, true, new MageObjectReference(source.getSourceObject(game), game))) {
if (!controller.playCard(card, game, true, true, new ApprovingObject(source, game))) {
if (card.getZoneChangeCounter(game) == zcc) {
card.setFaceDown(true, game);
}

View file

@ -1,5 +1,6 @@
package mage.abilities.effects.common;
import mage.ApprovingObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.Mode;
@ -38,7 +39,7 @@ public class PlayTargetWithoutPayingManaEffect extends OneShotEffect {
&& target != null) {
game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), Boolean.TRUE);
Boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(target, game, true),
game, true, new MageObjectReference(source.getSourceObject(game), game));
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), null);
return cardWasCast;
}

View file

@ -1,5 +1,6 @@
package mage.abilities.effects.common.cost;
import mage.ApprovingObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
@ -87,7 +88,7 @@ public class CastWithoutPayingManaCostEffect extends OneShotEffect {
if (cardToCast != null) {
game.getState().setValue("PlayFromNotOwnHandZone" + cardToCast.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(cardToCast, game, true),
game, true, new MageObjectReference(source.getSourceObject(game), game));
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + cardToCast.getId(), null);
}
}

View file

@ -1,5 +1,6 @@
package mage.abilities.keyword;
import mage.ApprovingObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
@ -109,7 +110,7 @@ class CascadeEffect extends OneShotEffect {
if (controller.chooseUse(outcome, "Use cascade effect on " + card.getLogName() + '?', source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new MageObjectReference(source.getSourceObject(game), game));
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
}

View file

@ -1,7 +1,6 @@
package mage.abilities.keyword;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
@ -20,6 +19,7 @@ import mage.game.stack.Spell;
import mage.players.Player;
import java.util.UUID;
import mage.ApprovingObject;
/**
* 702.33. Madness
@ -219,7 +219,7 @@ class MadnessCastEffect extends OneShotEffect {
castByMadness.setSpellAbilityCastMode(SpellAbilityCastMode.MADNESS);
costRef.clear();
costRef.add(madnessCost);
return owner.cast(castByMadness, game, false, new MageObjectReference(source.getSourceObject(game), game));
return owner.cast(castByMadness, game, false, new ApprovingObject(source, game));
}
@Override

View file

@ -1,5 +1,6 @@
package mage.abilities.keyword;
import mage.ApprovingObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
@ -144,7 +145,7 @@ class MiracleEffect extends OneShotEffect {
// replace with the new cost
costRef.clear();
costRef.add(miracleCosts);
controller.cast(abilityToCast, game, false, new MageObjectReference(source.getSourceObject(game), game));
controller.cast(abilityToCast, game, false, new ApprovingObject(source, game));
return true;
}
return false;

View file

@ -19,6 +19,7 @@ import mage.game.stack.Spell;
import mage.players.Player;
import java.util.UUID;
import mage.ApprovingObject;
/**
* This ability has no effect by default and will always return false on the
@ -189,7 +190,7 @@ class ReboundCastSpellFromExileEffect extends OneShotEffect {
&& reboundCard != null) {
game.getState().setValue("PlayFromNotOwnHandZone" + reboundCard.getId(), Boolean.TRUE);
Boolean cardWasCast = player.cast(player.chooseAbilityForCast(reboundCard, game, true),
game, true, new MageObjectReference(source.getSourceObject(game), game));
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + reboundCard.getId(), null);
return cardWasCast;
}

View file

@ -1,5 +1,6 @@
package mage.abilities.keyword;
import mage.ApprovingObject;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
@ -104,7 +105,7 @@ class RippleEffect extends OneShotEffect {
while (player.canRespond() && cards.count(sameNameFilter, game) > 0 && player.choose(Outcome.PlayForFree, cards, target1, game)) {
Card card = cards.get(target1.getFirstTarget(), game);
if (card != null) {
player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
player.cast(card.getSpellAbility(), game, true, new ApprovingObject(source, game));
cards.remove(card);
}
target1.clearChosen();

View file

@ -1,7 +1,6 @@
package mage.abilities.keyword;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
import mage.abilities.TriggeredAbilityImpl;
@ -27,6 +26,7 @@ import mage.target.targetpointer.FixedTarget;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.ApprovingObject;
/**
* 502.59. Suspend
@ -369,7 +369,7 @@ class SuspendPlayCardEffect extends OneShotEffect {
// cast the card for free
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
Boolean cardWasCast = player.cast(player.chooseAbilityForCast(card, game, true),
game, true, new MageObjectReference(source.getSourceObject(game), game));
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
if (cardWasCast) {
if (card.isCreature()) {

View file

@ -971,5 +971,5 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
}
}
return false;
}
}
}

View file

@ -1,12 +1,14 @@
package mage.game.events;
import mage.MageObjectReference;
import mage.constants.Zone;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.ApprovingObject;
import mage.MageIdentifier;
/**
* @author BetaSteward_at_googlemail.com
@ -27,7 +29,7 @@ public class GameEvent implements Serializable {
protected String data;
protected Zone zone;
protected List<UUID> appliedEffects = new ArrayList<>();
protected MageObjectReference reference; // e.g. the permitting object for casting a spell from non hand zone
protected ApprovingObject approvingObject; // e.g. the approving object for casting a spell from non hand zone
protected UUID customEventType = null;
public enum EventType {
@ -359,8 +361,8 @@ public class GameEvent implements Serializable {
this(type, null, targetId, sourceId, playerId, 0, false);
}
public GameEvent(EventType type, UUID targetId, UUID sourceId, UUID playerId, MageObjectReference reference) {
this(type, null, targetId, sourceId, playerId, 0, false, reference);
public GameEvent(EventType type, UUID targetId, UUID sourceId, UUID playerId, ApprovingObject approvingObject) {
this(type, null, targetId, sourceId, playerId, 0, false, approvingObject);
}
public GameEvent(EventType type, UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag) {
@ -383,8 +385,8 @@ public class GameEvent implements Serializable {
return new GameEvent(type, targetId, sourceId, playerId);
}
public static GameEvent getEvent(EventType type, UUID targetId, UUID sourceId, UUID playerId, MageObjectReference reference) {
return new GameEvent(type, targetId, sourceId, playerId, reference);
public static GameEvent getEvent(EventType type, UUID targetId, UUID sourceId, UUID playerId, ApprovingObject approvingObject) {
return new GameEvent(type, targetId, sourceId, playerId, approvingObject);
}
public static GameEvent getEvent(EventType type, UUID targetId, UUID playerId) {
@ -423,7 +425,7 @@ public class GameEvent implements Serializable {
}
private GameEvent(EventType type, UUID customEventType,
UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag, MageObjectReference reference) {
UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag, ApprovingObject approvingObject) {
this.type = type;
this.customEventType = customEventType;
this.targetId = targetId;
@ -431,7 +433,7 @@ public class GameEvent implements Serializable {
this.amount = amount;
this.playerId = playerId;
this.flag = flag;
this.reference = reference;
this.approvingObject = approvingObject;
}
public EventType getType() {
@ -501,12 +503,17 @@ public class GameEvent implements Serializable {
this.zone = zone;
}
public MageObjectReference getAdditionalReference() {
return reference;
/**
* Returns possibly approving object that allowed the creation of the event.
*
* @return
*/
public ApprovingObject getAdditionalReference() {
return approvingObject;
}
public void setAdditionalReference(MageObjectReference additionalReference) {
this.reference = additionalReference;
public void setAdditionalReference(ApprovingObject approvingObject) {
this.approvingObject = approvingObject;
}
/**
@ -546,4 +553,14 @@ public class GameEvent implements Serializable {
}
}
}
public boolean hasApprovingIdentifier(MageIdentifier identifier) {
if (approvingObject == null) {
return false;
}
if (identifier == null) {
return false;
}
return identifier.equals(approvingObject.getApprovingAbility().getIdentifier());
}
}

View file

@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import mage.MageIdentifier;
/**
* @author BetaSteward_at_googlemail.com
@ -690,4 +691,15 @@ public class StackAbility extends StackObjImpl implements Ability {
|| (this.getOriginalId().equals(ability.getOriginalId()))
|| (this.getClass() == ability.getClass() && this.getRule().equals(ability.getRule()));
}
@Override
public MageIdentifier getIdentifier() {
return ability.getIdentifier();
}
@Override
public AbilityImpl setIdentifier(MageIdentifier identifier) {
throw new UnsupportedOperationException("Not supported.");
}
}

View file

@ -2,6 +2,7 @@ package mage.players;
import java.io.Serializable;
import java.util.*;
import mage.ApprovingObject;
import mage.MageItem;
import mage.MageObject;
import mage.MageObjectReference;
@ -105,9 +106,9 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Can the player pay life for spells or activated abilities
*
*
* @param Ability
* @return
* @return
*/
boolean canPayLifeCost(Ability Ability);
@ -319,7 +320,7 @@ public interface Player extends MageItem, Copyable<Player> {
int drawCards(int num, UUID sourceId, Game game, List<UUID> appliedEffects);
boolean cast(SpellAbility ability, Game game, boolean noMana, MageObjectReference reference);
boolean cast(SpellAbility ability, Game game, boolean noMana, ApprovingObject approvingObject);
SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana);
@ -367,17 +368,17 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Plays a card if possible
*
* @param card the card that can be cast
* @param card the card that can be cast
* @param game
* @param noMana if it's a spell i can be cast without paying mana
* @param ignoreTiming if it's cast during the resolution of another spell
* no sorcery or play land timing restriction are
* checked. For a land it has to be the turn of the
* player playing that card.
* @param reference mage object that allows to play the card
* @param noMana if it's a spell i can be cast without paying mana
* @param ignoreTiming if it's cast during the resolution of another
* spell no sorcery or play land timing restriction
* are checked. For a land it has to be the turn of
* the player playing that card.
* @param approvingObject reference to the ability that allows to play the card
* @return
*/
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference);
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, ApprovingObject approvingObject);
/**
* @param card the land card to play
@ -941,13 +942,15 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Set the mana colors the user can pay with 2 life instead
* @param colors
*
* @param colors
*/
void addPhyrexianToColors(FilterMana colors);
/**
* Mana colors the player can pay instead with 2 life
* @return
*
* @return
*/
FilterMana getPhyrexianColors();

View file

@ -70,6 +70,7 @@ import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.ApprovingObject;
public abstract class PlayerImpl implements Player, Serializable {
@ -1136,7 +1137,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference) {
public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, ApprovingObject approvingObject) {
if (card == null) {
return false;
}
@ -1144,7 +1145,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (card.isLand()) {
result = playLand(card, game, ignoreTiming);
} else {
result = cast(card.getSpellAbility(), game, noMana, reference);
result = cast(card.getSpellAbility(), game, noMana, approvingObject);
}
if (!result) {
game.informPlayer(this, "You can't play " + card.getIdName() + '.');
@ -1156,11 +1157,11 @@ public abstract class PlayerImpl implements Player, Serializable {
* @param originalAbility
* @param game
* @param noMana cast it without paying mana costs
* @param permittingObject which object permitted the cast
* @param approvingObject which object approved the cast
* @return
*/
@Override
public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, MageObjectReference permittingObject) {
public boolean cast(SpellAbility originalAbility, Game game, boolean noMana, ApprovingObject approvingObject) {
if (game == null || originalAbility == null) {
return false;
}
@ -1178,7 +1179,7 @@ public abstract class PlayerImpl implements Player, Serializable {
Card card = game.getCard(ability.getSourceId());
if (card != null) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL,
ability.getId(), ability.getSourceId(), playerId, permittingObject), ability)) {
ability.getId(), ability.getSourceId(), playerId, approvingObject), ability)) {
int bookmark = game.bookmarkState();
setStoredBookmark(bookmark); // move global bookmark to current state (if you activated mana before then you can't rollback it)
Zone fromZone = game.getState().getZone(card.getMainCard().getId());
@ -1213,11 +1214,11 @@ public abstract class PlayerImpl implements Player, Serializable {
clearCastSourceIdManaCosts(); // TODO: test multiple alternative cost for different cards as same time
GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL,
spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject);
spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, approvingObject);
game.fireEvent(event);
if (spell.activate(game, noMana)) {
event = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST,
spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject);
spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, approvingObject);
event.setZone(fromZone);
game.fireEvent(event);
if (!game.isSimulation()) {
@ -1281,19 +1282,19 @@ public abstract class PlayerImpl implements Player, Serializable {
//20091005 - 305.1
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND,
card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()))) {
card.getId(), card.getId(), playerId, activationStatus.getApprovingObject()))) {
// 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(), card.getId(), playerId, activationStatus.getPermittingObject());
card.getId(), card.getId(), playerId, activationStatus.getApprovingObject());
landEventBefore.setZone(cardZoneBefore);
game.fireEvent(landEventBefore);
if (moveCards(card, Zone.BATTLEFIELD, playLandAbility, game, false, false, false, null)) {
landsPlayed++;
GameEvent landEventAfter = GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED,
card.getId(), card.getId(), playerId, activationStatus.getPermittingObject());
card.getId(), card.getId(), playerId, activationStatus.getApprovingObject());
landEventAfter.setZone(cardZoneBefore);
game.fireEvent(landEventAfter);
game.fireInformEvent(getLogName() + " plays " + card.getLogName());
@ -1439,7 +1440,7 @@ public abstract class PlayerImpl implements Player, Serializable {
result = playManaAbility((ActivatedManaAbilityImpl) ability.copy(), game);
break;
case SPELL:
result = cast((SpellAbility) ability, game, false, activationStatus.getPermittingObject());
result = cast((SpellAbility) ability, game, false, activationStatus.getApprovingObject());
break;
default:
result = playAbility(ability.copy(), game);
@ -3145,7 +3146,7 @@ public abstract class PlayerImpl implements Player, Serializable {
addPhyrexianLikePayOptions(abilityOptions, availableMana, game);
}
MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(),
ApprovingObject approvingObject = game.getContinuousEffects().asThough(ability.getSourceId(),
AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
for (Mana mana : abilityOptions) {
if (mana.count() == 0) {
@ -3158,7 +3159,7 @@ public abstract class PlayerImpl implements Player, Serializable {
//
// add tests for non any color like Sunglasses of Urza
if (permittingObject != null && mana.count() <= avail.count()) {
if (approvingObject != null && mana.count() <= avail.count()) {
return true;
}
if (avail instanceof ConditionalMana && !((ConditionalMana) avail).apply(ability, game, getId(), ability.getManaCosts())) {
@ -3427,17 +3428,17 @@ public abstract class PlayerImpl implements Player, Serializable {
continue;
}
MageObjectReference permittingObject;
ApprovingObject approvingObject;
if (isPlaySpell || isPlayLand) {
// play hand from non hand zone
permittingObject = game.getContinuousEffects().asThough(object.getId(),
approvingObject = game.getContinuousEffects().asThough(object.getId(),
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game);
} else {
// other abilities from direct zones
permittingObject = null;
approvingObject = null;
}
boolean canActivateAsHandZone = permittingObject != null
boolean canActivateAsHandZone = approvingObject != null
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
boolean possibleToPlay = false;