mirror of
https://github.com/magefree/mage.git
synced 2026-01-09 12:22:10 -08:00
[OTJ] Implement Plot mechanic (+8 cards) (#12017)
[OTJ] Implement Aloe Alchemist [OTJ] Implement Aven Interrupter [OTJ] Implement Longhorn Shapshooter [OTJ] Implement Kellan Joins Up [OTJ] Implement Make Your Own Luck [OTJ] Implement Jace Reawakened [OTJ] Implement Lilah, Undefeated Slickshot [OTJ] Implement Doc Aurlock, Grizzled Genius
This commit is contained in:
parent
ed3d6e3078
commit
97ab8074b3
19 changed files with 1476 additions and 25 deletions
|
|
@ -429,6 +429,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
case BESTOW:
|
||||
case MORPH:
|
||||
case DISGUISE:
|
||||
case PLOT:
|
||||
// from Snapcaster Mage:
|
||||
// If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs
|
||||
// (such as that of Foil). (2018-12-07)
|
||||
|
|
@ -521,7 +522,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
String message = controller.getLogName() + " announces a value of " + xValue + " (" + variableCost.getActionText() + ')'
|
||||
+ CardUtil.getSourceLogName(game, this);
|
||||
announceString.append(message);
|
||||
setCostsTag("X",xValue);
|
||||
setCostsTag("X", xValue);
|
||||
}
|
||||
}
|
||||
return announceString.toString();
|
||||
|
|
@ -626,7 +627,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
addManaCostsToPay(new ManaCostsImpl<>(manaString.toString()));
|
||||
getManaCostsToPay().setX(xValue * xValueMultiplier, amountMana);
|
||||
setCostsTag("X",xValue * xValueMultiplier);
|
||||
setCostsTag("X", xValue * xValueMultiplier);
|
||||
}
|
||||
variableManaCost.setPaid();
|
||||
}
|
||||
|
|
@ -718,7 +719,8 @@ public abstract class AbilityImpl implements Ability {
|
|||
public Map<String, Object> getCostsTagMap() {
|
||||
return costsTagMap;
|
||||
}
|
||||
public void setCostsTag(String tag, Object value){
|
||||
|
||||
public void setCostsTag(String tag, Object value) {
|
||||
if (costsTagMap == null) {
|
||||
costsTagMap = new HashMap<>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public class BecomesPlottedSourceTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
public BecomesPlottedSourceTriggeredAbility(Effect effect, boolean optional) {
|
||||
super(Zone.EXILED, effect, optional);
|
||||
setTriggerPhrase("When {this} becomes plotted, ");
|
||||
}
|
||||
|
||||
public BecomesPlottedSourceTriggeredAbility(Effect effect) {
|
||||
this(effect, false);
|
||||
}
|
||||
|
||||
protected BecomesPlottedSourceTriggeredAbility(final BecomesPlottedSourceTriggeredAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BecomesPlottedSourceTriggeredAbility copy() {
|
||||
return new BecomesPlottedSourceTriggeredAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.BECOME_PLOTTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (event.getTargetId().equals(this.getSourceId())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.PlotAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetCardInHand;
|
||||
|
||||
public class MayExileCardFromHandPlottedEffect extends OneShotEffect {
|
||||
|
||||
private final FilterCard filter;
|
||||
|
||||
public MayExileCardFromHandPlottedEffect(FilterCard filter) {
|
||||
super(Outcome.PutCardInPlay);
|
||||
this.filter = filter;
|
||||
this.staticText = "you may exile a " + filter.getMessage() + " from your hand. If you do, it becomes plotted";
|
||||
}
|
||||
|
||||
private MayExileCardFromHandPlottedEffect(final MayExileCardFromHandPlottedEffect effect) {
|
||||
super(effect);
|
||||
this.filter = effect.filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MayExileCardFromHandPlottedEffect copy() {
|
||||
return new MayExileCardFromHandPlottedEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
TargetCardInHand target = new TargetCardInHand(0, 1, filter);
|
||||
if (player.chooseTarget(outcome, target, source, game)) {
|
||||
Card card = game.getCard(target.getFirstTarget());
|
||||
if (card != null) {
|
||||
PlotAbility.doExileAndPlotCard(card, game, source);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -332,17 +332,17 @@ public class ForetellAbility extends SpecialAction {
|
|||
if (game.getState().getZone(mainCardId) != Zone.EXILED) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
Integer foretoldTurn = (Integer) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number");
|
||||
UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility");
|
||||
// Card must be Foretold
|
||||
if (game.getState().getValue(mainCardId.toString() + "Foretell Turn Number") == null
|
||||
&& game.getState().getValue(mainCardId + "foretellAbility") == null) {
|
||||
if (foretoldTurn == null || exileId == null) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
// Can't be cast if the turn it was Foretold is the same
|
||||
if ((int) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number") == game.getTurnNum()) {
|
||||
if (foretoldTurn == game.getTurnNum()) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
// Check that the card is actually in the exile zone (ex: Oblivion Ring exiles it after it was Foretold, etc)
|
||||
UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility");
|
||||
ExileZone exileZone = game.getState().getExile().getExileZone(exileId);
|
||||
if (exileZone != null
|
||||
&& exileZone.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,43 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpecialAction;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.TimingRule;
|
||||
import mage.constants.Zone;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.*;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TODO: Implement this
|
||||
*
|
||||
* @author TheElk801
|
||||
* @author Susucr
|
||||
*/
|
||||
public class PlotAbility extends SpecialAction {
|
||||
|
||||
private final String rule;
|
||||
|
||||
public PlotAbility(String plotCost) {
|
||||
super(Zone.HAND);
|
||||
this.addCost(new ManaCostsImpl<>(plotCost));
|
||||
this.addEffect(new PlotSourceExileEffect());
|
||||
this.setTiming(TimingRule.SORCERY);
|
||||
this.usesStack = false;
|
||||
this.rule = "Plot " + plotCost;
|
||||
}
|
||||
|
||||
private PlotAbility(final PlotAbility ability) {
|
||||
super(ability);
|
||||
this.rule = ability.rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -30,6 +47,258 @@ public class PlotAbility extends SpecialAction {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Plot";
|
||||
return rule;
|
||||
}
|
||||
|
||||
// TODO: handle [[Fblthp, Lost on the Range]] allowing player to plot from library.
|
||||
@Override
|
||||
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||
// plot can only be activated from a hand
|
||||
// TODO: change that for Fblthp.
|
||||
if (game.getState().getZone(getSourceId()) != Zone.HAND) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
// suspend uses card's timing restriction
|
||||
Card card = game.getCard(getSourceId());
|
||||
if (card == null) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
if (!card.getSpellAbility().spellCanBeActivatedRegularlyNow(playerId, game)) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
return super.canActivate(playerId, game);
|
||||
}
|
||||
|
||||
static UUID getPlotExileId(UUID playerId, Game game) {
|
||||
UUID exileId = (UUID) game.getState().getValue("PlotExileId" + playerId.toString());
|
||||
if (exileId == null) {
|
||||
exileId = UUID.randomUUID();
|
||||
game.getState().setValue("PlotExileId" + playerId, exileId);
|
||||
}
|
||||
return exileId;
|
||||
}
|
||||
|
||||
static String getPlotTurnKeyForCard(UUID cardId) {
|
||||
return cardId.toString() + "|" + "Plotted Turn";
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used in an OneShotEffect's apply.
|
||||
* 'Plot' the provided card. The card is exiled in it's owner plot zone,
|
||||
* and may be cast by that player without paying its mana cost at sorcery
|
||||
* speed on a future turn.
|
||||
*/
|
||||
public static boolean doExileAndPlotCard(Card card, Game game, Ability source) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
Player owner = game.getPlayer(card.getOwnerId());
|
||||
if (owner == null) {
|
||||
return false;
|
||||
}
|
||||
UUID exileId = PlotAbility.getPlotExileId(owner.getId(), game);
|
||||
String exileZoneName = "Plots of " + owner.getName();
|
||||
Card mainCard = card.getMainCard();
|
||||
if (mainCard.moveToExile(exileId, exileZoneName, source, game)) {
|
||||
// Remember on which turn the card was last plotted.
|
||||
game.getState().setValue(PlotAbility.getPlotTurnKeyForCard(mainCard.getId()), game.getTurnNum());
|
||||
game.addEffect(new PlotAddSpellAbilityEffect(new MageObjectReference(mainCard, game)), source);
|
||||
game.informPlayers(owner.getLogName() + " plots " + mainCard.getLogName());
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BECOME_PLOTTED, mainCard.getId(), source, owner.getId()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exile the source card in the plot exile zone of its owner
|
||||
* and allow its owner to cast it at sorcery speed starting
|
||||
* next turn.
|
||||
*/
|
||||
class PlotSourceExileEffect extends OneShotEffect {
|
||||
|
||||
PlotSourceExileEffect() {
|
||||
super(Outcome.Benefit);
|
||||
}
|
||||
|
||||
private PlotSourceExileEffect(final PlotSourceExileEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlotSourceExileEffect copy() {
|
||||
return new PlotSourceExileEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return PlotAbility.doExileAndPlotCard(game.getCard(source.getSourceId()), game, source);
|
||||
}
|
||||
}
|
||||
|
||||
class PlotAddSpellAbilityEffect extends ContinuousEffectImpl {
|
||||
|
||||
private final MageObjectReference mor;
|
||||
|
||||
PlotAddSpellAbilityEffect(MageObjectReference mor) {
|
||||
super(Duration.EndOfGame, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
|
||||
this.mor = mor;
|
||||
staticText = "Plot card";
|
||||
}
|
||||
|
||||
private PlotAddSpellAbilityEffect(final PlotAddSpellAbilityEffect effect) {
|
||||
super(effect);
|
||||
this.mor = effect.mor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlotAddSpellAbilityEffect copy() {
|
||||
return new PlotAddSpellAbilityEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Card card = mor.getCard(game);
|
||||
if (card == null) {
|
||||
discard();
|
||||
return true;
|
||||
}
|
||||
|
||||
Card mainCard = card.getMainCard();
|
||||
UUID mainCardId = mainCard.getId();
|
||||
Player player = game.getPlayer(card.getOwnerId());
|
||||
if (game.getState().getZone(mainCardId) != Zone.EXILED || player == null) {
|
||||
discard();
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Card> faces = CardUtil.getCastableComponents(mainCard, null, source, player, game, null, false);
|
||||
for (Card face : faces) {
|
||||
// Add the spell ability to each castable face to have the proper name/paramaters.
|
||||
PlotSpellAbility ability = new PlotSpellAbility(face.getName());
|
||||
ability.setSourceId(face.getId());
|
||||
ability.setControllerId(player.getId());
|
||||
ability.setSpellAbilityType(face.getSpellAbility().getSpellAbilityType());
|
||||
game.getState().addOtherAbility(face, ability);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is inspired (after a little cleanup) by how {@link ForetellAbility} does it.
|
||||
*/
|
||||
class PlotSpellAbility extends SpellAbility {
|
||||
|
||||
private String faceCardName; // Same as with Foretell, we identify the proper face with its spell name.
|
||||
private SpellAbility spellAbilityToResolve;
|
||||
|
||||
PlotSpellAbility(String faceCardName) {
|
||||
super(null, faceCardName, Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.PLOT);
|
||||
this.setRuleVisible(false);
|
||||
this.setAdditionalCostsRuleVisible(false);
|
||||
this.faceCardName = faceCardName;
|
||||
this.addCost(new ManaCostsImpl<>("{0}"));
|
||||
}
|
||||
|
||||
private PlotSpellAbility(final PlotSpellAbility ability) {
|
||||
super(ability);
|
||||
this.faceCardName = ability.faceCardName;
|
||||
this.spellAbilityToResolve = ability.spellAbilityToResolve;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlotSpellAbility copy() {
|
||||
return new PlotSpellAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivationStatus canActivate(UUID playerId, Game game) {
|
||||
if (super.canActivate(playerId, game).canActivate()) {
|
||||
Card card = game.getCard(getSourceId());
|
||||
if (card != null) {
|
||||
Card mainCard = card.getMainCard();
|
||||
UUID mainCardId = mainCard.getId();
|
||||
// Card must be in the exile zone
|
||||
if (game.getState().getZone(mainCardId) != Zone.EXILED) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
Integer plottedTurn = (Integer) game.getState().getValue(PlotAbility.getPlotTurnKeyForCard(mainCardId));
|
||||
// Card must have been plotted
|
||||
if (plottedTurn == null) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
// Can't be cast if the turn it was last Plotted is the same
|
||||
if (plottedTurn == game.getTurnNum()) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
// Only allow the cast at sorcery speed
|
||||
if (!game.canPlaySorcery(playerId)) {
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
// Check that the proper face can be cast.
|
||||
// TODO: As with Foretell, this does not look very clean. Is the face card sometimes incorrect on calling canActivate?
|
||||
if (mainCard instanceof CardWithHalves) {
|
||||
if (((CardWithHalves) mainCard).getLeftHalfCard().getName().equals(faceCardName)) {
|
||||
return ((CardWithHalves) mainCard).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
|
||||
} else if (((CardWithHalves) mainCard).getRightHalfCard().getName().equals(faceCardName)) {
|
||||
return ((CardWithHalves) mainCard).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
|
||||
}
|
||||
} else if (card instanceof AdventureCard) {
|
||||
if (card.getMainCard().getName().equals(faceCardName)) {
|
||||
return card.getMainCard().getSpellAbility().canActivate(playerId, game);
|
||||
} else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) {
|
||||
return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game);
|
||||
}
|
||||
}
|
||||
return card.getSpellAbility().canActivate(playerId, game);
|
||||
}
|
||||
}
|
||||
return ActivationStatus.getFalse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility getSpellAbilityToResolve(Game game) {
|
||||
Card card = game.getCard(getSourceId());
|
||||
if (card != null) {
|
||||
if (spellAbilityToResolve == null) {
|
||||
SpellAbility spellAbilityCopy = null;
|
||||
// TODO: As with Foretell, this does not look very clean. Is the face card sometimes incorrect on calling getSpellAbilityToResolve?
|
||||
if (card instanceof CardWithHalves) {
|
||||
if (((CardWithHalves) card).getLeftHalfCard().getName().equals(faceCardName)) {
|
||||
spellAbilityCopy = ((CardWithHalves) card).getLeftHalfCard().getSpellAbility().copy();
|
||||
} else if (((CardWithHalves) card).getRightHalfCard().getName().equals(faceCardName)) {
|
||||
spellAbilityCopy = ((CardWithHalves) card).getRightHalfCard().getSpellAbility().copy();
|
||||
}
|
||||
} else if (card instanceof AdventureCard) {
|
||||
if (card.getMainCard().getName().equals(faceCardName)) {
|
||||
spellAbilityCopy = card.getMainCard().getSpellAbility().copy();
|
||||
} else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) {
|
||||
spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy();
|
||||
}
|
||||
} else {
|
||||
spellAbilityCopy = card.getSpellAbility().copy();
|
||||
}
|
||||
if (spellAbilityCopy == null) {
|
||||
return null;
|
||||
}
|
||||
spellAbilityCopy.setId(this.getId());
|
||||
spellAbilityCopy.clearManaCosts();
|
||||
spellAbilityCopy.clearManaCostsToPay();
|
||||
spellAbilityCopy.addCost(this.getCosts().copy());
|
||||
spellAbilityCopy.addCost(this.getManaCosts().copy());
|
||||
spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode());
|
||||
spellAbilityToResolve = spellAbilityCopy;
|
||||
}
|
||||
}
|
||||
return spellAbilityToResolve;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Costs<Cost> getCosts() {
|
||||
if (spellAbilityToResolve == null) {
|
||||
return super.getCosts();
|
||||
}
|
||||
return spellAbilityToResolve.getCosts();
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,8 @@ public enum SpellAbilityCastMode {
|
|||
DISGUISE("Disguise", false, true),
|
||||
TRANSFORMED("Transformed", true),
|
||||
DISTURB("Disturb", true),
|
||||
MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true);
|
||||
MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true),
|
||||
PLOT("Plot");
|
||||
|
||||
private final String text;
|
||||
|
||||
|
|
@ -91,6 +92,7 @@ public enum SpellAbilityCastMode {
|
|||
case MADNESS:
|
||||
case FLASHBACK:
|
||||
case DISTURB:
|
||||
case PLOT:
|
||||
case MORE_THAN_MEETS_THE_EYE:
|
||||
// it changes only cost, so keep other characteristics
|
||||
// TODO: research - why TRANSFORMED here - is it used in this.isTransformed code?!
|
||||
|
|
|
|||
|
|
@ -623,6 +623,12 @@ public class GameEvent implements Serializable {
|
|||
playerId controller of the creature mentoring
|
||||
*/
|
||||
MENTORED_CREATURE,
|
||||
/* the card becomes plotted
|
||||
targetId card that was plotted
|
||||
sourceId of the plotting ability (may be the card itself or another one)
|
||||
playerId owner of the plotted card (the one able to cast the card)
|
||||
*/
|
||||
BECOME_PLOTTED,
|
||||
//custom events
|
||||
CUSTOM_EVENT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1323,7 +1323,7 @@ public final class CardUtil {
|
|||
* such as the adventure and main side of adventure spells or both sides of a fuse card.
|
||||
*
|
||||
* @param cardToCast
|
||||
* @param filter A filter to determine if a card is eligible for casting.
|
||||
* @param filter An optional filter to determine if a card is eligible for casting.
|
||||
* @param source The ability or source responsible for the casting.
|
||||
* @param player
|
||||
* @param game
|
||||
|
|
@ -1347,7 +1347,9 @@ public final class CardUtil {
|
|||
if (!playLand || !player.canPlayLand() || !game.isActivePlayer(playerId)) {
|
||||
cards.removeIf(card -> card.isLand(game));
|
||||
}
|
||||
cards.removeIf(card -> !filter.match(card, playerId, source, game));
|
||||
if (filter != null) {
|
||||
cards.removeIf(card -> !filter.match(card, playerId, source, game));
|
||||
}
|
||||
if (spellCastTracker != null) {
|
||||
cards.removeIf(card -> !spellCastTracker.checkCard(card, game));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue