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:
Susucre 2024-04-13 12:10:53 +02:00 committed by GitHub
parent 31295eb645
commit c77634c843
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
142 changed files with 464 additions and 531 deletions

View file

@ -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);

View file

@ -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");

View file

@ -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) {

View file

@ -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";
}

View file

@ -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;
}
}

View file

@ -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.";
}

View file

@ -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);

View file

@ -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));
}
}

View file

@ -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) {

View file

@ -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),
//

View file

@ -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";
}

View file

@ -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;

View file

@ -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)) {

View file

@ -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);
}