mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
Improve handling of abilities that let you cast spells from not hand zone once per turn (#11946)
* fix name of existing test * adjust class name * generate text * use common class for Danitha * add test * update Gisa and Geralf * rework Cemetery Illuminator, add test * simplify effects allowing play from top of library * simplify common classes which were bloated to support a single card * simplify another * remove unused import from test * rework to check spell ability * add more test cases * add test cases * add failing test case * common watcher and hint for "once each turn, you may cast..." * fix Assemble the Players to check spell ability, add test * apply same improvements to Johann, Apprentice Sorcerer
This commit is contained in:
parent
f04fcc43ef
commit
d5295b0de3
53 changed files with 910 additions and 899 deletions
|
|
@ -20,12 +20,9 @@ public enum MageIdentifier {
|
|||
// "Once each turn, you may cast an instant or sorcery spell from the top of your library."
|
||||
//
|
||||
CastFromGraveyardOnceWatcher,
|
||||
CemeteryIlluminatorWatcher,
|
||||
GisaAndGeralfWatcher,
|
||||
DanithaNewBenaliasLightWatcher,
|
||||
OnceEachTurnCastWatcher,
|
||||
HaukensInsightWatcher,
|
||||
IntrepidPaleontologistWatcher,
|
||||
KaradorGhostChieftainWatcher,
|
||||
KessDissidentMageWatcher,
|
||||
MuldrothaTheGravetideWatcher,
|
||||
ShareTheSpoilsWatcher,
|
||||
|
|
@ -33,8 +30,6 @@ public enum MageIdentifier {
|
|||
GlimpseTheCosmosWatcher,
|
||||
SerraParagonWatcher,
|
||||
OneWithTheMultiverseWatcher("Without paying manacost"),
|
||||
JohannApprenticeSorcererWatcher,
|
||||
AssembleThePlayersWatcher,
|
||||
KaghaShadowArchdruidWatcher,
|
||||
CourtOfLocthwainWatcher("Without paying manacost"),
|
||||
LaraCroftTombRaiderWatcher,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mage.abilities.common;
|
|||
import mage.MageIdentifier;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.*;
|
||||
|
|
@ -10,6 +11,7 @@ import mage.filter.FilterCard;
|
|||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
|
@ -18,26 +20,26 @@ import java.util.UUID;
|
|||
|
||||
/**
|
||||
* Once during each of your turns, you may cast... from your graveyard
|
||||
*
|
||||
* <p>
|
||||
* See Lurrus of the Dream Den and Rivaz of the Claw
|
||||
*
|
||||
* @author weirddan455
|
||||
*/
|
||||
public class CastFromGraveyardOnceStaticAbility extends SimpleStaticAbility {
|
||||
public class CastFromGraveyardOnceEachTurnAbility extends SimpleStaticAbility {
|
||||
|
||||
public CastFromGraveyardOnceStaticAbility(FilterCard filter, String text) {
|
||||
super(new CastFromGraveyardOnceEffect(filter, text));
|
||||
public CastFromGraveyardOnceEachTurnAbility(FilterCard filter) {
|
||||
super(new CastFromGraveyardOnceEffect(filter));
|
||||
this.addWatcher(new CastFromGraveyardOnceWatcher());
|
||||
this.setIdentifier(MageIdentifier.CastFromGraveyardOnceWatcher);
|
||||
}
|
||||
|
||||
private CastFromGraveyardOnceStaticAbility(final CastFromGraveyardOnceStaticAbility ability) {
|
||||
private CastFromGraveyardOnceEachTurnAbility(final CastFromGraveyardOnceEachTurnAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CastFromGraveyardOnceStaticAbility copy() {
|
||||
return new CastFromGraveyardOnceStaticAbility(this);
|
||||
public CastFromGraveyardOnceEachTurnAbility copy() {
|
||||
return new CastFromGraveyardOnceEachTurnAbility(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,10 +47,10 @@ class CastFromGraveyardOnceEffect extends AsThoughEffectImpl {
|
|||
|
||||
private final FilterCard filter;
|
||||
|
||||
CastFromGraveyardOnceEffect(FilterCard filter, String text) {
|
||||
CastFromGraveyardOnceEffect(FilterCard filter) {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
this.filter = filter;
|
||||
this.staticText = text;
|
||||
this.staticText = "Once during each of your turns, you may cast " + filter.getMessage() + " from your graveyard";
|
||||
}
|
||||
|
||||
private CastFromGraveyardOnceEffect(final CastFromGraveyardOnceEffect effect) {
|
||||
|
|
@ -68,19 +70,30 @@ class CastFromGraveyardOnceEffect extends AsThoughEffectImpl {
|
|||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
|
||||
if (source.isControlledBy(affectedControllerId)
|
||||
&& Zone.GRAVEYARD.equals(game.getState().getZone(objectId))
|
||||
&& game.isActivePlayer(affectedControllerId)) {
|
||||
Card card = game.getCard(objectId);
|
||||
Permanent sourceObject = source.getSourcePermanentIfItStillExists(game);
|
||||
if (card != null && sourceObject != null
|
||||
&& card.isOwnedBy(affectedControllerId)
|
||||
&& card.getSpellAbility() != null
|
||||
&& card.getSpellAbility().spellCanBeActivatedRegularlyNow(affectedControllerId, game)
|
||||
&& filter.match(card, affectedControllerId, source, game)) {
|
||||
CastFromGraveyardOnceWatcher watcher = game.getState().getWatcher(CastFromGraveyardOnceWatcher.class);
|
||||
return watcher != null && watcher.abilityNotUsed(new MageObjectReference(sourceObject, game));
|
||||
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game);
|
||||
CastFromGraveyardOnceWatcher watcher = game.getState().getWatcher(CastFromGraveyardOnceWatcher.class);
|
||||
if (controller == null || sourcePermanent == null || watcher == null) {
|
||||
return false;
|
||||
}
|
||||
if (game.isActivePlayer(playerId) // only during your turn
|
||||
&& source.isControlledBy(playerId) // only you may cast
|
||||
&& Zone.GRAVEYARD.equals(game.getState().getZone(objectId)) // from graveyard
|
||||
&& affectedAbility instanceof SpellAbility // characteristics to check
|
||||
&& watcher.abilityNotUsed(new MageObjectReference(sourcePermanent, game)) // once per turn
|
||||
) {
|
||||
SpellAbility spellAbility = (SpellAbility) affectedAbility;
|
||||
Card cardToCheck = spellAbility.getCharacteristics(game);
|
||||
if (spellAbility.getManaCosts().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)
|
||||
&& filter.match(cardToCheck, playerId, source, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,63 +1,32 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.*;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl {
|
||||
|
||||
private final TargetController targetLibrary;
|
||||
|
||||
public LookAtTopCardOfLibraryAnyTimeEffect() {
|
||||
this(TargetController.YOU, Duration.WhileOnBattlefield);
|
||||
this(Duration.WhileOnBattlefield);
|
||||
}
|
||||
|
||||
public LookAtTopCardOfLibraryAnyTimeEffect(TargetController targetLibrary, Duration duration) {
|
||||
public LookAtTopCardOfLibraryAnyTimeEffect(Duration duration) {
|
||||
super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit);
|
||||
this.targetLibrary = targetLibrary;
|
||||
|
||||
String libInfo;
|
||||
switch (this.targetLibrary) {
|
||||
case YOU:
|
||||
libInfo = "your library";
|
||||
break;
|
||||
case OPPONENT:
|
||||
libInfo = "opponents libraries";
|
||||
break;
|
||||
case SOURCE_TARGETS:
|
||||
libInfo = "target player's library";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown target library type: " + targetLibrary);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String durationString = duration.toString();
|
||||
if (durationString != null && !durationString.isEmpty()) {
|
||||
sb.append(durationString);
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append("you may look at the top card of ");
|
||||
sb.append(libInfo);
|
||||
sb.append(" any time");
|
||||
staticText = sb.toString();
|
||||
staticText = (duration.toString().isEmpty() ? "" : duration.toString() + ", ") +
|
||||
"you may look at the top care of your library any time";
|
||||
}
|
||||
|
||||
protected LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) {
|
||||
super(effect);
|
||||
this.targetLibrary = effect.targetLibrary;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -72,43 +41,11 @@ public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl {
|
|||
if (!canLookAtNextTopLibraryCard(game)) {
|
||||
return false;
|
||||
}
|
||||
MageObject obj = source.getSourceObject(game);
|
||||
if (obj == null) {
|
||||
Card topCard = controller.getLibrary().getFromTop(game);
|
||||
if (topCard == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<UUID> needPlayers = new HashSet<>();
|
||||
switch (this.targetLibrary) {
|
||||
case YOU: {
|
||||
needPlayers.add(source.getControllerId());
|
||||
break;
|
||||
}
|
||||
case OPPONENT: {
|
||||
needPlayers.addAll(game.getOpponents(source.getControllerId()));
|
||||
break;
|
||||
}
|
||||
case SOURCE_TARGETS: {
|
||||
needPlayers.addAll(CardUtil.getAllSelectedTargets(source, game));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Set<Card> needCards = new HashSet<>();
|
||||
needPlayers.stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.map(player -> player.getLibrary().getFromTop(game))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(needCards::add);
|
||||
if (needCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// all fine, can show top card
|
||||
needCards.forEach(topCard -> {
|
||||
Player owner = game.getPlayer(topCard.getOwnerId());
|
||||
controller.lookAtCards(String.format("%s: top card of %s", obj.getName(), owner == null ? "error" : owner.getName()), topCard, game);
|
||||
});
|
||||
controller.lookAtCards("Top card of your library", topCard, game);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.TargetController;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class LookAtTopCardOfLibraryAnyTimeTargetEffect extends LookAtTopCardOfLibraryAnyTimeEffect {
|
||||
|
||||
public LookAtTopCardOfLibraryAnyTimeTargetEffect(Duration duration) {
|
||||
super(TargetController.SOURCE_TARGETS, duration);
|
||||
}
|
||||
|
||||
private LookAtTopCardOfLibraryAnyTimeTargetEffect(final LookAtTopCardOfLibraryAnyTimeTargetEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LookAtTopCardOfLibraryAnyTimeTargetEffect copy() {
|
||||
return new LookAtTopCardOfLibraryAnyTimeTargetEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author nantuko, JayDi85, xenohedron
|
||||
*/
|
||||
public class PlayFromTopOfLibraryEffect extends AsThoughEffectImpl {
|
||||
|
||||
private final FilterCard filter;
|
||||
|
||||
private static final FilterCard defaultFilter = new FilterCard("play lands and cast spells");
|
||||
|
||||
/**
|
||||
* You may play lands and cast spells from the top of your library
|
||||
*/
|
||||
public PlayFromTopOfLibraryEffect() {
|
||||
this(defaultFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* You may [play lands and/or cast spells, according to filter] from the top of your library
|
||||
*/
|
||||
public PlayFromTopOfLibraryEffect(FilterCard filter) {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
this.filter = filter;
|
||||
this.staticText = "you may " + filter.getMessage() + " from the top of your library";
|
||||
|
||||
// verify check: this ability is to allow playing lands or casting spells, not playing a "card"
|
||||
if (filter.getMessage().toLowerCase(Locale.ENGLISH).contains("card")) {
|
||||
throw new IllegalArgumentException("Wrong code usage or wrong filter text: PlayTheTopCardEffect");
|
||||
}
|
||||
}
|
||||
|
||||
protected PlayFromTopOfLibraryEffect(final PlayFromTopOfLibraryEffect effect) {
|
||||
super(effect);
|
||||
this.filter = effect.filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayFromTopOfLibraryEffect copy() {
|
||||
return new PlayFromTopOfLibraryEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
|
||||
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
||||
// can play lands/spells (must check specific part and allows specific part)
|
||||
|
||||
Card cardToCheck = game.getCard(objectId); // maybe this should be removed and only check SpellAbility characteristics
|
||||
if (cardToCheck == null) {
|
||||
return false;
|
||||
}
|
||||
if (affectedAbility instanceof SpellAbility) {
|
||||
SpellAbility spell = (SpellAbility) affectedAbility;
|
||||
cardToCheck = spell.getCharacteristics(game);
|
||||
if (spell.getManaCosts().isEmpty()){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// only permits you to cast
|
||||
if (!playerId.equals(source.getControllerId())) {
|
||||
return false;
|
||||
}
|
||||
Player cardOwner = game.getPlayer(cardToCheck.getOwnerId());
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (cardOwner == null || controller == null) {
|
||||
return false;
|
||||
}
|
||||
// main card of spell must be on top of your library
|
||||
Card topCard = controller.getLibrary().getFromTop(game);
|
||||
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
|
||||
return false;
|
||||
}
|
||||
// spell characteristics must match filter
|
||||
return filter.match(cardToCheck, playerId, source, game);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author nantuko, JayDi85
|
||||
*/
|
||||
public class PlayTheTopCardEffect extends AsThoughEffectImpl {
|
||||
|
||||
private final FilterCard filter;
|
||||
private final TargetController targetLibrary;
|
||||
|
||||
// can play card or can play lands/cast spells, see two modes below
|
||||
private final boolean canPlayCardOnly;
|
||||
|
||||
|
||||
/**
|
||||
* Support targets, use TargetController.SOURCE_TARGETS
|
||||
*/
|
||||
public PlayTheTopCardEffect() {
|
||||
this(TargetController.YOU);
|
||||
}
|
||||
|
||||
public PlayTheTopCardEffect(TargetController targetLibrary) {
|
||||
this(targetLibrary, new FilterCard("play lands and cast spells"), false);
|
||||
}
|
||||
|
||||
public PlayTheTopCardEffect(TargetController targetLibrary, FilterCard filter, boolean canPlayCardOnly) {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
this.filter = filter;
|
||||
this.targetLibrary = targetLibrary;
|
||||
this.canPlayCardOnly = canPlayCardOnly;
|
||||
|
||||
String libInfo;
|
||||
switch (this.targetLibrary) {
|
||||
case YOU:
|
||||
libInfo = "your library";
|
||||
break;
|
||||
case OPPONENT:
|
||||
libInfo = "opponents libraries";
|
||||
break;
|
||||
case SOURCE_TARGETS:
|
||||
libInfo = "target player's library";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown target library type: " + targetLibrary);
|
||||
}
|
||||
this.staticText = "you may " + filter.getMessage() + " from the top of " + libInfo;
|
||||
|
||||
// verify check: if you see "card" text in the rules then use card mode
|
||||
// (there aren't any real cards after oracle update, but can be added in the future)
|
||||
if (this.canPlayCardOnly != filter.getMessage().toLowerCase(Locale.ENGLISH).contains("card")) {
|
||||
throw new IllegalArgumentException("Wrong usage of card mode settings");
|
||||
}
|
||||
}
|
||||
|
||||
protected PlayTheTopCardEffect(final PlayTheTopCardEffect effect) {
|
||||
super(effect);
|
||||
this.filter = effect.filter;
|
||||
this.targetLibrary = effect.targetLibrary;
|
||||
this.canPlayCardOnly = effect.canPlayCardOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayTheTopCardEffect copy() {
|
||||
return new PlayTheTopCardEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
|
||||
throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility");
|
||||
}
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
||||
// main card and all parts are checks in different calls.
|
||||
// two modes:
|
||||
// * can play cards (must check main card and allows any parts)
|
||||
// * can play lands/spells (must check specific part and allows specific part)
|
||||
|
||||
// current card's part
|
||||
Card cardToCheck = game.getCard(objectId);
|
||||
if (cardToCheck == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.canPlayCardOnly) {
|
||||
// check whole card instead part
|
||||
cardToCheck = cardToCheck.getMainCard();
|
||||
} else if (affectedAbility instanceof SpellAbility) {
|
||||
SpellAbility spell = (SpellAbility) affectedAbility;
|
||||
cardToCheck = spell.getCharacteristics(game);
|
||||
if (spell.getManaCosts().isEmpty()){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// must be you
|
||||
if (!playerId.equals(source.getControllerId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player cardOwner = game.getPlayer(cardToCheck.getOwnerId());
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (cardOwner == null || controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// must be your or opponents library
|
||||
switch (this.targetLibrary) {
|
||||
case YOU: {
|
||||
Card topCard = controller.getLibrary().getFromTop(game);
|
||||
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OPPONENT: {
|
||||
if (!game.getOpponents(controller.getId()).contains(cardOwner.getId())) {
|
||||
return false;
|
||||
}
|
||||
Card topCard = cardOwner.getLibrary().getFromTop(game);
|
||||
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SOURCE_TARGETS: {
|
||||
UUID needCardId = cardToCheck.getMainCard().getId();
|
||||
if (CardUtil.getAllSelectedTargets(source, game).stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.noneMatch(player -> {
|
||||
Card topCard = player.getLibrary().getFromTop(game);
|
||||
return topCard != null && topCard.getId().equals(needCardId);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// must be correct card
|
||||
return filter.match(cardToCheck, playerId, source, game);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.constants.TargetController;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class PlayTheTopCardTargetEffect extends PlayTheTopCardEffect {
|
||||
|
||||
public PlayTheTopCardTargetEffect() {
|
||||
super(TargetController.SOURCE_TARGETS);
|
||||
}
|
||||
|
||||
protected PlayTheTopCardTargetEffect(final PlayTheTopCardTargetEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayTheTopCardTargetEffect copy() {
|
||||
return new PlayTheTopCardTargetEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.players.Player;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author xenohedron
|
||||
*/
|
||||
public class OnceEachTurnCastWatcher extends Watcher {
|
||||
|
||||
private final Map<UUID, Set<MageObjectReference>> usedFrom = new HashMap<>();
|
||||
|
||||
/**
|
||||
* For abilities that permit the casting of a spell from not own hand zone once each turn (per player)
|
||||
*/
|
||||
public OnceEachTurnCastWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.SPELL_CAST
|
||||
&& event.getPlayerId() != null
|
||||
&& event.hasApprovingIdentifier(MageIdentifier.OnceEachTurnCastWatcher)) {
|
||||
usedFrom.computeIfAbsent(event.getPlayerId(), k -> new HashSet<>())
|
||||
.add(event.getAdditionalReference().getApprovingMageObjectReference());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
usedFrom.clear();
|
||||
}
|
||||
|
||||
public boolean isAbilityUsed(UUID playerId, MageObjectReference mor) {
|
||||
return usedFrom.getOrDefault(playerId, Collections.emptySet()).contains(mor);
|
||||
}
|
||||
|
||||
public static Hint getHint() {
|
||||
return OnceEachTurnCastHint.instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum OnceEachTurnCastHint implements Hint {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public String getText(Game game, Ability ability) {
|
||||
OnceEachTurnCastWatcher watcher = game.getState().getWatcher(OnceEachTurnCastWatcher.class);
|
||||
if (watcher != null) {
|
||||
boolean used = watcher.isAbilityUsed(ability.getControllerId(), new MageObjectReference(ability.getSourceId(), game));
|
||||
if (used) {
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player != null) {
|
||||
return "A spell has been cast by " + player.getLogName() + " with {this} this turn.";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public OnceEachTurnCastHint copy() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue