* Added logic to add permitting object to play/cast events.

This commit is contained in:
LevelX2 2018-05-27 23:47:57 +02:00
parent b97a443a37
commit 27ced167fb
124 changed files with 1095 additions and 964 deletions

View file

@ -157,7 +157,7 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
return stream()
.filter(ability -> ability instanceof ActivatedManaAbilityImpl)
.filter(ability -> ability.getZone().match(zone))
.filter(ability -> (((ActivatedManaAbilityImpl) ability).canActivate(ability.getControllerId(), game)))
.filter(ability -> (((ActivatedManaAbilityImpl) ability).canActivate(ability.getControllerId(), game).canActivate()))
.map(ability -> (ActivatedManaAbilityImpl) ability)
.collect(Collectors.toCollection(AbilitiesImpl::new));

View file

@ -28,6 +28,7 @@
package mage.abilities;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.mana.ManaOptions;
import mage.constants.TargetController;
import mage.game.Game;
@ -38,9 +39,36 @@ import mage.game.Game;
*/
public interface ActivatedAbility extends Ability {
boolean canActivate(UUID playerId, Game game);
final public class ActivationStatus {
public void setMayActivate(TargetController mayActivate);
private final boolean canActivate;
private final MageObjectReference permittingObject;
public ActivationStatus(boolean canActivate, MageObjectReference permittingObject) {
this.canActivate = canActivate;
this.permittingObject = permittingObject;
}
public boolean canActivate() {
return canActivate;
}
public MageObjectReference getPermittingObject() {
return permittingObject;
}
public static ActivationStatus getFalse() {
return new ActivationStatus(false, null);
}
public static ActivationStatus getTrue() {
return new ActivationStatus(true, null);
}
}
ActivationStatus canActivate(UUID playerId, Game game); // has to return a reference to the permitting ability/source
void setMayActivate(TargetController mayActivate);
/**
* Returns the minimal possible cost for what the ability can be activated

View file

@ -29,6 +29,7 @@ package mage.abilities;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
@ -177,38 +178,39 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
public abstract ActivatedAbilityImpl copy();
@Override
public boolean canActivate(UUID playerId, Game game) {
public ActivationStatus canActivate(UUID playerId, Game game) {
//20091005 - 602.2
if (!(hasMoreActivationsThisTurn(game) && (condition == null || condition.apply(game, this)))) {
return false;
return ActivationStatus.getFalse();
}
switch (mayActivate) {
case ANY:
break;
case NOT_YOU:
if (controlsAbility(playerId, game)) {
return false;
return ActivationStatus.getFalse();
}
break;
case TEAM:
if (game.getPlayer(controllerId).hasOpponent(playerId, game)) {
return false;
return ActivationStatus.getFalse();
}
break;
case OPPONENT:
if (!game.getPlayer(controllerId).hasOpponent(playerId, game)) {
return false;
return ActivationStatus.getFalse();
}
break;
case OWNER:
Permanent permanent = game.getPermanent(getSourceId());
if (!permanent.getOwnerId().equals(playerId)) {
return false;
return ActivationStatus.getFalse();
}
break;
case YOU:
if (!controlsAbility(playerId, game)) {
return false;
return ActivationStatus.getFalse();
}
break;
case CONTROLLER_ATTACHED_TO:
@ -219,17 +221,17 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
break;
}
}
return false;
return ActivationStatus.getFalse();
}
//20091005 - 602.5d/602.5e
if (timing == TimingRule.INSTANT || game.canPlaySorcery(playerId)
|| null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_AS_INSTANT, this, controllerId, game)) {
MageObjectReference permittingObject = game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_AS_INSTANT, this, controllerId, game);
if (timing == TimingRule.INSTANT || game.canPlaySorcery(playerId) || null != permittingObject) {
if (costs.canPay(this, sourceId, playerId, game) && canChooseTarget(game)) {
this.activatorId = playerId;
return true;
return new ActivationStatus(true, permittingObject);
}
}
return false;
return ActivationStatus.getFalse();
}
@Override
@ -310,10 +312,12 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
return false;
}
@Override
public void setMaxActivationsPerTurn(int maxActivationsPerTurn) {
this.maxActivationsPerTurn = maxActivationsPerTurn;
}
@Override
public int getMaxActivationsPerTurn(Game game) {
return maxActivationsPerTurn;
}

View file

@ -28,6 +28,7 @@
package mage.abilities;
import java.util.UUID;
import mage.MageObjectReference;
import mage.constants.AbilityType;
import mage.constants.AsThoughEffectType;
import mage.constants.Zone;
@ -50,13 +51,13 @@ public class PlayLandAbility extends ActivatedAbilityImpl {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
if (!controlsAbility(playerId, game)
&& null == game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, playerId, game)) {
return false;
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) {
return ActivationStatus.getFalse();
}
//20091005 - 114.2a
return game.getActivePlayerId().equals(playerId) && game.getPlayer(playerId).canPlayLand() && game.canPlaySorcery(playerId);
return new ActivationStatus(game.getActivePlayerId().equals(playerId) && game.getPlayer(playerId).canPlayLand() && game.canPlaySorcery(playerId), permittingObject);
}
@Override

View file

@ -29,6 +29,7 @@ package mage.abilities;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.ManaCost;
@ -91,48 +92,49 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
public ActivationStatus canActivate(UUID playerId, Game game) {
if (null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
|| this.spellCanBeActivatedRegularlyNow(playerId, game)) {
if (spellAbilityType == SpellAbilityType.SPLIT || spellAbilityType == SpellAbilityType.SPLIT_AFTERMATH) {
return false;
return ActivationStatus.getFalse();
}
// fix for Gitaxian Probe and casting opponent's spells
if (null == game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, playerId, game)) {
MageObjectReference permittingSource = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, playerId, game);
if (permittingSource == null) {
Card card = game.getCard(sourceId);
if (!(card != null && card.getOwnerId().equals(playerId))) {
return false;
return ActivationStatus.getFalse();
}
}
// Check if rule modifying events prevent to cast the spell in check playable mode
if (this.isCheckPlayableMode()) {
if (game.getContinuousEffects().preventedByRuleModification(
GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, this.getId(), this.getSourceId(), playerId), this, game, true)) {
return false;
return ActivationStatus.getFalse();
}
}
// 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 && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) {
return false;
return ActivationStatus.getFalse();
}
}
if (costs.canPay(this, sourceId, controllerId, game)) {
if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard splitCard = (SplitCard) game.getCard(getSourceId());
if (splitCard != null) {
return (splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game)
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game));
return new ActivationStatus(splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game)
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game), null);
}
return false;
return ActivationStatus.getFalse();
} else {
return canChooseTarget(game);
return new ActivationStatus(canChooseTarget(game), permittingSource);
}
}
}
return false;
return ActivationStatus.getFalse();
}
@Override

View file

@ -1,16 +1,16 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
@ -20,18 +20,17 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.common;
import java.util.UUID;
import mage.constants.Zone;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.effects.common.PassEffect;
import mage.constants.Zone;
import mage.game.Game;
/**
@ -55,8 +54,8 @@ public class PassAbility extends ActivatedAbilityImpl {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
return true;
public ActivationStatus canActivate(UUID playerId, Game game) {
return ActivationStatus.getTrue();
}
@Override

View file

@ -63,7 +63,7 @@ public class TapSourceCost extends CostImpl {
Permanent permanent = game.getPermanent(sourceId);
if (permanent != null) {
return !permanent.isTapped()
&& (permanent.canTap() || null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_HASTE, controllerId, game));
&& (permanent.canTap() || null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game));
}
return false;
}

View file

@ -62,7 +62,7 @@ public class UntapSourceCost extends CostImpl {
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
Permanent permanent = game.getPermanent(sourceId);
if (permanent != null) {
return permanent.isTapped() && (permanent.canTap() || null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_HASTE, controllerId, game));
return permanent.isTapped() && (permanent.canTap() || null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game));
}
return false;
}

View file

@ -82,9 +82,9 @@ public class ConditionalGainActivatedAbility extends ActivatedAbilityImpl {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
public ActivationStatus canActivate(UUID playerId, Game game) {
if (!condition.apply(game, this)) {
return false;
return ActivationStatus.getFalse();
}
return super.canActivate(playerId, game);
}

View file

@ -32,6 +32,7 @@ import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
@ -502,19 +503,6 @@ public class ContinuousEffects implements Serializable {
return spliceEffects;
}
/**
*
* @param objectId
* @param type
* @param controllerId
* @param game
* @return sourceId of the permitting effect if any exists otherwise returns
* null
*/
public UUID asThough(UUID objectId, AsThoughEffectType type, UUID controllerId, Game game) {
return asThough(objectId, type, null, controllerId, game);
}
/**
*
* @param objectId
@ -525,17 +513,17 @@ public class ContinuousEffects implements Serializable {
* @return sourceId of the permitting effect if any exists otherwise returns
* null
*/
public UUID asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
public MageObjectReference asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);
for (AsThoughEffect effect : asThoughEffectsList) {
Set<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
for (Ability ability : abilities) {
if (affectedAbility == null) {
if (effect.applies(objectId, ability, controllerId, game)) {
return ability.getSourceId();
return new MageObjectReference(ability.getSourceObject(game), game);
}
} else if (effect.applies(objectId, affectedAbility, ability, game)) {
return ability.getSourceId();
return new MageObjectReference(ability.getSourceObject(game), game);
}
}
}

View file

@ -28,6 +28,7 @@
package mage.abilities.effects.common;
import java.util.Set;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
@ -88,7 +89,7 @@ public class CastCardFromOutsideTheGameEffect extends OneShotEffect {
}
return false;
}
Cards filteredCards = new CardsImpl();
for (Card card : filtered) {
filteredCards.add(card.getId());
@ -98,7 +99,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);
player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
}
}
}
@ -106,4 +107,4 @@ public class CastCardFromOutsideTheGameEffect extends OneShotEffect {
return true;
}
}
}

View file

@ -28,6 +28,7 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
@ -151,7 +152,7 @@ class CipherStoreEffect extends OneShotEffect {
}
ability.getEffects().remove(cipherEffect);
if (ability instanceof SpellAbility) {
controller.cast(ability, game, true);
controller.cast(ability, game, true, new MageObjectReference(source.getSourceObject(game), game));
}
}

View file

@ -27,6 +27,8 @@
*/
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
@ -36,8 +38,6 @@ import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author LevelX2
*
@ -82,7 +82,7 @@ public class HideawayPlayEffect extends OneShotEffect {
}
}
if (!controller.playCard(card, game, true, true)) {
if (!controller.playCard(card, game, true, true, new MageObjectReference(source.getSourceObject(game), game))) {
if (card.getZoneChangeCounter(game) == zcc) {
card.setFaceDown(true, game);
}

View file

@ -24,16 +24,16 @@
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
*/
package mage.abilities.effects.common;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
@ -62,7 +62,7 @@ public class PlayTargetWithoutPayingManaEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId());
Card target = (Card) game.getObject(source.getFirstTarget());
if (controller != null && target != null) {
return controller.cast(target.getSpellAbility(), game, true);
return controller.cast(target.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
}
return false;
}
@ -74,8 +74,7 @@ public class PlayTargetWithoutPayingManaEffect extends OneShotEffect {
Target target = mode.getTargets().get(0);
if (mode.getTargets().get(0).getZone() == Zone.HAND) {
sb.append("you may put ").append(target.getTargetName()).append(" from your hand onto the battlefield");
}
else {
} else {
sb.append("you may cast target ").append(target.getTargetName()).append(" without paying its mana cost");
}
}

View file

@ -27,6 +27,7 @@
*/
package mage.abilities.effects.common.cost;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
@ -101,7 +102,7 @@ public class CastWithoutPayingManaCostEffect extends OneShotEffect {
}
}
if (cardToCast != null) {
controller.cast(cardToCast.getSpellAbility(), game, true);
controller.cast(cardToCast.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
}
}
return true;

View file

