diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 48b58631072..5ad90a81653 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -1212,6 +1212,8 @@ public abstract class GameState { p.setLandsPlayedThisTurn(landsPlayed); p.setLandsPlayedLastTurn(landsPlayedLastTurn); + p.clearPaidForSA(); + for (Entry kv : playerCards.entrySet()) { PlayerZone zone = p.getZone(kv.getKey()); if (kv.getKey() == ZoneType.Battlefield) { diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index bfd195888f2..8b586340f72 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -119,6 +119,7 @@ public class Game { private Table>> countersAddedThisTurn = HashBasedTable.create(); private Map topLibsCast = Maps.newHashMap(); + private Map facedownWhileCasting = Maps.newHashMap(); private Player monarch = 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)); } } - public void clearTopLibsCast() { - topLibsCast.clear(); + public void clearTopLibsCast(SpellAbility sa) { + // if nothing left to pay + if (sa.getActivatingPlayer().getPaidForSA() == null) { + 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 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)); } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index eda31ec26ec..f4e4227b0dc 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -25,6 +25,7 @@ public enum AbilityKey { AttackedTarget("AttackedTarget"), Blocker("Blocker"), Blockers("Blockers"), + CanReveal("CanReveal"), CastSA("CastSA"), CastSACMC("CastSACMC"), Card("Card"), diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index a7475f95864..52ca6af870d 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1219,20 +1219,6 @@ public class Player extends GameEntity implements Comparable { 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) { final Map repParams = AbilityKey.mapFromAffected(this); repParams.put(AbilityKey.Source, cause); @@ -1300,12 +1286,25 @@ public class Player extends GameEntity implements Comparable { 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) { return drawCards(n, null); } public final CardCollectionView drawCards(final int n, SpellAbility cause) { final CardCollection drawn = new CardCollection(); - final Map toReveal = Maps.newHashMap(); // Replacement effects final Map repRunParams = AbilityKey.mapFromAffected(this); @@ -1317,6 +1316,7 @@ public class Player extends GameEntity implements Comparable { // 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 Map toReveal = Maps.newHashMap(); for (int i = 0; i < n; i++) { if (gameStarted && !canDraw()) { @@ -1358,7 +1358,6 @@ public class Player extends GameEntity implements Comparable { } List pList = Lists.newArrayList(); - for (Player p : getAllOtherPlayers()) { if (c.mayPlayerLook(p)) { pList.add(p); @@ -1385,8 +1384,16 @@ public class Player extends GameEntity implements Comparable { } view.updateNumDrawnThisTurn(this); - // Run triggers final Map 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.Number, numDrawnThisTurn); runParams.put(AbilityKey.Player, this); @@ -3167,6 +3174,9 @@ public class Player extends GameEntity implements Comparable { // it could be empty if spell couldn't be cast paidForStack.poll(); } + public void clearPaidForSA() { + paidForStack.clear(); + } public boolean isMonarch() { return equals(game.getMonarch()); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java b/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java index 517760610a6..2a6ada5654c 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java @@ -86,6 +86,18 @@ public class TriggerDrawn extends Trigger { 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; } diff --git a/forge-gui/res/cardsfolder/g/god_eternal_kefnet.txt b/forge-gui/res/cardsfolder/g/god_eternal_kefnet.txt index b784df0cf46..7e312c59cc7 100644 --- a/forge-gui/res/cardsfolder/g/god_eternal_kefnet.txt +++ b/forge-gui/res/cardsfolder/g/god_eternal_kefnet.txt @@ -3,7 +3,7 @@ ManaCost:2 U U Types:Legendary Creature Zombie God PT:4/5 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: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 @@ -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. 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. - diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java index 68f3d28c69d..0159ec383ae 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java @@ -356,11 +356,9 @@ public abstract class InputPayMana extends InputSyncronizedBase { player.getManaPool().payManaFromAbility(saPaidFor, InputPayMana.this.manaCost, chosen); } onManaAbilityPaid(); - onStateChanged(); - } else { - // Need to call this to unlock - onStateChanged(); } + // Need to call this to unlock + onStateChanged(); } }); diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSyncronizedBase.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSyncronizedBase.java index b6e957f1b5d..728b1018a18 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSyncronizedBase.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSyncronizedBase.java @@ -36,6 +36,8 @@ public abstract class InputSyncronizedBase extends InputBase implements InputSyn } protected final void stop() { + onStop(); + // ensure input won't accept any user actions. FThreads.invokeInEdtNowOrLater(new Runnable() { @Override @@ -44,8 +46,6 @@ public abstract class InputSyncronizedBase extends InputBase implements InputSyn } }); - onStop(); - // thread irrelevant if (getController().getInputQueue().getInput() != null) { getController().getInputQueue().removeInput(InputSyncronizedBase.this); diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 25297942cce..ea7aa35525d 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -171,7 +171,7 @@ public class HumanPlaySpellAbility { manapool.restoreColorReplacements(); human.decNumManaConversion(); } - game.clearTopLibsCast(); + game.clearTopLibsCast(ability); return false; } @@ -197,7 +197,7 @@ public class HumanPlaySpellAbility { manapool.restoreColorReplacements(); } } - game.clearTopLibsCast(); + game.clearTopLibsCast(ability); return true; }