mirror of
https://github.com/magefree/mage.git
synced 2025-12-28 22:42:03 -08:00
separate 'you may play'|'you may cast' AsThoughtEffect approuvers
Also, reworked Gonti, Lord of Luxury and checks it now works properly with Zoetic Cavern.
This commit is contained in:
parent
31295eb645
commit
c77634c843
142 changed files with 464 additions and 531 deletions
|
|
@ -118,7 +118,9 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
}
|
||||
|
||||
// play from not own hand
|
||||
Set<ApprovingObject> approvingObjects = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
Set<ApprovingObject> approvingObjects = new HashSet<>();
|
||||
approvingObjects.addAll(game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this, playerId, game));
|
||||
approvingObjects.addAll(game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, this, playerId, game));
|
||||
if (approvingObjects.isEmpty() && getSpellAbilityType().equals(SpellAbilityType.ADVENTURE_SPELL)) {
|
||||
// allowed to cast adventures from non-hand?
|
||||
approvingObjects = game.getContinuousEffects().asThough(getSourceId(), AsThoughEffectType.CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE, this, playerId, game);
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class CastFromGraveyardOnceEffect extends AsThoughEffectImpl {
|
|||
private final FilterCard filter;
|
||||
|
||||
CastFromGraveyardOnceEffect(FilterCard filter) {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
this.filter = filter;
|
||||
this.staticText = "Once during each of your turns, you may cast " + filter.getMessage()
|
||||
+ (filter.getMessage().contains("from your graveyard") ? "" : " from your graveyard");
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package mage.abilities.common;
|
|||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.costs.common.ExileSourceFromHandCost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
|
|
@ -68,7 +67,7 @@ class CastExiledFromHandCardEffect extends OneShotEffect {
|
|||
.map(MageObjectReference.class::cast)
|
||||
.map(mor -> mor.getCard(game))
|
||||
.ifPresent(card -> CardUtil.makeCardPlayable(
|
||||
game, source, card, Duration.Custom, false
|
||||
game, source, card, true, Duration.Custom, false
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
|
@ -83,12 +82,12 @@ class GainManaAbilitiesWhileExiledEffect extends ContinuousEffectImpl {
|
|||
this.colors = colors;
|
||||
this.staticText =
|
||||
"target land gains \"{T}: Add " +
|
||||
CardUtil.concatWithOr(
|
||||
Arrays.stream(colors.split(""))
|
||||
.map(s -> '{' + s + '}')
|
||||
.collect(Collectors.toList())
|
||||
) +
|
||||
"\" until {this} is cast from exile";
|
||||
CardUtil.concatWithOr(
|
||||
Arrays.stream(colors.split(""))
|
||||
.map(s -> '{' + s + '}')
|
||||
.collect(Collectors.toList())
|
||||
) +
|
||||
"\" until {this} is cast from exile";
|
||||
}
|
||||
|
||||
private GainManaAbilitiesWhileExiledEffect(final GainManaAbilitiesWhileExiledEffect effect) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class MayCastFromGraveyardSourceAbility extends StaticAbility {
|
|||
class MayCastFromGraveyardEffect extends AsThoughEffectImpl {
|
||||
|
||||
MayCastFromGraveyardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay);
|
||||
super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay);
|
||||
staticText = "you may cast {this} from your graveyard";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.common.asthought.MayLookAtTargetCardEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CastManaAdjustment;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This exiles the target card or cards.
|
||||
* Each can be looked at by the source's controller.
|
||||
* For each card exiled this way, that player may play|cast that card as long as it stays exiled. (+ mana adjustement)
|
||||
* e.g. [[Gonti, Lord of Luxury]]
|
||||
*
|
||||
* @author Susucr
|
||||
*/
|
||||
public class ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect extends OneShotEffect {
|
||||
|
||||
private final boolean useCastSpellOnly;
|
||||
private final CastManaAdjustment manaAdjustment;
|
||||
|
||||
public ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect(boolean useCastSpellOnly, CastManaAdjustment manaAdjustment) {
|
||||
super(Outcome.Exile);
|
||||
switch (manaAdjustment) {
|
||||
case NONE:
|
||||
case AS_THOUGH_ANY_MANA_TYPE:
|
||||
case AS_THOUGH_ANY_MANA_COLOR:
|
||||
this.manaAdjustment = manaAdjustment;
|
||||
break;
|
||||
case WITHOUT_PAYING_MANA_COST: // TODO when needed
|
||||
default:
|
||||
throw new IllegalArgumentException("Wrong code usage, manaAdjustment is not yet supported: " + manaAdjustment);
|
||||
}
|
||||
this.useCastSpellOnly = useCastSpellOnly;
|
||||
}
|
||||
|
||||
private ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect(final ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect effect) {
|
||||
super(effect);
|
||||
this.manaAdjustment = effect.manaAdjustment;
|
||||
this.useCastSpellOnly = effect.useCastSpellOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect copy() {
|
||||
return new ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Cards cards = new CardsImpl(getTargetPointer()
|
||||
.getTargets(game, source)
|
||||
.stream()
|
||||
.map(game::getCard)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
if (controller == null || cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// move card to exile
|
||||
UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter());
|
||||
MageObject sourceObject = source.getSourceObject(game);
|
||||
String exileName = sourceObject == null ? "" : sourceObject.getIdName();
|
||||
for (Card card : cards.getCards(game)) {
|
||||
card.setFaceDown(true, game);
|
||||
if (controller.moveCardsToExile(card, source, game, false, exileZoneId, exileName)) {
|
||||
card.setFaceDown(true, game);
|
||||
switch (manaAdjustment) {
|
||||
case NONE:
|
||||
CardUtil.makeCardPlayable(game, source, card, useCastSpellOnly, Duration.Custom, false, controller.getId(), null);
|
||||
break;
|
||||
case AS_THOUGH_ANY_MANA_TYPE:
|
||||
case AS_THOUGH_ANY_MANA_COLOR:
|
||||
// TODO: untangle why there is a confusion between the two.
|
||||
CardUtil.makeCardPlayable(game, source, card, useCastSpellOnly, Duration.Custom, true, controller.getId(), null);
|
||||
break;
|
||||
case WITHOUT_PAYING_MANA_COST: // TODO.
|
||||
default:
|
||||
throw new IllegalArgumentException("Wrong code usage, manaAdjustment is not yet supported: " + manaAdjustment);
|
||||
}
|
||||
// For as long as that card remains exiled, you may look at it
|
||||
ContinuousEffect effect = new MayLookAtTargetCardEffect(controller.getId());
|
||||
effect.setTargetPointer(new FixedTarget(card.getId(), game));
|
||||
game.addEffect(effect, source);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing
|
|||
class AdventureCastFromExileEffect extends AsThoughEffectImpl {
|
||||
|
||||
public AdventureCastFromExileEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit);
|
||||
super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit);
|
||||
staticText = "Then exile this card. You may cast the creature later from exile.";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ public class MayCastTargetCardEffect extends OneShotEffect {
|
|||
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
|
||||
} else {
|
||||
// TODO: support (and add tests!) for the non-NONE manaAdjustment
|
||||
CardUtil.makeCardPlayable(game, source, card, duration, false);
|
||||
CardUtil.makeCardPlayable(game, source, card, true, duration, false);
|
||||
}
|
||||
if (thenExile) {
|
||||
ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(true);
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@ public class CanPlayCardControllerEffect extends AsThoughEffectImpl {
|
|||
protected final UUID playerId;
|
||||
protected final Condition condition;
|
||||
|
||||
public CanPlayCardControllerEffect(Game game, UUID cardId, int cardZCC, Duration duration) {
|
||||
this(game, cardId, cardZCC, duration, null, null);
|
||||
public CanPlayCardControllerEffect(Game game, UUID cardId, int cardZCC, boolean useCastSpellOnly, Duration duration) {
|
||||
this(game, cardId, cardZCC, useCastSpellOnly, duration, null, null);
|
||||
}
|
||||
|
||||
public CanPlayCardControllerEffect(Game game, UUID cardId, int cardZCC, Duration duration, UUID playerId, Condition condition) {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, duration, Outcome.Benefit);
|
||||
this.staticText = "You may play those card";
|
||||
public CanPlayCardControllerEffect(Game game, UUID cardId, int cardZCC, boolean useCastSpellOnly, Duration duration, UUID playerId, Condition condition) {
|
||||
super(useCastSpellOnly ? AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE : AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, duration, Outcome.Benefit);
|
||||
this.staticText = useCastSpellOnly ? "You may cast this card" : "You may play this card";
|
||||
this.mor = new MageObjectReference(cardId, cardZCC, game);
|
||||
this.playerId = playerId;
|
||||
this.condition = condition;
|
||||
|
|
@ -57,18 +57,18 @@ public class CanPlayCardControllerEffect extends AsThoughEffectImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) {
|
||||
public boolean applies(UUID objectId, Ability ability, UUID affectedControllerId, Game game) {
|
||||
if (mor.getCard(game) == null) {
|
||||
discard();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (condition != null && !condition.apply(game, source)) {
|
||||
if (condition != null && !condition.apply(game, ability)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UUID objectIdToCast = CardUtil.getMainCardId(game, sourceId); // affected to all card's parts
|
||||
UUID objectIdToCast = CardUtil.getMainCardId(game, objectId); // affected to all card's parts
|
||||
return mor.refersTo(objectIdToCast, game)
|
||||
&& (playerId == null ? source.isControlledBy(affectedControllerId) : playerId.equals(affectedControllerId));
|
||||
&& (playerId == null ? ability.isControlledBy(affectedControllerId) : playerId.equals(affectedControllerId));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public class AftermathAbility extends SimpleStaticAbility {
|
|||
class AftermathCastFromGraveyard extends AsThoughEffectImpl {
|
||||
|
||||
public AftermathCastFromGraveyard() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit);
|
||||
super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit);
|
||||
}
|
||||
|
||||
protected AftermathCastFromGraveyard(final AftermathCastFromGraveyard effect) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ public enum AsThoughEffectType {
|
|||
// 3. Target must points to mainCard, but checking goes for every card's parts and characteristics from objectId (split, adventure)
|
||||
// 4. You must implement/override an applies method with "Ability affectedAbility" (e.g. check multiple play/cast abilities from all card's parts)
|
||||
// TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId
|
||||
PLAY_FROM_NOT_OWN_HAND_ZONE(true, true),
|
||||
PLAY_FROM_NOT_OWN_HAND_ZONE(true, true), // for play lands & cast spells
|
||||
CAST_FROM_NOT_OWN_HAND_ZONE(true, true), // for cast spells
|
||||
CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE(true, true),
|
||||
CAST_AS_INSTANT(true, true),
|
||||
//
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public final class JayaBallardEmblem extends Emblem {
|
|||
class JayaBallardCastFromGraveyardEffect extends AsThoughEffectImpl {
|
||||
|
||||
JayaBallardCastFromGraveyardEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit);
|
||||
super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit);
|
||||
staticText = "You may cast instant and sorcery spells from your graveyard";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class TibaltCosmicImpostorPlayFromExileEffect extends AsThoughEffectImpl {
|
|||
if (exileZone.contains(mainCardId)
|
||||
&& affectedControllerId.equals(source.getControllerId())
|
||||
&& game.getState().getZone(mainCardId).equals(Zone.EXILED)) {
|
||||
CardUtil.makeCardPlayable(game, source, cardInExile, Duration.Custom, true);
|
||||
CardUtil.makeCardPlayable(game, source, cardInExile, false, Duration.Custom, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -3987,9 +3987,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
Set<ApprovingObject> approvingObjects;
|
||||
if ((isPlaySpell || isPlayLand) && (fromZone != Zone.BATTLEFIELD)) {
|
||||
// play hand from non hand zone (except battlefield - you can't play already played permanents)
|
||||
approvingObjects = game.getContinuousEffects().asThough(object.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game);
|
||||
// play card from non hand zone (except battlefield - you can't play already played permanents)
|
||||
approvingObjects = new HashSet<>();
|
||||
approvingObjects.addAll(game.getContinuousEffects().asThough(object.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game));
|
||||
if (isPlaySpell) {
|
||||
approvingObjects.addAll(game.getContinuousEffects().asThough(object.getId(),
|
||||
AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, ability, this.getId(), game));
|
||||
}
|
||||
|
||||
if (approvingObjects.isEmpty() && isPlaySpell
|
||||
&& ((SpellAbility) ability).getSpellAbilityType().equals(SpellAbilityType.ADVENTURE_SPELL)) {
|
||||
|
|
|
|||
|
|
@ -1282,8 +1282,8 @@ public final class CardUtil {
|
|||
}
|
||||
|
||||
// TODO: use CastManaAdjustment instead of boolean anyColor
|
||||
public static void makeCardPlayable(Game game, Ability source, Card card, Duration duration, boolean anyColor) {
|
||||
makeCardPlayable(game, source, card, duration, anyColor, null, null);
|
||||
public static void makeCardPlayable(Game game, Ability source, Card card, boolean useCastSpellOnly, Duration duration, boolean anyColor) {
|
||||
makeCardPlayable(game, source, card, useCastSpellOnly, duration, anyColor, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1299,14 +1299,14 @@ public final class CardUtil {
|
|||
* @param condition can be null
|
||||
*/
|
||||
// TODO: use CastManaAdjustment instead of boolean anyColor
|
||||
public static void makeCardPlayable(Game game, Ability source, Card card, Duration duration, boolean anyColor, UUID playerId, Condition condition) {
|
||||
public static void makeCardPlayable(Game game, Ability source, Card card, boolean useCastSpellOnly, Duration duration, boolean anyColor, UUID playerId, Condition condition) {
|
||||
// Effect can be used for cards in zones and permanents on battlefield
|
||||
// PermanentCard's ZCC is static, but we need updated ZCC from the card (after moved to another zone)
|
||||
// So there is a workaround to get actual card's ZCC
|
||||
// Example: Hostage Taker
|
||||
UUID objectId = card.getMainCard().getId();
|
||||
int zcc = game.getState().getZoneChangeCounter(objectId);
|
||||
game.addEffect(new CanPlayCardControllerEffect(game, objectId, zcc, duration, playerId, condition), source);
|
||||
game.addEffect(new CanPlayCardControllerEffect(game, objectId, zcc, useCastSpellOnly, duration, playerId, condition), source);
|
||||
if (anyColor) {
|
||||
game.addEffect(new YouMaySpendManaAsAnyColorToCastTargetEffect(duration, playerId, condition).setTargetPointer(new FixedTarget(objectId, zcc)), source);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue