Fix drawn card being revealed during ability resolve

This commit is contained in:
tool4EvEr
2021-08-07 13:17:06 +02:00
parent 058ad2b1dd
commit f78c3ba441
9 changed files with 74 additions and 29 deletions

View File

@@ -1212,6 +1212,8 @@ public abstract class GameState {
p.setLandsPlayedThisTurn(landsPlayed); p.setLandsPlayedThisTurn(landsPlayed);
p.setLandsPlayedLastTurn(landsPlayedLastTurn); p.setLandsPlayedLastTurn(landsPlayedLastTurn);
p.clearPaidForSA();
for (Entry<ZoneType, CardCollectionView> kv : playerCards.entrySet()) { for (Entry<ZoneType, CardCollectionView> kv : playerCards.entrySet()) {
PlayerZone zone = p.getZone(kv.getKey()); PlayerZone zone = p.getZone(kv.getKey());
if (kv.getKey() == ZoneType.Battlefield) { if (kv.getKey() == ZoneType.Battlefield) {

View File

@@ -119,6 +119,7 @@ public class Game {
private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create(); private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create();
private Map<Player, Card> topLibsCast = Maps.newHashMap(); private Map<Player, Card> topLibsCast = Maps.newHashMap();
private Map<Card, Integer> facedownWhileCasting = Maps.newHashMap();
private Player monarch = null; private Player monarch = null;
private Player monarchBeginTurn = null; private Player monarchBeginTurn = null;
@@ -1113,7 +1114,29 @@ public class Game {
topLibsCast.put(p, p.getTopXCardsFromLibrary(1).isEmpty() ? null : p.getTopXCardsFromLibrary(1).get(0)); topLibsCast.put(p, p.getTopXCardsFromLibrary(1).isEmpty() ? null : p.getTopXCardsFromLibrary(1).get(0));
} }
} }
public void clearTopLibsCast() { public void clearTopLibsCast(SpellAbility sa) {
// if nothing left to pay
if (sa.getActivatingPlayer().getPaidForSA() == null) {
topLibsCast.clear(); topLibsCast.clear();
for (Card c : facedownWhileCasting.keySet()) {
// maybe it was discarded as payment?
if (c.isInZone(ZoneType.Hand)) {
c.forceTurnFaceUp();
// run triggers for cards that need reveal
final Map<AbilityKey, Object> runParams = Maps.newHashMap();
runParams.put(AbilityKey.Card, c);
runParams.put(AbilityKey.Number, facedownWhileCasting.get(c));
runParams.put(AbilityKey.Player, this);
runParams.put(AbilityKey.CanReveal, true);
// need to hold trigger to clear list first
getTriggerHandler().runTrigger(TriggerType.Drawn, runParams, true);
}
}
facedownWhileCasting.clear();
}
}
public void addFacedownWhileCasting(Card c, int numDrawn) {
facedownWhileCasting.put(c, Integer.valueOf(numDrawn));
} }
} }

View File

@@ -25,6 +25,7 @@ public enum AbilityKey {
AttackedTarget("AttackedTarget"), AttackedTarget("AttackedTarget"),
Blocker("Blocker"), Blocker("Blocker"),
Blockers("Blockers"), Blockers("Blockers"),
CanReveal("CanReveal"),
CastSA("CastSA"), CastSA("CastSA"),
CastSACMC("CastSACMC"), CastSACMC("CastSACMC"),
Card("Card"), Card("Card"),

View File

@@ -1219,20 +1219,6 @@ public class Player extends GameEntity implements Comparable<Player> {
return false; return false;
} }
public final boolean canDraw() {
if (hasKeyword("You can't draw cards.")) {
return false;
}
if (hasKeyword("You can't draw more than one card each turn.")) {
return numDrawnThisTurn < 1;
}
return true;
}
public final CardCollectionView drawCard() {
return drawCards(1, null);
}
public void surveil(int num, SpellAbility cause, CardZoneTable table) { public void surveil(int num, SpellAbility cause, CardZoneTable table) {
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Source, cause); repParams.put(AbilityKey.Source, cause);
@@ -1300,12 +1286,25 @@ public class Player extends GameEntity implements Comparable<Player> {
return !getZone(ZoneType.Hand).isEmpty(); return !getZone(ZoneType.Hand).isEmpty();
} }
public final boolean canDraw() {
if (hasKeyword("You can't draw cards.")) {
return false;
}
if (hasKeyword("You can't draw more than one card each turn.")) {
return numDrawnThisTurn < 1;
}
return true;
}
public final CardCollectionView drawCard() {
return drawCards(1, null);
}
public final CardCollectionView drawCards(final int n) { public final CardCollectionView drawCards(final int n) {
return drawCards(n, null); return drawCards(n, null);
} }
public final CardCollectionView drawCards(final int n, SpellAbility cause) { public final CardCollectionView drawCards(final int n, SpellAbility cause) {
final CardCollection drawn = new CardCollection(); final CardCollection drawn = new CardCollection();
final Map<Player, CardCollection> toReveal = Maps.newHashMap();
// Replacement effects // Replacement effects
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this); final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(this);
@@ -1317,6 +1316,7 @@ public class Player extends GameEntity implements Comparable<Player> {
// always allow drawing cards before the game actually starts (e.g. Maralen of the Mornsong Avatar) // always allow drawing cards before the game actually starts (e.g. Maralen of the Mornsong Avatar)
final boolean gameStarted = game.getAge().ordinal() > GameStage.Mulligan.ordinal(); final boolean gameStarted = game.getAge().ordinal() > GameStage.Mulligan.ordinal();
final Map<Player, CardCollection> toReveal = Maps.newHashMap();
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
if (gameStarted && !canDraw()) { if (gameStarted && !canDraw()) {
@@ -1358,7 +1358,6 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
List<Player> pList = Lists.newArrayList(); List<Player> pList = Lists.newArrayList();
for (Player p : getAllOtherPlayers()) { for (Player p : getAllOtherPlayers()) {
if (c.mayPlayerLook(p)) { if (c.mayPlayerLook(p)) {
pList.add(p); pList.add(p);
@@ -1385,8 +1384,16 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
view.updateNumDrawnThisTurn(this); view.updateNumDrawnThisTurn(this);
// Run triggers
final Map<AbilityKey, Object> runParams = Maps.newHashMap(); final Map<AbilityKey, Object> runParams = Maps.newHashMap();
// CR 121.8 card was drawn as part of another sa (e.g. paying with Chromantic Sphere), hide it temporarily
if (game.getTopLibForPlayer(this) != null && getPaidForSA() != null && cause != null && getPaidForSA() != cause.getRootAbility()) {
c.turnFaceDown();
game.addFacedownWhileCasting(c, numDrawnThisTurn);
runParams.put(AbilityKey.CanReveal, false);
}
// Run triggers
runParams.put(AbilityKey.Card, c); runParams.put(AbilityKey.Card, c);
runParams.put(AbilityKey.Number, numDrawnThisTurn); runParams.put(AbilityKey.Number, numDrawnThisTurn);
runParams.put(AbilityKey.Player, this); runParams.put(AbilityKey.Player, this);
@@ -3167,6 +3174,9 @@ public class Player extends GameEntity implements Comparable<Player> {
// it could be empty if spell couldn't be cast // it could be empty if spell couldn't be cast
paidForStack.poll(); paidForStack.poll();
} }
public void clearPaidForSA() {
paidForStack.clear();
}
public boolean isMonarch() { public boolean isMonarch() {
return equals(game.getMonarch()); return equals(game.getMonarch());

View File

@@ -86,6 +86,18 @@ public class TriggerDrawn extends Trigger {
return false; return false;
} }
if (runParams.containsKey(AbilityKey.CanReveal)) {
// while drawing this is only set if false
boolean canReveal = (boolean) runParams.get(AbilityKey.CanReveal);
if (hasParam("ForReveal")) {
if (!canReveal) {
return false;
}
} else if (canReveal) {
return false;
}
}
return true; return true;
} }

View File

@@ -3,7 +3,7 @@ ManaCost:2 U U
Types:Legendary Creature Zombie God Types:Legendary Creature Zombie God
PT:4/5 PT:4/5
K:Flying K:Flying
T:Mode$ Drawn | ValidCard$ Card.YouOwn | Number$ 1 | OptionalDecider$ You | Static$ True | Execute$ DBReveal | TriggerZones$ Battlefield | TriggerDescription$ You may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast. T:Mode$ Drawn | ValidCard$ Card.YouOwn | Number$ 1 | OptionalDecider$ You | Static$ True | ForReveal$ True | Execute$ DBReveal | TriggerZones$ Battlefield | TriggerDescription$ You may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast.
SVar:DBReveal:DB$ Reveal | Defined$ You | RevealDefined$ TriggeredCard | RememberRevealed$ True | SubAbility$ DBTrigger | AILogic$ Kefnet SVar:DBReveal:DB$ Reveal | Defined$ You | RevealDefined$ TriggeredCard | RememberRevealed$ True | SubAbility$ DBTrigger | AILogic$ Kefnet
SVar:DBTrigger:DB$ ImmediateTrigger | RememberObjects$ RememberedCard | ConditionDefined$ Remembered | ConditionPresent$ Instant,Sorcery | SubAbility$ DBCleanup | Execute$ DBPlay | TriggerDescription$ Whenever you reveal an instant or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast. SVar:DBTrigger:DB$ ImmediateTrigger | RememberObjects$ RememberedCard | ConditionDefined$ Remembered | ConditionPresent$ Instant,Sorcery | SubAbility$ DBCleanup | Execute$ DBPlay | TriggerDescription$ Whenever you reveal an instant or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast.
SVar:DBPlay:DB$ Play | Defined$ DelayTriggerRemembered | ValidSA$ Spell | PlayReduceCost$ 2 | Optional$ True | CopyCard$ True SVar:DBPlay:DB$ Play | Defined$ DelayTriggerRemembered | ValidSA$ Spell | PlayReduceCost$ 2 | Optional$ True | CopyCard$ True
@@ -11,4 +11,3 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard,Exile | ValidCard$ Card.Self | Execute$ TriReturn | OptionalDecider$ You | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies or is put into exile from the battlefield, you may put it into its owner's library third from the top. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard,Exile | ValidCard$ Card.Self | Execute$ TriReturn | OptionalDecider$ You | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.
SVar:TriReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Destination$ Library | LibraryPosition$ 2 SVar:TriReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Destination$ Library | LibraryPosition$ 2
Oracle:Flying\nYou may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast.\nWhen God-Eternal Kefnet dies or is put into exile from the battlefield, you may put it into its owner's library third from the top. Oracle:Flying\nYou may reveal the first card you draw each turn as you draw it. Whenever you reveal an instant or sorcery card this way, copy that card and you may cast the copy. That copy costs {2} less to cast.\nWhen God-Eternal Kefnet dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.

View File

@@ -356,12 +356,10 @@ public abstract class InputPayMana extends InputSyncronizedBase {
player.getManaPool().payManaFromAbility(saPaidFor, InputPayMana.this.manaCost, chosen); player.getManaPool().payManaFromAbility(saPaidFor, InputPayMana.this.manaCost, chosen);
} }
onManaAbilityPaid(); onManaAbilityPaid();
onStateChanged(); }
} else {
// Need to call this to unlock // Need to call this to unlock
onStateChanged(); onStateChanged();
} }
}
}); });
return true; return true;

View File

@@ -36,6 +36,8 @@ public abstract class InputSyncronizedBase extends InputBase implements InputSyn
} }
protected final void stop() { protected final void stop() {
onStop();
// ensure input won't accept any user actions. // ensure input won't accept any user actions.
FThreads.invokeInEdtNowOrLater(new Runnable() { FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override @Override
@@ -44,8 +46,6 @@ public abstract class InputSyncronizedBase extends InputBase implements InputSyn
} }
}); });
onStop();
// thread irrelevant // thread irrelevant
if (getController().getInputQueue().getInput() != null) { if (getController().getInputQueue().getInput() != null) {
getController().getInputQueue().removeInput(InputSyncronizedBase.this); getController().getInputQueue().removeInput(InputSyncronizedBase.this);

View File

@@ -171,7 +171,7 @@ public class HumanPlaySpellAbility {
manapool.restoreColorReplacements(); manapool.restoreColorReplacements();
human.decNumManaConversion(); human.decNumManaConversion();
} }
game.clearTopLibsCast(); game.clearTopLibsCast(ability);
return false; return false;
} }
@@ -197,7 +197,7 @@ public class HumanPlaySpellAbility {
manapool.restoreColorReplacements(); manapool.restoreColorReplacements();
} }
} }
game.clearTopLibsCast(); game.clearTopLibsCast(ability);
return true; return true;
} }