@ -38,12 +38,15 @@ import mage.choices.ChoiceColor;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import org.apache.log4j.Logger;
/**
* @author noxx
*/
public class AddConditionalManaOfAnyColorEffect extends ManaEffect {
private static final Logger logger = Logger.getLogger(AddConditionalManaOfAnyColorEffect.class);
private final DynamicValue amount;
private final ConditionalManaBuilder manaBuilder;
private final boolean oneChoice;
@ -87,8 +90,13 @@ public class AddConditionalManaOfAnyColorEffect extends ManaEffect {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
checkToFirePossibleEvents(getMana(game, source), game, source);
controller.getManaPool().addMana(getMana(game, source), game, source);
Mana mana = getMana(game, source);
if (mana != null) {
checkToFirePossibleEvents(mana, game, source);
controller.getManaPool().addMana(mana, game, source);
} else {
logger.error("There was no mana created: " + source.getSourceObject(game).getName() + " - Ability: " + source.getRule());
}
return true;
}
return false;

View file

@ -1,158 +1,159 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.constants.AbilityType;
import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterPlayer;
import mage.filter.predicate.other.PlayerPredicate;
import mage.game.Game;
import mage.players.ManaPool;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPlayer;
/*
* @author emerald000
*/
public class AssistAbility extends SimpleStaticAbility implements AlternateManaPaymentAbility {
private static final FilterPlayer filter = new FilterPlayer("another player");
static {
filter.add(new PlayerPredicate(TargetController.NOT_YOU));
}
public AssistAbility() {
super(Zone.STACK, null);
this.setRuleAtTheTop(true);
}
public AssistAbility(final AssistAbility ability) {
super(ability);
}
@Override
public AssistAbility copy() {
return new AssistAbility(this);
}
@Override
public void addSpecialAction(Ability source, Game game, ManaCost unpaid) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null
&& source.getAbilityType() == AbilityType.SPELL
&& unpaid.getMana().getGeneric() >= 1
&& game.getState().getValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId())) == null) {
SpecialAction specialAction = new AssistSpecialAction(unpaid);
specialAction.setControllerId(source.getControllerId());
specialAction.setSourceId(source.getSourceId());
Target target = new TargetPlayer(1, 1, true, filter);
specialAction.addTarget(target);
if (specialAction.canActivate(source.getControllerId(), game)) {
game.getState().getSpecialActions().add(specialAction);
}
}
}
@Override
public String getRule() {
return "Assist <i>(Another player can help pay the generic mana of this spell's cost.)</i>";
}
}
class AssistSpecialAction extends SpecialAction {
AssistSpecialAction(ManaCost unpaid) {
super(Zone.ALL, true);
setRuleVisible(false);
this.addEffect(new AssistEffect(unpaid));
}
AssistSpecialAction(final AssistSpecialAction ability) {
super(ability);
}
@Override
public AssistSpecialAction copy() {
return new AssistSpecialAction(this);
}
}
class AssistEffect extends OneShotEffect {
private final ManaCost unpaid;
AssistEffect(ManaCost unpaid) {
super(Outcome.Benefit);
this.unpaid = unpaid;
this.staticText = "Assist <i>(Another player can help pay the generic mana of this spell's cost.)</i>";
}
AssistEffect(final AssistEffect effect) {
super(effect);
this.unpaid = effect.unpaid;
}
@Override
public AssistEffect copy() {
return new AssistEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Player targetPlayer = game.getPlayer(this.getTargetPointer().getFirst(game, source));
if (controller != null && targetPlayer != null) {
int amountToPay = targetPlayer.announceXMana(0, unpaid.getMana().getGeneric(), "How much mana to pay?", game, source);
if (amountToPay > 0) {
Cost cost = new GenericManaCost(amountToPay);
if (cost.pay(source, game, source.getSourceId(), targetPlayer.getId(), false)) {
ManaPool manaPool = controller.getManaPool();
manaPool.addMana(Mana.ColorlessMana(amountToPay), game, source);
manaPool.unlockManaType(ManaType.COLORLESS);
game.informPlayers(targetPlayer.getLogName() + " paid " + amountToPay + " mana.");
game.getState().setValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId()), true);
}
}
return true;
}
return false;
}
}
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.constants.AbilityType;
import mage.constants.ManaType;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterPlayer;
import mage.filter.predicate.other.PlayerPredicate;
import mage.game.Game;
import mage.players.ManaPool;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPlayer;
/*
* @author emerald000
*/
public class AssistAbility extends SimpleStaticAbility implements AlternateManaPaymentAbility {
private static final FilterPlayer filter = new FilterPlayer("another player");
static {
filter.add(new PlayerPredicate(TargetController.NOT_YOU));
}
public AssistAbility() {
super(Zone.STACK, null);
this.setRuleAtTheTop(true);
}
public AssistAbility(final AssistAbility ability) {
super(ability);
}
@Override
public AssistAbility copy() {
return new AssistAbility(this);
}
@Override
public void addSpecialAction(Ability source, Game game, ManaCost unpaid) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null
&& source.getAbilityType() == AbilityType.SPELL
&& unpaid.getMana().getGeneric() >= 1
&& game.getState().getValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId())) == null) {
SpecialAction specialAction = new AssistSpecialAction(unpaid);
specialAction.setControllerId(source.getControllerId());
specialAction.setSourceId(source.getSourceId());
Target target = new TargetPlayer(1, 1, true, filter);
specialAction.addTarget(target);
if (specialAction.canActivate(source.getControllerId(), game).canActivate()) {
game.getState().getSpecialActions().add(specialAction);
}
}
}
@Override
public String getRule() {
return "Assist <i>(Another player can help pay the generic mana of this spell's cost.)</i>";
}
}
class AssistSpecialAction extends SpecialAction {
AssistSpecialAction(ManaCost unpaid) {
super(Zone.ALL, true);
setRuleVisible(false);
this.addEffect(new AssistEffect(unpaid));
}
AssistSpecialAction(final AssistSpecialAction ability) {
super(ability);
}
@Override
public AssistSpecialAction copy() {
return new AssistSpecialAction(this);
}
}
class AssistEffect extends OneShotEffect {
private final ManaCost unpaid;
AssistEffect(ManaCost unpaid) {
super(Outcome.Benefit);
this.unpaid = unpaid;
this.staticText = "Assist <i>(Another player can help pay the generic mana of this spell's cost.)</i>";
}
AssistEffect(final AssistEffect effect) {
super(effect);
this.unpaid = effect.unpaid;
}
@Override
public AssistEffect copy() {
return new AssistEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Player targetPlayer = game.getPlayer(this.getTargetPointer().getFirst(game, source));
if (controller != null && targetPlayer != null) {
int amountToPay = targetPlayer.announceXMana(0, unpaid.getMana().getGeneric(), "How much mana to pay?", game, source);
if (amountToPay > 0) {
Cost cost = new GenericManaCost(amountToPay);
if (cost.pay(source, game, source.getSourceId(), targetPlayer.getId(), false)) {
ManaPool manaPool = controller.getManaPool();
manaPool.addMana(Mana.ColorlessMana(amountToPay), game, source);
manaPool.unlockManaType(ManaType.COLORLESS);
game.informPlayers(targetPlayer.getLogName() + " paid " + amountToPay + " mana.");
game.getState().setValue(source.getSourceId().toString() + game.getState().getZoneChangeCounter(source.getSourceId()), true);
}
}
return true;
}
return false;
}
}

View file

@ -27,6 +27,7 @@
*/
package mage.abilities.keyword;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
@ -124,7 +125,7 @@ class CascadeEffect extends OneShotEffect {
if (card != null) {
if (controller.chooseUse(outcome, "Use cascade effect on " + card.getLogName() + '?', source, game)) {
controller.cast(card.getSpellAbility(), game, true);
controller.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
}
}
// Move the remaining cards to the buttom of the library in a random order

View file

@ -145,7 +145,7 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana
Target target = new TargetControlledCreaturePermanent(1, 1, filter, true);
target.setTargetName("creature to convoke");
specialAction.addTarget(target);
if (specialAction.canActivate(source.getControllerId(), game)) {
if (specialAction.canActivate(source.getControllerId(), game).canActivate()) {
game.getState().getSpecialActions().add(specialAction);
}
}

View file

@ -110,7 +110,7 @@ public class DelveAbility extends SimpleStaticAbility implements AlternateManaPa
}
specialAction.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(
0, Math.min(controller.getGraveyard().size(), unpaidAmount), new FilterCard(), true)));
if (specialAction.canActivate(source.getControllerId(), game)) {
if (specialAction.canActivate(source.getControllerId(), game).canActivate()) {
game.getState().getSpecialActions().add(specialAction);
}
}

View file

@ -1,140 +1,140 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import java.util.UUID;
import mage.Mana;
import mage.abilities.SpellAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.mana.ManaOptions;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.CardIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.util.CardUtil;
/**
*
* @author emerald000
*/
public class EmergeAbility extends SpellAbility {
private final ManaCosts<ManaCost> emergeCost;
public EmergeAbility(Card card, ManaCosts<ManaCost> emergeCost) {
super(emergeCost, card.getName() + " with emerge", Zone.HAND, SpellAbilityType.BASE_ALTERNATE);
this.getCosts().addAll(card.getSpellAbility().getCosts().copy());
this.getEffects().addAll(card.getSpellAbility().getEffects().copy());
this.getTargets().addAll(card.getSpellAbility().getTargets().copy());
this.timing = card.getSpellAbility().getTiming();
this.setRuleAtTheTop(true);
this.emergeCost = emergeCost.copy();
}
public EmergeAbility(final EmergeAbility ability) {
super(ability);
this.emergeCost = ability.emergeCost.copy();
}
@Override
public boolean canActivate(UUID playerId, Game game) {
if (super.canActivate(playerId, game)) {
Player controller = game.getPlayer(this.getControllerId());
if (controller != null) {
for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) {
ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost());
if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) {
return true;
}
}
}
}
return false;
}
@Override
public ManaOptions getMinimumCostToActivate(UUID playerId, Game game) {
int maxCMC = 0;
for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), playerId, this.getSourceId(), game)) {
int cmc = creature.getConvertedManaCost();
if (cmc > maxCMC) {
maxCMC = cmc;
}
}
ManaOptions manaOptions = super.getMinimumCostToActivate(playerId, game);
for (Mana mana : manaOptions) {
if (mana.getGeneric() > maxCMC) {
mana.setGeneric(mana.getGeneric() - maxCMC);
} else {
mana.setGeneric(0);
}
}
return manaOptions;
}
@Override
public boolean activate(Game game, boolean noMana) {
Player controller = game.getPlayer(this.getControllerId());
if (controller != null) {
TargetPermanent target = new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent("creature to sacrifice for emerge"));
if (controller.choose(Outcome.Sacrifice, target, this.getSourceId(), game)) {
Permanent creature = game.getPermanent(target.getFirstTarget());
CardUtil.reduceCost(this, creature.getConvertedManaCost());
FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(creature.getLogName());
filter.add(new CardIdPredicate(creature.getId()));
this.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(filter)));
return super.activate(game, false);
}
}
return false;
}
@Override
public EmergeAbility copy() {
return new EmergeAbility(this);
}
@Override
public String getRule(boolean all) {
return getRule();
}
@Override
public String getRule() {
return "Emerge " + emergeCost.getText() + " <i>(You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's converted mana cost.)</i>";
}
}
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import java.util.UUID;
import mage.Mana;
import mage.abilities.SpellAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.mana.ManaOptions;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.CardIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.util.CardUtil;
/**
*
* @author emerald000
*/
public class EmergeAbility extends SpellAbility {
private final ManaCosts<ManaCost> emergeCost;
public EmergeAbility(Card card, ManaCosts<ManaCost> emergeCost) {
super(emergeCost, card.getName() + " with emerge", Zone.HAND, SpellAbilityType.BASE_ALTERNATE);
this.getCosts().addAll(card.getSpellAbility().getCosts().copy());
this.getEffects().addAll(card.getSpellAbility().getEffects().copy());
this.getTargets().addAll(card.getSpellAbility().getTargets().copy());
this.timing = card.getSpellAbility().getTiming();
this.setRuleAtTheTop(true);
this.emergeCost = emergeCost.copy();
}
public EmergeAbility(final EmergeAbility ability) {
super(ability);
this.emergeCost = ability.emergeCost.copy();
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
if (super.canActivate(playerId, game).canActivate()) {
Player controller = game.getPlayer(this.getControllerId());
if (controller != null) {
for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), this.getControllerId(), this.getSourceId(), game)) {
ManaCost costToPay = CardUtil.reduceCost(emergeCost.copy(), creature.getConvertedManaCost());
if (costToPay.canPay(this, this.getSourceId(), this.getControllerId(), game)) {
return ActivationStatus.getTrue();
}
}
}
}
return ActivationStatus.getFalse();
}
@Override
public ManaOptions getMinimumCostToActivate(UUID playerId, Game game) {
int maxCMC = 0;
for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), playerId, this.getSourceId(), game)) {
int cmc = creature.getConvertedManaCost();
if (cmc > maxCMC) {
maxCMC = cmc;
}
}
ManaOptions manaOptions = super.getMinimumCostToActivate(playerId, game);
for (Mana mana : manaOptions) {
if (mana.getGeneric() > maxCMC) {
mana.setGeneric(mana.getGeneric() - maxCMC);
} else {
mana.setGeneric(0);
}
}
return manaOptions;
}
@Override
public boolean activate(Game game, boolean noMana) {
Player controller = game.getPlayer(this.getControllerId());
if (controller != null) {
TargetPermanent target = new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent("creature to sacrifice for emerge"));
if (controller.choose(Outcome.Sacrifice, target, this.getSourceId(), game)) {
Permanent creature = game.getPermanent(target.getFirstTarget());
CardUtil.reduceCost(this, creature.getConvertedManaCost());
FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(creature.getLogName());
filter.add(new CardIdPredicate(creature.getId()));
this.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(filter)));
return super.activate(game, false);
}
}
return false;
}
@Override
public EmergeAbility copy() {
return new EmergeAbility(this);
}
@Override
public String getRule(boolean all) {
return getRule();
}
@Override
public String getRule() {
return "Emerge " + emergeCost.getText() + " <i>(You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's converted mana cost.)</i>";
}
}

View file

