[OTJ] Implement Fblthp, Lost on the Range (#12042)

This commit is contained in:
Susucre 2024-04-02 14:55:09 +02:00 committed by GitHub
parent feacb55caf
commit 4bbdc3c543
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 302 additions and 14 deletions

View file

@ -27,7 +27,7 @@ public class PlotAbility extends SpecialAction {
private final String rule;
public PlotAbility(String plotCost) {
super(Zone.HAND);
super(Zone.ALL); // Usually, plot only works from hand. However [[Fblthp, Lost on the Range]] allows plotting from library
this.addCost(new ManaCostsImpl<>(plotCost));
this.addEffect(new PlotSourceExileEffect());
this.setTiming(TimingRule.SORCERY);
@ -50,19 +50,34 @@ public class PlotAbility extends SpecialAction {
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
// Plot ability uses card's timing restriction
Card card = game.getCard(getSourceId());
if (card == null) {
return ActivationStatus.getFalse();
}
// plot can only be activated from hand or from top of library if allowed to.
Zone zone = game.getState().getZone(getSourceId());
if (zone == Zone.HAND) {
// Allowed from hand
} else if (zone == Zone.LIBRARY) {
// Allowed only if permitted for top card, and only if the card is on top and is nonland
// Note: if another effect changes zones where permitted, or if different card categories are permitted,
// it would be better to refactor this as an unique AsThoughEffect.
// As of now, only Fblthp, Lost on the Range changes permission of plot.
Player player = game.getPlayer(getControllerId());
if (player == null || !player.canPlotFromTopOfLibrary()) {
return ActivationStatus.getFalse();
}
Card topCardLibrary = player.getLibrary().getFromTop(game);
if (topCardLibrary == null || !topCardLibrary.getId().equals(card.getId()) || card.isLand()) {
return ActivationStatus.getFalse();
}
} else {
// Not Allowed from other zones
return ActivationStatus.getFalse();
}
if (!card.getSpellAbility().spellCanBeActivatedRegularlyNow(playerId, game)) {
return ActivationStatus.getFalse();
}
@ -99,11 +114,17 @@ public class PlotAbility extends SpecialAction {
UUID exileId = PlotAbility.getPlotExileId(owner.getId(), game);
String exileZoneName = "Plots of " + owner.getName();
Card mainCard = card.getMainCard();
Zone zone = game.getState().getZone(mainCard.getId());
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.informPlayers(
owner.getLogName()
+ " plots " + mainCard.getLogName()
+ " from " + zone.toString().toLowerCase()
+ CardUtil.getSourceLogName(game, source, card.getId())
);
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BECOME_PLOTTED, mainCard.getId(), source, owner.getId()));
}
return true;

View file

@ -195,6 +195,10 @@ public interface Player extends MageItem, Copyable<Player> {
boolean canPlayCardsFromGraveyard();
void setPlotFromTopOfLibrary(boolean canPlotFromTopOfLibrary);
boolean canPlotFromTopOfLibrary();
void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn);
boolean isDrawsOnOpponentsTurn();
@ -363,6 +367,7 @@ public interface Player extends MageItem, Copyable<Player> {
/**
* Return player's turn control to prev player
*
* @param value
* @param fullRestore return turn control to own
*/
@ -988,7 +993,7 @@ public interface Player extends MageItem, Copyable<Player> {
* @param source
* @param game
* @param fromZone
* @param withName for face down: used to hide card name in game logs before real face down status apply
* @param withName for face down: used to hide card name in game logs before real face down status apply
* @return
*/
@Deprecated

View file

@ -154,6 +154,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected PayLifeCostLevel payLifeCostLevel = PayLifeCostLevel.allAbilities;
protected boolean loseByZeroOrLessLife = true;
protected boolean canPlayCardsFromGraveyard = true;
protected boolean canPlotFromTopOfLibrary = false;
protected boolean drawsOnOpponentsTurn = false;
protected FilterPermanent sacrificeCostFilter;
@ -251,6 +252,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.canLoseLife = player.canLoseLife;
this.loseByZeroOrLessLife = player.loseByZeroOrLessLife;
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard;
this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary;
this.drawsOnOpponentsTurn = player.drawsOnOpponentsTurn;
this.attachments.addAll(player.attachments);
@ -360,6 +362,7 @@ public abstract class PlayerImpl implements Player, Serializable {
? player.getSacrificeCostFilter().copy() : null;
this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife();
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard();
this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary();
this.drawsOnOpponentsTurn = player.isDrawsOnOpponentsTurn();
this.alternativeSourceCosts.clear();
this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts());
@ -474,6 +477,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.payLifeCostLevel = PayLifeCostLevel.allAbilities;
this.loseByZeroOrLessLife = true;
this.canPlayCardsFromGraveyard = true;
this.canPlotFromTopOfLibrary = false;
this.drawsOnOpponentsTurn = false;
this.sacrificeCostFilter = null;
@ -516,6 +520,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.sacrificeCostFilter = null;
this.loseByZeroOrLessLife = true;
this.canPlayCardsFromGraveyard = false;
this.canPlotFromTopOfLibrary = false;
this.drawsOnOpponentsTurn = false;
this.topCardRevealed = false;
this.alternativeSourceCosts.clear();
@ -4526,6 +4531,16 @@ public abstract class PlayerImpl implements Player, Serializable {
this.canPlayCardsFromGraveyard = playCardsFromGraveyard;
}
@Override
public boolean canPlotFromTopOfLibrary() {
return canPlotFromTopOfLibrary;
}
@Override
public void setPlotFromTopOfLibrary(boolean canPlotFromTopOfLibrary) {
this.canPlotFromTopOfLibrary = canPlotFromTopOfLibrary;
}
@Override
public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) {
this.drawsOnOpponentsTurn = drawsOnOpponentsTurn;