@ -27,6 +27,7 @@
*/
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.effects.common.AttachEffect;
@ -39,8 +40,6 @@ import mage.game.permanent.Permanent;
import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -57,14 +56,15 @@ public class EquipAbility extends ActivatedAbilityImpl {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
if (super.canActivate(playerId, game)) {
public ActivationStatus canActivate(UUID playerId, Game game) {
ActivationStatus activationStatus = super.canActivate(playerId, game);
if (activationStatus.canActivate()) {
Permanent permanent = game.getPermanent(sourceId);
if (permanent != null && permanent.hasSubtype(SubType.EQUIPMENT, game)) {
return true;
return activationStatus;
}
}
return false;
return activationStatus;
}
public EquipAbility(final EquipAbility ability) {

View file

@ -1,96 +1,95 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.effects.common.AttachEffect;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.SupertypePredicate;
/**
* @author Rystan
*/
public class EquipLegendaryAbility extends ActivatedAbilityImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("legendary creature you control");
static {
filter.add(new SupertypePredicate(SuperType.LEGENDARY));
}
public EquipLegendaryAbility(Outcome outcome, Cost cost) {
this(outcome, cost, new TargetControlledCreaturePermanent(filter));
}
public EquipLegendaryAbility(Outcome outcome, Cost cost, Target target) {
super(Zone.BATTLEFIELD, new AttachEffect(outcome, "Equip"), cost);
this.addTarget(target);
this.timing = TimingRule.SORCERY;
}
@Override
public boolean canActivate(UUID playerId, Game game) {
if (super.canActivate(playerId, game)) {
Permanent permanent = game.getPermanent(sourceId);
if (permanent != null && permanent.hasSubtype(SubType.EQUIPMENT, game)) {
return true;
}
}
return false;
}
public EquipLegendaryAbility(final EquipLegendaryAbility ability) {
super(ability);
}
@Override
public EquipLegendaryAbility copy() {
return new EquipLegendaryAbility(this);
}
@Override
public String getRule() {
return "Equip legendary creature " + costs.getText() +
manaCosts.getText() + " (" + manaCosts.getText() +
": <i>Attach to target legendary creature you control. Equip only as a sorcery.)</i>";
}
}
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.effects.common.AttachEffect;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.SupertypePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent;
/**
* @author Rystan
*/
public class EquipLegendaryAbility extends ActivatedAbilityImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("legendary creature you control");
static {
filter.add(new SupertypePredicate(SuperType.LEGENDARY));
}
public EquipLegendaryAbility(Outcome outcome, Cost cost) {
this(outcome, cost, new TargetControlledCreaturePermanent(filter));
}
public EquipLegendaryAbility(Outcome outcome, Cost cost, Target target) {
super(Zone.BATTLEFIELD, new AttachEffect(outcome, "Equip"), cost);
this.addTarget(target);
this.timing = TimingRule.SORCERY;
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
ActivationStatus activationStatus = super.canActivate(playerId, game);
if (activationStatus.canActivate()) {
Permanent permanent = game.getPermanent(sourceId);
if (permanent != null && permanent.hasSubtype(SubType.EQUIPMENT, game)) {
return activationStatus;
}
}
return activationStatus;
}
public EquipLegendaryAbility(final EquipLegendaryAbility ability) {
super(ability);
}
@Override
public EquipLegendaryAbility copy() {
return new EquipLegendaryAbility(this);
}
@Override
public String getRule() {
return "Equip legendary creature " + costs.getText()
+ manaCosts.getText() + " (" + manaCosts.getText()
+ ": <i>Attach to target legendary creature you control. Equip only as a sorcery.)</i>";
}
}

View file

@ -83,13 +83,14 @@ public class FlashbackAbility extends SpellAbility {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
if (super.canActivate(playerId, game)) {
public ActivationStatus canActivate(UUID playerId, Game game) {
ActivationStatus activationStatus = super.canActivate(playerId, game);
if (activationStatus.canActivate()) {
Card card = game.getCard(getSourceId());
if (card != null) {
// Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision)
if (card.getManaCost().isEmpty()) {
return false;
return ActivationStatus.getFalse();
}
// Flashback can never cast a split card by Fuse, because Fuse only works from hand
if (card.isSplitCard()) {
@ -102,7 +103,7 @@ public class FlashbackAbility extends SpellAbility {
return card.getSpellAbility().canActivate(playerId, game);
}
}
return false;
return activationStatus;
}
@Override

View file

@ -43,10 +43,10 @@ import mage.game.Game;
*
* 702.56b A forecast ability may be activated only during the upkeep step of
* the card's owner and only once each turn. The controller of the forecast
* ability reveals the card with that ability from their hand as the
* ability is activated. That player plays with that card revealed in their
* hand until it leaves the player's hand or until a step or phase that isn't an
* upkeep step begins, whichever comes first.
* ability reveals the card with that ability from their hand as the ability is
* activated. That player plays with that card revealed in their hand until it
* leaves the player's hand or until a step or phase that isn't an upkeep step
* begins, whichever comes first.
*
* @author LevelX2
*
@ -68,11 +68,11 @@ public class ForecastAbility extends LimitedTimesPerTurnActivatedAbility {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
public ActivationStatus canActivate(UUID playerId, Game game) {
// May be activated only during the upkeep step of the card's owner
// Because it can only be activated from a players hand it should be ok to check here with controllerId instead of card.getOwnerId().
if (!game.getActivePlayerId().equals(controllerId) || PhaseStep.UPKEEP != game.getStep().getType()) {
return false;
return ActivationStatus.getFalse();
}
return super.canActivate(playerId, game);
}

View file

@ -6,7 +6,6 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
@ -69,7 +68,7 @@ public class ImproviseAbility extends SimpleStaticAbility implements AlternateMa
Target target = new TargetControlledPermanent(1, unpaid.getMana().getGeneric(), filter, true);
target.setTargetName("artifact to Improvise");
specialAction.addTarget(target);
if (specialAction.canActivate(source.getControllerId(), game)) {
if (specialAction.canActivate(source.getControllerId(), game).canActivate()) {
game.getState().getSpecialActions().add(specialAction);
}
}

View file

@ -2,6 +2,7 @@ package mage.abilities.keyword;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
@ -33,10 +34,10 @@ import mage.players.Player;
* first ability is applied.
*
* "Madness [cost]" means "If a player would discard this card, that player
* discards it, but may exile it instead of putting it into their
* graveyard" and "When this card is exiled this way, its owner may cast it by
* paying [cost] rather than paying its mana cost. If that player doesn't, he or
* she puts this card into their graveyard.
* discards it, but may exile it instead of putting it into their graveyard" and
* "When this card is exiled this way, its owner may cast it by paying [cost]
* rather than paying its mana cost. If that player doesn't, he or she puts this
* card into their graveyard.
*
* 702.33b. Casting a spell using its madness ability follows the rules for
* paying alternative costs in rules 601.2b and 601.2e-g.
@ -219,7 +220,7 @@ class MadnessCastEffect extends OneShotEffect {
castByMadness.setSpellAbilityCastMode(SpellAbilityCastMode.MADNESS);
costRef.clear();
costRef.add(madnessCost);
boolean result = owner.cast(castByMadness, game, false);
boolean result = owner.cast(castByMadness, game, false, new MageObjectReference(source.getSourceObject(game), game));
return result;
}

View file

@ -27,6 +27,7 @@
*/
package mage.abilities.keyword;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbilityImpl;
@ -52,8 +53,8 @@ import mage.watchers.common.MiracleWatcher;
* cost."
*
* 702.92b If a player chooses to reveal a card using its miracle ability, he or
* she plays with that card revealed until that card leaves their hand,
* that ability resolves, or that ability otherwise leaves the stack.
* she plays with that card revealed until that card leaves their hand, that
* ability resolves, or that ability otherwise leaves the stack.
*
* You can cast a card for its miracle cost only as the miracle triggered
* ability resolves. If you don't want to cast it at that time (or you can't
@ -172,7 +173,7 @@ class MiracleEffect extends OneShotEffect {
// replace with the new cost
costRef.clear();
costRef.add(miracleCosts);
controller.cast(abilityToCast, game, false);
controller.cast(abilityToCast, game, false, new MageObjectReference(source.getSourceObject(game), game));
return true;
}
return false;

View file

@ -28,6 +28,7 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.SpellAbility;
@ -206,7 +207,7 @@ class ReboundCastSpellFromExileEffect extends OneShotEffect {
Player player = game.getPlayer(source.getControllerId());
if (player != null && reboundCard != null) {
SpellAbility ability = reboundCard.getSpellAbility();
player.cast(ability, game, true);
player.cast(ability, game, true, new MageObjectReference(source.getSourceObject(game), game));
zone.remove(reboundCard.getId());
return true;
}

View file

@ -1,6 +1,7 @@
package mage.abilities.keyword;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
@ -103,7 +104,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);
player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game));
cards.remove(card);
}
target1.clearChosen();

View file

@ -27,6 +27,7 @@
*/
package mage.abilities.keyword;
import java.util.Iterator;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
@ -42,63 +43,66 @@ import mage.game.Game;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.Iterator;
/**
* 702.45. Splice
*
* 702.45a Splice is a static ability that functions while a card is in your hand.
* "Splice onto [subtype] [cost]" means "You may reveal this card from your hand
* as you cast a [subtype] spell. If you do, copy this card's text box onto that
* spell and pay [cost] as an additional cost to cast that spell." Paying a card's
* splice cost follows the rules for paying additional costs in rules 601.2b and
* 601.2e-g.
* 702.45a Splice is a static ability that functions while a card is in your
* hand. "Splice onto [subtype] [cost]" means "You may reveal this card from
* your hand as you cast a [subtype] spell. If you do, copy this card's text box
* onto that spell and pay [cost] as an additional cost to cast that spell."
* Paying a card's splice cost follows the rules for paying additional costs in
* rules 601.2b and 601.2e-g.
*
* Example: Since the card with splice remains in the player's hand, it can later
* be cast normally or spliced onto another spell. It can even be discarded to pay
* a "discard a card" cost of the spell it's spliced onto.
* Example: Since the card with splice remains in the player's hand, it can
* later be cast normally or spliced onto another spell. It can even be
* discarded to pay a "discard a card" cost of the spell it's spliced onto.
*
* 702.45b You can't choose to use a splice ability if you can't make the required
* choices (targets, etc.) for that card's instructions. You can't splice any one
* card onto the same spell more than once. If you're splicing more than one card
* onto a spell, reveal them all at once and choose the order in which their
* instructions will be followed. The instructions on the main spell have to be
* followed first.
* 702.45b You can't choose to use a splice ability if you can't make the
* required choices (targets, etc.) for that card's instructions. You can't
* splice any one card onto the same spell more than once. If you're splicing
* more than one card onto a spell, reveal them all at once and choose the order
* in which their instructions will be followed. The instructions on the main
* spell have to be followed first.
*
* 702.45c The spell has the characteristics of the main spell, plus the text boxes
* of each of the spliced cards. The spell doesn't gain any other characteristics
* (name, mana cost, color, supertypes, card types, subtypes, etc.) of the spliced
* cards. Text copied onto the spell that refers to a card by name refers to the spell
* on the stack, not the card from which the text was copied.
* 702.45c The spell has the characteristics of the main spell, plus the text
* boxes of each of the spliced cards. The spell doesn't gain any other
* characteristics (name, mana cost, color, supertypes, card types, subtypes,
* etc.) of the spliced cards. Text copied onto the spell that refers to a card
* by name refers to the spell on the stack, not the card from which the text
* was copied.
*
* Example: Glacial Ray is a red card with splice onto Arcane that reads, "Glacial
* Ray deals 2 damage to any target." Suppose Glacial Ray is spliced
* onto Reach Through Mists, a blue spell. The spell is still blue, and Reach Through
* Mists deals the damage. This means that the ability can target a creature with
* protection from red and deal 2 damage to that creature.
* Example: Glacial Ray is a red card with splice onto Arcane that reads,
* "Glacial Ray deals 2 damage to any target." Suppose Glacial Ray is spliced
* onto Reach Through Mists, a blue spell. The spell is still blue, and Reach
* Through Mists deals the damage. This means that the ability can target a
* creature with protection from red and deal 2 damage to that creature.
*
* 702.45d Choose targets for the added text normally (see rule 601.2c). Note that a
* spell with one or more targets will be countered if all of its targets are illegal
* on resolution.
* 702.45d Choose targets for the added text normally (see rule 601.2c). Note
* that a spell with one or more targets will be countered if all of its targets
* are illegal on resolution.
*
* 702.45e The spell loses any splice changes once it leaves the stack (for example,
* when it's countered, it's exiled, or it resolves).
* 702.45e The spell loses any splice changes once it leaves the stack (for
* example, when it's countered, it's exiled, or it resolves).
*
* Rulings
* Rulings
*
* You must reveal all of the cards you intend to splice at the same time. Each individual card can only be spliced once onto a spell.
* If you have more than one card with the same name in your hand, you may splice both of them onto the spell.
* A card with a splice ability can't be spliced onto itself because the spell is on the stack (and not in your hand) when you reveal the cards you want to splice onto it.
* The target for a card that's spliced onto a spell may be the same as the target chosen for the original spell or for another spliced-on card. (A recent change to the targeting rules allows this, but most other cards are unaffected by the change.)
* If you splice a targeted card onto an untargeted spell, the entire spell will be countered if the target isn't legal when the spell resolves.
* If you splice an untargeted card onto a targeted spell, the entire spell will be countered if the target isn't legal when the spell resolves.
* A spell is countered on resolution only if *all* of its targets are illegal (or the spell is countered by an effect).
* You must reveal all of the cards you intend to splice at the same time. Each
* individual card can only be spliced once onto a spell. If you have more than
* one card with the same name in your hand, you may splice both of them onto
* the spell. A card with a splice ability can't be spliced onto itself because
* the spell is on the stack (and not in your hand) when you reveal the cards
* you want to splice onto it. The target for a card that's spliced onto a spell
* may be the same as the target chosen for the original spell or for another
* spliced-on card. (A recent change to the targeting rules allows this, but
* most other cards are unaffected by the change.) If you splice a targeted card
* onto an untargeted spell, the entire spell will be countered if the target
* isn't legal when the spell resolves. If you splice an untargeted card onto a
* targeted spell, the entire spell will be countered if the target isn't legal
* when the spell resolves. A spell is countered on resolution only if *all* of
* its targets are illegal (or the spell is countered by an effect).
*
* @author LevelX2
*/
public class SpliceOntoArcaneAbility extends SimpleStaticAbility {
private static final String KEYWORD_TEXT = "Splice onto Arcane";
@ -134,8 +138,8 @@ public class SpliceOntoArcaneAbility extends SimpleStaticAbility {
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
sb.append(KEYWORD_TEXT).append(nonManaCosts?"-":" ");
sb.append(spliceCosts.getText()).append(nonManaCosts?". ":" ");
sb.append(KEYWORD_TEXT).append(nonManaCosts ? "-" : " ");
sb.append(spliceCosts.getText()).append(nonManaCosts ? ". " : " ");
sb.append("<i>(As you cast an Arcane spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell.)</i>");
return sb.toString();
}
@ -193,7 +197,7 @@ class SpliceOntoArcaneEffect extends SpliceCardEffectImpl {
if (card.getManaCost().isEmpty()) { // e.g. Evermind
return card.getSpellAbility().spellCanBeActivatedRegularlyNow(source.getControllerId(), game);
} else {
return card.getSpellAbility().canActivate(source.getControllerId(), game);
return card.getSpellAbility().canActivate(source.getControllerId(), game).canActivate();
}
}
return false;

View file

@ -66,7 +66,7 @@ public class SurgeAbility extends SpellAbility {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
public ActivationStatus canActivate(UUID playerId, Game game) {
// check if controller or teammate has already cast a spell this turn
CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getSimpleName());
if (watcher != null) {
@ -81,7 +81,7 @@ public class SurgeAbility extends SpellAbility {
}
}
}
return false;
return ActivationStatus.getFalse();
}
@Override

View file

@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
import mage.abilities.TriggeredAbilityImpl;
@ -228,16 +229,16 @@ public class SuspendAbility extends SpecialAction {
}
@Override
public boolean canActivate(UUID playerId, Game game) {
public ActivationStatus canActivate(UUID playerId, Game game) {
if (game.getState().getZone(getSourceId()) != Zone.HAND) {
// Supend can only be activated from hand
return false;
return ActivationStatus.getFalse();
}
MageObject object = game.getObject(sourceId);
return (object.isInstant()
return new ActivationStatus(object.isInstant()
|| object.hasAbility(FlashAbility.getInstance().getId(), game)
|| null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game)
|| game.canPlaySorcery(playerId));
|| game.canPlaySorcery(playerId), null);
}
@Override
@ -376,7 +377,7 @@ class SuspendPlayCardEffect extends OneShotEffect {
card.getAbilities().removeAll(abilitiesToRemove);
}
// cast the card for free
if (player.cast(card.getSpellAbility(), game, true)) {
if (player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game))) {
if (card.isCreature()) {
ContinuousEffect effect = new GainHasteEffect();
effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game) + 1));

View file

@ -58,7 +58,7 @@ public class ActivateOncePerTurnManaAbility extends ActivatedManaAbilityImpl {
@Override
public boolean activate(Game game, boolean noMana) {
if (canActivate(this.controllerId, game)) {
if (canActivate(this.controllerId, game).canActivate()) {
return super.activate(game, noMana);
}
return false;

View file

@ -69,21 +69,21 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
}
@Override
public boolean canActivate(UUID playerId, Game game) {
public ActivationStatus canActivate(UUID playerId, Game game) {
if (!super.hasMoreActivationsThisTurn(game) || !(condition == null || condition.apply(game, this))) {
return false;
return ActivationStatus.getFalse();
}
if (!controlsAbility(playerId, game)) {
return false;
return ActivationStatus.getFalse();
}
if (timing == TimingRule.SORCERY
&& !game.canPlaySorcery(playerId)
&& null == game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_AS_INSTANT, this, controllerId, game)) {
return false;
return ActivationStatus.getFalse();
}
// check if player is in the process of playing spell costs and he is no longer allowed to use activated mana abilities (e.g. because he started to use improvise)
//20091005 - 605.3a
return costs.canPay(this, sourceId, controllerId, game);
return new ActivationStatus(costs.canPay(this, sourceId, controllerId, game), null);
}

View file

@ -61,7 +61,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, input.getControllerId(), game);
return !input.isTapped() || null != game.getState().getContinuousEffects().asThough(input.getId(), AsThoughEffectType.BLOCK_TAPPED, null, input.getControllerId(), game);
}
@Override

View file

@ -31,6 +31,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.MageObjectReference;
import mage.constants.Zone;
/**
@ -48,6 +49,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 UUID customEventType = null;
public enum EventType {
@ -232,7 +234,7 @@ public class GameEvent implements Serializable {
FLIP_COIN, COIN_FLIPPED, SCRY, FATESEAL,
ROLL_DICE, DICE_ROLLED,
ROLL_PLANAR_DIE, PLANAR_DIE_ROLLED,
PLANESWALK, PLANESWALKED,
PLANESWALK, PLANESWALKED,
PAID_CUMULATIVE_UPKEEP,
DIDNT_PAY_CUMULATIVE_UPKEEP,
//permanent events
@ -334,21 +336,14 @@ public class GameEvent implements Serializable {
CUSTOM_EVENT
}
private GameEvent(EventType type, UUID customEventType,
UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag) {
this.type = type;
this.customEventType = customEventType;
this.targetId = targetId;
this.sourceId = sourceId;
this.amount = amount;
this.playerId = playerId;
this.flag = flag;
}
public GameEvent(EventType type, UUID targetId, UUID sourceId, UUID playerId) {
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, int amount, boolean flag) {
this(type, null, targetId, sourceId, playerId, amount, flag);
}
@ -369,6 +364,10 @@ 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 playerId) {
return new GameEvent(type, targetId, null, playerId);
}
@ -399,6 +398,23 @@ public class GameEvent implements Serializable {
return event;
}
private GameEvent(EventType type, UUID customEventType,
UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag) {
this(type, customEventType, targetId, sourceId, playerId, amount, flag, null);
}
private GameEvent(EventType type, UUID customEventType,
UUID targetId, UUID sourceId, UUID playerId, int amount, boolean flag, MageObjectReference reference) {
this.type = type;
this.customEventType = customEventType;
this.targetId = targetId;
this.sourceId = sourceId;
this.amount = amount;
this.playerId = playerId;
this.flag = flag;
this.reference = reference;
}
public EventType getType() {
return type;
}
@ -455,6 +471,14 @@ public class GameEvent implements Serializable {
this.zone = zone;
}
public MageObjectReference getAdditionalReference() {
return reference;
}
public void setAdditionalReference(MageObjectReference additionalReference) {
this.reference = additionalReference;
}
/**
* used to store which replacement effects were already applied to an event
* or or any modified events that may replace it

View file

@ -935,20 +935,20 @@ 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, sourceControllerId, game)) {
if (null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, null, sourceControllerId, game)) {
return false;
}
}
if (abilities.containsKey(HexproofAbility.getInstance().getId())) {
if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, sourceControllerId, game)) {
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)) {
return false;
}
}
if (abilities.containsKey(HexproofFromBlackAbility.getInstance().getId())) {
if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& source.getColor(game).isBlack()) {
return false;
}
@ -956,7 +956,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
if (abilities.containsKey(HexproofFromWhiteAbility.getInstance().getId())) {
if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game)
&& source.getColor(game).isWhite()) {
return false;
}
@ -1096,7 +1096,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public boolean canAttackInPrinciple(UUID defenderId, Game game) {
if (hasSummoningSickness()
&& null == game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK_AS_HASTE, this.getControllerId(), game)) {
&& null == game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK_AS_HASTE, null, this.getControllerId(), game)) {
return false;
}
//20101001 - 508.1c
@ -1116,7 +1116,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
return !abilities.containsKey(DefenderAbility.getInstance().getId())
|| null != game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK, this.getControllerId(), game);
|| null != game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK, null, this.getControllerId(), game);
}
private boolean canAttackCheckRestrictionEffects(UUID defenderId, Game game) {
@ -1136,7 +1136,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public boolean canBlock(UUID attackerId, Game game) {
if (tapped && null == game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, this.getControllerId(), game)) {
if (tapped && null == game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game)) {
return false;
}
Permanent attacker = game.getPermanent(attackerId);
@ -1169,7 +1169,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, this.getControllerId(), game)) {
if (tapped && null == game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game)) {
return false;
}

View file

@ -36,6 +36,7 @@ import java.util.Set;
import java.util.UUID;
import mage.MageItem;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Abilities;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
@ -359,7 +360,7 @@ public interface Player extends MageItem, Copyable<Player> {
int drawCards(int num, Game game, List<UUID> appliedEffects);
boolean cast(SpellAbility ability, Game game, boolean noMana);
boolean cast(SpellAbility ability, Game game, boolean noMana, MageObjectReference reference);
SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana);
@ -399,7 +400,7 @@ public interface Player extends MageItem, Copyable<Player> {
* to be the turn of the player playing that card.
* @return
*/
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming);
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference);
/**
*

View file

@ -33,8 +33,10 @@ import java.util.*;
import java.util.Map.Entry;
import mage.ConditionalMana;
import mage.MageObject;
import mage.MageObjectReference;
import mage.Mana;
import mage.abilities.*;
import mage.abilities.ActivatedAbility.ActivationStatus;
import mage.abilities.common.PassAbility;
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
@ -610,7 +612,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
if (abilities.containsKey(HexproofAbility.getInstance().getId())) {
if (sourceControllerId != null && this.hasOpponent(sourceControllerId, game)
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, this.getId(), game)) {
&& null == game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, this.getId(), game)) {
return false;
}
}
@ -1010,7 +1012,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming) {
public boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference) {
if (card == null) {
return false;
}
@ -1018,7 +1020,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (card.isLand()) {
result = playLand(card, game, ignoreTiming);
} else {
result = cast(card.getSpellAbility(), game, noMana);
result = cast(card.getSpellAbility(), game, noMana, reference);
}
if (!result) {
game.informPlayer(this, "You can't play " + card.getIdName() + '.');
@ -1027,7 +1029,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
public boolean cast(SpellAbility ability, Game game, boolean noMana) {
public boolean cast(SpellAbility ability, Game game, boolean noMana, MageObjectReference permittingObject) {
if (game == null || ability == null) {
return false;
}
@ -1046,7 +1048,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), ability)) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, ability.getId(), ability.getSourceId(), playerId, permittingObject), ability)) {
int bookmark = game.bookmarkState();
Zone fromZone = game.getState().getZone(card.getMainCard().getId());
card.cast(game, fromZone, ability, playerId);
@ -1074,10 +1076,10 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
setCastSourceIdWithAlternateMana(null, null, null);
GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId);
GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject);
game.fireEvent(event);
if (spell.activate(game, noMana)) {
event = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId);
event = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject);
event.setZone(fromZone);
game.fireEvent(event);
if (!game.isSimulation()) {
@ -1116,7 +1118,7 @@ public abstract class PlayerImpl implements Player, Serializable {
SpellAbility spellAbility = new SpellAbility(null, "", game.getState().getZone(card.getId()), SpellAbilityType.FACE_DOWN_CREATURE);
spellAbility.setControllerId(this.getId());
spellAbility.setSourceId(card.getId());
if (cast(spellAbility, game, false)) {
if (cast(spellAbility, game, false, null)) {
return true;
}
}
@ -1124,17 +1126,18 @@ public abstract class PlayerImpl implements Player, Serializable {
return false;
}
//20091005 - 114.2a
if (!ignoreTiming && !playLandAbility.canActivate(this.playerId, game)) {
ActivationStatus activationStatus = playLandAbility.canActivate(this.playerId, game);
if (!ignoreTiming && !activationStatus.canActivate()) {
return false;
}
//20091005 - 305.1
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId))) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()))) {
// int bookmark = game.bookmarkState();
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId));
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()));
if (moveCards(card, Zone.BATTLEFIELD, playLandAbility, game, false, false, false, null)) {
landsPlayed++;
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED, card.getId(), card.getId(), playerId));
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LAND_PLAYED, card.getId(), card.getId(), playerId, activationStatus.getPermittingObject()));
game.fireInformEvent(getLogName() + " plays " + card.getLogName());
// game.removeBookmark(bookmark);
resetStoredBookmark(game); // prevent undo after playing a land
@ -1239,7 +1242,8 @@ public abstract class PlayerImpl implements Player, Serializable {
Card card = game.getCard(ability.getSourceId());
result = playLand(card, game, false);
} else {
if (!ability.canActivate(this.playerId, game)) {
ActivationStatus activationStatus = ability.canActivate(this.playerId, game);
if (!activationStatus.canActivate()) {
return false;
}
@ -1251,7 +1255,7 @@ public abstract class PlayerImpl implements Player, Serializable {
result = playManaAbility((ActivatedManaAbilityImpl) ability.copy(), game);
break;
case SPELL:
result = cast((SpellAbility) ability.copy(), game, false);
result = cast((SpellAbility) ability.copy(), game, false, activationStatus.getPermittingObject());
break;
default:
result = playAbility(ability.copy(), game);
@ -1357,7 +1361,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (ability.getZone().match(zone)) {
if (ability instanceof ActivatedAbility) {
if (ability instanceof ActivatedManaAbilityImpl) {
if (((ActivatedAbility) ability).canActivate(playerId, game)) {
if (((ActivatedAbility) ability).canActivate(playerId, game).canActivate()) {
output.put(ability.getId(), (ActivatedAbility) ability);
}
} else if (canPlay(((ActivatedAbility) ability), availableMana, object, game)) {
@ -1379,19 +1383,19 @@ public abstract class PlayerImpl implements Player, Serializable {
if (Zone.GRAVEYARD == zone && canPlayCardsFromGraveyard()) {
for (ActivatedAbility ability : candidateAbilites.getPlayableAbilities(Zone.HAND)) {
if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
if (ability.canActivate(playerId, game)) {
if (ability.canActivate(playerId, game).canActivate()) {
output.put(ability.getId(), ability);
}
}
}
}
if (zone != Zone.BATTLEFIELD
&& null != game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) {
&& null != game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) {
for (Ability ability : candidateAbilites) {
if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
ability.setControllerId(this.getId());
if (ability instanceof ActivatedAbility && ability.getZone().match(Zone.HAND)
&& ((ActivatedAbility) ability).canActivate(playerId, game)) {
&& ((ActivatedAbility) ability).canActivate(playerId, game).canActivate()) {
output.put(ability.getId(), (ActivatedAbility) ability);
}
}
@ -1439,7 +1443,7 @@ public abstract class PlayerImpl implements Player, Serializable {
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_LEFT);
flashbackAbility.setAbilityName(((SplitCard) card).getLeftHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game)) {
if (flashbackAbility.canActivate(playerId, game).canActivate()) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
// Right Half
@ -1452,7 +1456,7 @@ public abstract class PlayerImpl implements Player, Serializable {
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_RIGHT);
flashbackAbility.setAbilityName(((SplitCard) card).getRightHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game)) {
if (flashbackAbility.canActivate(playerId, game).canActivate()) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
@ -1469,7 +1473,7 @@ public abstract class PlayerImpl implements Player, Serializable {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
for (ActivatedManaAbilityImpl ability : object.getAbilities().getActivatedManaAbilities(zone)) {
if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
if (ability.canActivate(playerId, game)) {
if (ability.canActivate(playerId, game).canActivate()) {
useable.put(ability.getId(), ability);
}
}
@ -2477,7 +2481,7 @@ public abstract class PlayerImpl implements Player, Serializable {
Card card = game.getCard(entry.getKey());
if (card != null) {
// TODO: fix costs (why is Panglacial Wurm automatically accepting payment?)
player.cast(card.getSpellAbility(), game, false);
player.cast(card.getSpellAbility(), game, false, null);
}
chooseCard.clearChoice();
libraryCastableCardTracker.clear();
@ -2633,7 +2637,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (canUse == null) {
canUse = permanent.canUseActivatedAbilities(game);
}
if (canUse && ability.canActivate(playerId, game)) {
if (canUse && ability.canActivate(playerId, game).canActivate()) {
canAdd = true;
if (!ability.getManaCosts().isEmpty()) {
withCost = true;
@ -2677,7 +2681,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (canUse == null) {
canUse = permanent.canUseActivatedAbilities(game);
}
if (canUse && ability.canActivate(playerId, game)) {
if (canUse && ability.canActivate(playerId, game).canActivate()) {
canAdd = true;
}
}
@ -2692,7 +2696,7 @@ public abstract class PlayerImpl implements Player, Serializable {
canAdd = false;
break;
}
if (ability.canActivate(playerId, game)) {
if (ability.canActivate(playerId, game).canActivate()) {
canAdd = true;
}
}
@ -2712,7 +2716,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (canUse == null) {
canUse = permanent.canUseActivatedAbilities(game);
}
if (canUse && ability.canActivate(playerId, game) && !ability.getManaCosts().isEmpty()) {
if (canUse && ability.canActivate(playerId, game).canActivate() && !ability.getManaCosts().isEmpty()) {
result.add(permanent);
break;
}
@ -2732,7 +2736,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (!(ability instanceof ActivatedManaAbilityImpl)) {
ActivatedAbility copy = ability.copy();
copy.setCheckPlayableMode(); // prevents from endless loops for asking player to use effects by checking this mode
if (!copy.canActivate(playerId, game)) {
if (!copy.canActivate(playerId, game).canActivate()) {
return false;
}
if (available != null) {
@ -2765,10 +2769,10 @@ public abstract class PlayerImpl implements Player, Serializable {
if (available == null) {
return true;
}
UUID spendAnyManaSourceId = game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
for (Mana mana : abilityOptions) {
for (Mana avail : available) {
if (spendAnyManaSourceId != null && mana.count() <= avail.count()) {
if (permittingObject != null && mana.count() <= avail.count()) {
return true;
}
if (mana.enough(avail)) { // here we need to check if spend mana as though allow to pay the mana cost
@ -2893,13 +2897,13 @@ public abstract class PlayerImpl implements Player, Serializable {
}
private void getPlayableFromGraveyardCard(Game game, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) {
UUID asThoughtCastSourceId = game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game);
MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game);
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
boolean possible = false;
if (ability.getZone().match(Zone.GRAVEYARD)) {
possible = true;
} else if (ability.getZone().match(Zone.HAND) && (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) {
if (asThoughtCastSourceId != null || canPlayCardsFromGraveyard()) {
if (permittingObject != null || canPlayCardsFromGraveyard()) {
possible = true;
}
}
@ -2968,12 +2972,12 @@ public abstract class PlayerImpl implements Player, Serializable {
}
for (ExileZone exile : game.getExile().getExileZones()) {
for (Card card : exile.getCards(game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) {
for (Ability ability : card.getAbilities()) {
if (ability.getZone().match(Zone.HAND)) {
ability.setControllerId(this.getId()); // controller must be set for case owner != caster
if (ability instanceof ActivatedAbility) {
if (((ActivatedAbility) ability).canActivate(playerId, game)) {
if (((ActivatedAbility) ability).canActivate(playerId, game).canActivate()) {
playable.add(ability);
}
}
@ -2986,7 +2990,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// Check to play revealed cards
for (Cards cards : game.getState().getRevealed().values()) {
for (Card card : cards.getCards(game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, this.getId(), game)) {
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) {
playable.add(ability);
@ -3001,7 +3005,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (player != null) {
if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) {
Card card = player.getLibrary().getFromTop(game);
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, getId(), game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, null, getId(), game)) {
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
if (ability instanceof SpellAbility || ability instanceof PlayLandAbility) {
playable.add(ability);
@ -3354,7 +3358,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean lookAtFaceDownCard(Card card, Game game
) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.LOOK_AT_FACE_DOWN, this.getId(), game)) {
if (null != game.getContinuousEffects().asThough(card.getId(), AsThoughEffectType.LOOK_AT_FACE_DOWN, card.getSpellAbility(), this.getId(), game)) {
if (chooseUse(Outcome.Benefit, "Look at that card?", null, game)) {
Cards cards = new CardsImpl(card);
this.lookAtCards(getName() + " - " + sdf.format(System.currentTimeMillis()), cards, game);