From e046cf1b8d4d5b0955a471dea6a7942572e6f71d Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Sat, 13 May 2023 17:37:02 -0500 Subject: [PATCH] Public Discard Pile Support - Added support for public discard piles on a per-format basis. - Altered the "removed" pile to be public info. - Altered handling of the end-game-at-reconcile option to not actually require the reconcile. - Altered the PC formats to make discard piles public and to end the game after regroup actions --- .../src/main/web/js/gemp-022/gameUi.js | 185 +++++++++++------- .../src/main/resources/lotrFormats.json | 6 + .../com/gempukku/lotro/common/JSONDefs.java | 1 + .../java/com/gempukku/lotro/common/Zone.java | 3 +- .../communication/GameStateListener.java | 2 +- .../com/gempukku/lotro/game/LotroFormat.java | 2 + .../lotro/game/state/EventSerializer.java | 2 + .../game/state/GameCommunicationChannel.java | 21 +- .../gempukku/lotro/game/state/GameEvent.java | 10 + .../gempukku/lotro/game/state/GameState.java | 9 +- .../logic/actions/PlayerReconcilesAction.java | 71 ++++--- .../lotro/logic/timing/DefaultLotroGame.java | 2 +- .../logic/timing/rules/WinConditionRule.java | 2 +- .../com/gempukku/lotro/game/GameRecorder.java | 2 +- .../lotro/game/LotroGameMediator.java | 2 +- .../game/formats/DefaultLotroFormat.java | 12 +- .../errata/set01/Card_01_055_ErrataTests.java | 41 +++- 17 files changed, 246 insertions(+), 127 deletions(-) diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/gameUi.js b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/gameUi.js index 7226d37c9..6747c533f 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/gameUi.js +++ b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/gameUi.js @@ -258,7 +258,7 @@ var GempLotrGameUI = Class.extend({ this.layoutUI(false); }, - initializeGameUI: function () { + initializeGameUI: function (discardPublic) { this.advPathGroup = new AdvPathCardGroup($("#main")); var that = this; @@ -330,16 +330,18 @@ var GempLotrGameUI = Class.extend({ //TODO: This is where we will need to add support for public discard piles if (!this.spectatorMode) { - $("#discard" + this.getPlayerIndex(this.bottomPlayerId)).addClass("clickable").click( - (function (index) { - return function () { - var dialog = that.discardPileDialogs[index]; - var group = that.discardPileGroups[index]; - openSizeDialog(dialog); - that.dialogResize(dialog, group); - group.layoutCards(); - }; - })(that.bottomPlayerId)); + if(!discardPublic) { + $("#discard" + this.getPlayerIndex(this.bottomPlayerId)).addClass("clickable").click( + (function (index) { + return function () { + var dialog = that.discardPileDialogs[index]; + var group = that.discardPileGroups[index]; + openSizeDialog(dialog); + that.dialogResize(dialog, group); + group.layoutCards(); + }; + })(that.bottomPlayerId)); + } $("#adventureDeck" + this.getPlayerIndex(this.bottomPlayerId)).addClass("clickable").click( (function (index) { return function () { @@ -363,6 +365,19 @@ var GempLotrGameUI = Class.extend({ group.layoutCards(); }; })(i)); + + if(discardPublic) { + $("#discard" + i).addClass("clickable").click( + (function (index) { + return function () { + var dialog = that.discardPileDialogs[that.allPlayerIds[index]]; + var group = that.discardPileGroups[that.allPlayerIds[index]]; + openSizeDialog(dialog); + that.dialogResize(dialog, group); + group.layoutCards(); + }; + })(i)); + } } this.alertBox = $("
"); @@ -1359,6 +1374,7 @@ var GempLotrGameUI = Class.extend({ participant: function (element) { var participantId = element.getAttribute("participantId"); this.allPlayerIds = element.getAttribute("allParticipantIds").split(","); + var discardPublic = element.getAttribute("discardPublic") === 'true'; this.bottomPlayerId = participantId; @@ -1371,79 +1387,100 @@ var GempLotrGameUI = Class.extend({ } else { this.spectatorMode = false; - var discardPileDialog = $("
").dialog({ - autoOpen: false, - closeOnEscape: true, - resizable: true, - title: "Discard - " + participantId, - minHeight: 80, - minWidth: 200, - width: 600, - height: 300 - }); + if(!discardPublic) { + this.createPile(participantId, "Discard Pile", "discardPileDialogs", "discardPileGroups"); + } - this.discardPileDialogs[participantId] = discardPileDialog; - this.discardPileGroups[participantId] = new NormalCardGroup(discardPileDialog, function (card) { - return true; - }, false); - - this.discardPileGroups[participantId].setBounds(this.padding, this.padding, 580 - 2 * (this.padding), 250 - 2 * (this.padding)); - - discardPileDialog.bind("dialogresize", function () { - that.dialogResize(discardPileDialog, that.discardPileGroups[participantId]); - }); - - var adventureDeckDialog = $("
").dialog({ - autoOpen: false, - closeOnEscape: true, - resizable: true, - title: "Adventure deck - " + participantId, - minHeight: 80, - minWidth: 200, - width: 600, - height: 300 - }); - - this.adventureDeckDialogs[participantId] = adventureDeckDialog; - this.adventureDeckGroups[participantId] = new NormalCardGroup(adventureDeckDialog, function (card) { - return true; - }, false); - - this.adventureDeckGroups[participantId].setBounds(this.padding, this.padding, 580 - 2 * (this.padding), 250 - 2 * (this.padding)); - - adventureDeckDialog.bind("dialogresize", function () { - that.dialogResize(adventureDeckDialog, that.adventureDeckGroups[participantId]); - }); + this.createPile(participantId, "Adventure Deck", "adventureDeckDialogs", "adventureDeckGroups"); } for (var i = 0; i < this.allPlayerIds.length; i++) { - var deadPileDialog = $("
").dialog({ - autoOpen: false, - closeOnEscape: true, - resizable: true, - title: "Dead pile - " + this.allPlayerIds[i], - minHeight: 80, - minWidth: 200, - width: 600, - height: 300 - }); - this.deadPileDialogs[this.allPlayerIds[i]] = deadPileDialog; - this.deadPileGroups[this.allPlayerIds[i]] = new NormalCardGroup(deadPileDialog, function (card) { - return true; - }, false); + + participantId = this.allPlayerIds[i]; + + this.createPile(participantId, "Dead Pile", "deadPileDialogs", "deadPileGroups"); + + if(discardPublic) { + this.createPile(participantId, "Discard Pile", "discardPileDialogs", "discardPileGroups"); + } + + // var deadPileDialog = $("
").dialog({ + // autoOpen: false, + // closeOnEscape: true, + // resizable: true, + // title: "Dead pile - " + this.allPlayerIds[i], + // minHeight: 80, + // minWidth: 200, + // width: 600, + // height: 300 + // }); + // this.deadPileDialogs[this.allPlayerIds[i]] = deadPileDialog; + // this.deadPileGroups[this.allPlayerIds[i]] = new NormalCardGroup(deadPileDialog, function (card) { + // return true; + // }, false); - this.deadPileGroups[this.allPlayerIds[i]].setBounds(this.padding, this.padding, 580 - 2 * (this.padding), 250 - 2 * (this.padding)); + // this.deadPileGroups[this.allPlayerIds[i]].setBounds(this.padding, this.padding, 580 - 2 * (this.padding), 250 - 2 * (this.padding)); - deadPileDialog.bind("dialogresize", (function (dialog, index) { - return function () { - that.dialogResize(dialog, that.deadPileGroups[that.allPlayerIds[index]]); - } - })(deadPileDialog, i)); + // deadPileDialog.bind("dialogresize", (function (dialog, index) { + // return function () { + // that.dialogResize(dialog, that.deadPileGroups[that.allPlayerIds[index]]); + // } + // })(deadPileDialog, i)); } - this.initializeGameUI(); + this.initializeGameUI(discardPublic); this.layoutUI(true); }, + + createAdventureDeckPiles: function(id) { + var adventureDeckDialog = $("
").dialog({ + autoOpen: false, + closeOnEscape: true, + resizable: true, + title: "Adventure deck - " + participantId, + minHeight: 80, + minWidth: 200, + width: 600, + height: 300 + }); + + this.adventureDeckDialogs[participantId] = adventureDeckDialog; + this.adventureDeckGroups[participantId] = new NormalCardGroup(adventureDeckDialog, function (card) { + return true; + }, false); + + this.adventureDeckGroups[participantId].setBounds(this.padding, this.padding, 580 - 2 * (this.padding), 250 - 2 * (this.padding)); + + adventureDeckDialog.bind("dialogresize", function () { + that.dialogResize(adventureDeckDialog, that.adventureDeckGroups[participantId]); + }); + }, + + createPile: function(playerId, name, dialogsName, groupsName) { + var dialog = $("
").dialog({ + autoOpen: false, + closeOnEscape: true, + resizable: true, + title: name + " - " + playerId, + minHeight: 80, + minWidth: 200, + width: 600, + height: 300 + }); + + this[dialogsName][playerId] = dialog; + this[groupsName][playerId] = new NormalCardGroup(dialog, function (card) { + return true; + }, false); + + this[groupsName][playerId].setBounds(this.padding, this.padding, 580 - 2 * (this.padding), 250 - 2 * (this.padding)); + + var that = this; + + dialog.bind("dialogresize", function () { + that.dialogResize(dialog, that[groupsName][playerId]); + }); + }, getDecisionParameter: function (decision, name) { var parameters = decision.getElementsByTagName("parameter"); diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/lotrFormats.json b/gemp-lotr/gemp-lotr-cards/src/main/resources/lotrFormats.json index c761c8f25..7d1bcbe75 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/lotrFormats.json +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/lotrFormats.json @@ -23,6 +23,8 @@ "order": 2, "sites":"FELLOWSHIP", "cancelRingBearerSkirmish":true, + "winAtEndOfRegroup":true, + "discardPileIsPublic":true, "set":[1, 2, 3, 101], "erratasets": [ 51,52,53 @@ -41,6 +43,8 @@ "code":"pc_movie", "order": 4, "sites":"KING", + "winAtEndOfRegroup":true, + "discardPileIsPublic":true, "set":[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 101], "erratasets": [ 51,52,53,54,55,56,57,58,59,60 @@ -60,6 +64,8 @@ "code":"pc_expanded", "order": 6, "sites":"SHADOWS", + "winAtEndOfRegroup":true, + "discardPileIsPublic":true, "set":[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 101], "erratasets": [ 51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69 diff --git a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/JSONDefs.java b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/JSONDefs.java index 4efa31e0c..2a48f6866 100644 --- a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/JSONDefs.java +++ b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/JSONDefs.java @@ -40,6 +40,7 @@ public class JSONDefs { public boolean cancelRingBearerSkirmish = false; public boolean ruleOfFour = true; public boolean winAtEndOfRegroup = false; + public boolean discardPileIsPublic = false; public boolean winOnControlling5Sites = false; public boolean playtest = false; public boolean validateShadowFPCount = true; diff --git a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Zone.java b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Zone.java index acaf70cec..d03b1fca5 100644 --- a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Zone.java +++ b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/Zone.java @@ -7,13 +7,14 @@ public enum Zone implements Filterable { // Public knowledge but not in play STACKED("stacked", true, true, false), DEAD("dead pile", true, true, false), + REMOVED("removed", true, true, false), // Private knowledge HAND("hand", false, true, false), DISCARD("discard", false, true, false), ADVENTURE_DECK("adventureDeck", false, true, false), // Nobody sees - VOID("void", false, false, false), VOID_FROM_HAND("voidFromHand", false, false, false), DECK("deck", false, false, false), REMOVED("removed", false, false, false); + VOID("void", false, false, false), VOID_FROM_HAND("voidFromHand", false, false, false), DECK("deck", false, false, false); private final String _humanReadable; private final boolean _public; diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java index f1ff85400..d5f2f92be 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/communication/GameStateListener.java @@ -16,7 +16,7 @@ public interface GameStateListener { public void cardsRemoved(String playerPerforming, Collection cards); - public void setPlayerOrder(List playerIds); + public void initializeBoard(List playerIds, boolean discardIsPublic); public void setPlayerPosition(String playerId, int i); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroFormat.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroFormat.java index f90c32ec7..883508cae 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroFormat.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/LotroFormat.java @@ -18,6 +18,8 @@ public interface LotroFormat { public boolean winWhenShadowReconciles(); + public boolean discardPileIsPublic(); + public boolean winOnControlling5Sites(); public boolean isPlaytest(); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java index ef4fde98e..7991048f1 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java @@ -46,6 +46,8 @@ public class EventSerializer { eventElem.setAttribute("message", gameEvent.getMessage()); if (gameEvent.getVersion() != null) eventElem.setAttribute("version", gameEvent.getVersion().toString()); + if (gameEvent.getType() == GameEvent.Type.PARTICIPANTS) + eventElem.setAttribute("discardPublic", String.valueOf(gameEvent.isDiscardPublic())); if (gameEvent.getGameStats() != null) serializeGameStats(doc, eventElem, gameEvent.getGameStats()); if (gameEvent.getAwaitingDecision() != null) diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java index 4d7a4ae52..fd3e667d5 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java @@ -1,7 +1,9 @@ package com.gempukku.lotro.game.state; import com.gempukku.lotro.common.Token; +import com.gempukku.lotro.common.Zone; import com.gempukku.lotro.communication.GameStateListener; +import com.gempukku.lotro.game.LotroFormat; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.logic.decisions.AwaitingDecision; import com.gempukku.lotro.logic.timing.GameStats; @@ -19,9 +21,12 @@ public class GameCommunicationChannel implements GameStateListener, LongPollable private final int _channelNumber; private volatile WaitingRequest _waitingRequest; - public GameCommunicationChannel(String self, int channelNumber) { + private final LotroFormat _format; + + public GameCommunicationChannel(String self, int channelNumber, LotroFormat format) { _self = self; _channelNumber = channelNumber; + _format = format; } public int getChannelNumber() { @@ -29,10 +34,14 @@ public class GameCommunicationChannel implements GameStateListener, LongPollable } @Override - public void setPlayerOrder(List participants) { + public void initializeBoard(List participants, boolean discardIsPublic) { List participantIds = new LinkedList<>(); participantIds.addAll(participants); - appendEvent(new GameEvent(PARTICIPANTS).participantId(_self).allParticipantIds(participantIds)); + appendEvent(new GameEvent(PARTICIPANTS) + .participantId(_self) + .allParticipantIds(participantIds) + .discardPublic(discardIsPublic) + ); } @Override @@ -107,7 +116,8 @@ public class GameCommunicationChannel implements GameStateListener, LongPollable @Override public void cardCreated(PhysicalCard card) { - if (card.getZone().isPublic() || (card.getZone().isVisibleByOwner() && card.getOwner().equals(_self))) + boolean publicDiscard = card.getZone() == Zone.DISCARD && _format.discardPileIsPublic(); + if (card.getZone().isPublic() || publicDiscard || (card.getZone().isVisibleByOwner() && card.getOwner().equals(_self))) appendEvent(new GameEvent(PUT_CARD_INTO_PLAY).card(card)); } @@ -120,7 +130,8 @@ public class GameCommunicationChannel implements GameStateListener, LongPollable public void cardsRemoved(String playerPerforming, Collection cards) { Set removedCardsVisibleByPlayer = new HashSet<>(); for (PhysicalCard card : cards) { - if (card.getZone().isPublic() || (card.getZone().isVisibleByOwner() && card.getOwner().equals(_self))) + boolean publicDiscard = card.getZone() == Zone.DISCARD && _format.discardPileIsPublic(); + if (card.getZone().isPublic() || publicDiscard || (card.getZone().isVisibleByOwner() && card.getOwner().equals(_self))) removedCardsVisibleByPlayer.add(card); } if (removedCardsVisibleByPlayer.size() > 0) diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java index d3db064f5..2bf1c412e 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java @@ -57,6 +57,7 @@ public class GameEvent { private AwaitingDecision _awaitingDecision; private ZonedDateTime _timestamp; private Integer _version; + private boolean _discardIsPublic; public GameEvent(Type type) { _type = type; @@ -241,4 +242,13 @@ public class GameEvent { _phase = phase; return this; } + + public boolean isDiscardPublic() { + return _discardIsPublic; + } + + public GameEvent discardPublic(boolean discardPublic) { + _discardIsPublic = discardPublic; + return this; + } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java index 78c3a2762..82d3910f7 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java @@ -18,6 +18,8 @@ public class GameState { private static final int LAST_MESSAGE_STORED_COUNT = 15; private PlayerOrder _playerOrder; + private LotroFormat _format; + private final Map> _adventureDecks = new HashMap<>(); private final Map> _decks = new HashMap<>(); private final Map> _hands = new HashMap<>(); @@ -67,9 +69,10 @@ public class GameState { return _nextCardId++; } - public void init(PlayerOrder playerOrder, String firstPlayer, Map> cards, Map ringBearers, Map rings, LotroCardBlueprintLibrary library, GameStats gameStats) { + public void init(PlayerOrder playerOrder, String firstPlayer, Map> cards, Map ringBearers, Map rings, LotroCardBlueprintLibrary library, LotroFormat format) { _playerOrder = playerOrder; _currentPlayerId = firstPlayer; + _format = format; for (Map.Entry> stringListEntry : cards.entrySet()) { String playerId = stringListEntry.getKey(); @@ -101,7 +104,7 @@ public class GameState { } for (GameStateListener listener : getAllGameStateListeners()) { - listener.setPlayerOrder(playerOrder.getAllPlayers()); + listener.initializeBoard(playerOrder.getAllPlayers(), format.discardPileIsPublic()); } //This needs done after the Player Order initialization has been issued, or else the player @@ -198,7 +201,7 @@ public class GameState { private void sendStateToPlayer(String playerId, GameStateListener listener, GameStats gameStats) { if (_playerOrder != null) { - listener.setPlayerOrder(_playerOrder.getAllPlayers()); + listener.initializeBoard(_playerOrder.getAllPlayers(), _format.discardPileIsPublic()); if (_currentPlayerId != null) listener.setCurrentPlayerId(_currentPlayerId); if (_currentPhase != null) diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/actions/PlayerReconcilesAction.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/actions/PlayerReconcilesAction.java index 8813b838f..f7953b163 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/actions/PlayerReconcilesAction.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/actions/PlayerReconcilesAction.java @@ -80,45 +80,54 @@ public class PlayerReconcilesAction implements Action { @Override public Effect nextEffect(LotroGame game) { if (_effectQueue == null) { - game.getGameState().sendMessage(_playerId + " reconciles"); + _effectQueue = new LinkedList<>(); final int handSize = game.getFormat().getHandSize(); GameState gameState = _game.getGameState(); final Set cardsInHand = new HashSet(gameState.getHand(_playerId)); - if (cardsInHand.size() > handSize) { - _effectQueue.add(new PlayoutDecisionEffect(_playerId, - new CardsSelectionDecision(1, "Choose cards to discard down to "+handSize, cardsInHand, cardsInHand.size() - handSize, cardsInHand.size() - handSize) { - @Override - public void decisionMade(String result) throws DecisionResultInvalidException { - Set cards = getSelectedCardsByResponse(result); - _effectQueue.add(new DiscardCardsFromHandEffect(null, _playerId, cards, false)); - _effectQueue.add( - new TriggeringResultEffect(new ReconcileResult(_playerId), "Player reconciled")); - } - })); - } else if (cardsInHand.size() > 0) { - _effectQueue.add(new PlayoutDecisionEffect(_playerId, - new CardsSelectionDecision(1, "Reconcile - choose card to discard or press DONE", cardsInHand, 0, 1) { - @Override - public void decisionMade(String result) throws DecisionResultInvalidException { - Set selectedCards = getSelectedCardsByResponse(result); - if (selectedCards.size() > 0) { - _effectQueue.add(new DiscardCardsFromHandEffect(null, _playerId, selectedCards, false)); + + if(game.getFormat().winWhenShadowReconciles() && game.getGameState().getCurrentPhase() == Phase.REGROUP + && game.getGameState().getCurrentSiteNumber() == 9) { + game.getGameState().sendMessage("End of regroup phase reached."); + } + else { + game.getGameState().sendMessage(_playerId + " reconciles"); + + if (cardsInHand.size() > handSize) { + _effectQueue.add(new PlayoutDecisionEffect(_playerId, + new CardsSelectionDecision(1, "Choose cards to discard down to "+handSize, cardsInHand, cardsInHand.size() - handSize, cardsInHand.size() - handSize) { + @Override + public void decisionMade(String result) throws DecisionResultInvalidException { + Set cards = getSelectedCardsByResponse(result); + _effectQueue.add(new DiscardCardsFromHandEffect(null, _playerId, cards, false)); + _effectQueue.add( + new TriggeringResultEffect(new ReconcileResult(_playerId), "Player reconciled")); } - int cardsInHandAfterDiscard = cardsInHand.size() - selectedCards.size(); - if (cardsInHandAfterDiscard < handSize) { - _effectQueue.add(new DrawCardsEffect(PlayerReconcilesAction.this, _playerId, handSize - cardsInHandAfterDiscard)); + })); + } else if (cardsInHand.size() > 0) { + _effectQueue.add(new PlayoutDecisionEffect(_playerId, + new CardsSelectionDecision(1, "Reconcile - choose card to discard or press DONE", cardsInHand, 0, 1) { + @Override + public void decisionMade(String result) throws DecisionResultInvalidException { + Set selectedCards = getSelectedCardsByResponse(result); + if (selectedCards.size() > 0) { + _effectQueue.add(new DiscardCardsFromHandEffect(null, _playerId, selectedCards, false)); + } + int cardsInHandAfterDiscard = cardsInHand.size() - selectedCards.size(); + if (cardsInHandAfterDiscard < handSize) { + _effectQueue.add(new DrawCardsEffect(PlayerReconcilesAction.this, _playerId, handSize - cardsInHandAfterDiscard)); + } + _effectQueue.add( + new TriggeringResultEffect(new ReconcileResult(_playerId), "Player reconciled")); } - _effectQueue.add( - new TriggeringResultEffect(new ReconcileResult(_playerId), "Player reconciled")); - } - })); - } else { - _effectQueue.add(new DrawCardsEffect(PlayerReconcilesAction.this, _playerId, handSize)); - _effectQueue.add( - new TriggeringResultEffect(new ReconcileResult(_playerId), "Player reconciled")); + })); + } else { + _effectQueue.add(new DrawCardsEffect(PlayerReconcilesAction.this, _playerId, handSize)); + _effectQueue.add( + new TriggeringResultEffect(new ReconcileResult(_playerId), "Player reconciled")); + } } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java index 843e9b5a4..702516164 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/DefaultLotroGame.java @@ -80,7 +80,7 @@ public class DefaultLotroGame implements LotroGame { @Override public void setPlayerOrder(PlayerOrder playerOrder, String firstPlayer) { final GameStats gameStats = _turnProcedure.getGameStats(); - _gameState.init(playerOrder, firstPlayer, cards, ringBearers, rings, library, gameStats); + _gameState.init(playerOrder, firstPlayer, cards, ringBearers, rings, library, format); } }, characterDeathRule); _userFeedback = userFeedback; diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/rules/WinConditionRule.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/rules/WinConditionRule.java index 7c0951f28..2fc2317ae 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/rules/WinConditionRule.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/timing/rules/WinConditionRule.java @@ -31,7 +31,7 @@ public class WinConditionRule { if (isWinAtReconcile(game)) { if (effectResults.getType() == EffectResult.Type.RECONCILE && !((ReconcileResult) effectResults).getPlayerId().equals(game.getGameState().getCurrentPlayerId())) { - game.playerWon(game.getGameState().getCurrentPlayerId(), "Surviving till Shadow player reconciles on site 9"); + game.playerWon(game.getGameState().getCurrentPlayerId(), "Surviving to end of Regroup phase on site 9"); } } else if (effectResults.getType() == EffectResult.Type.START_OF_PHASE) { game.playerWon(game.getGameState().getCurrentPlayerId(), "Surviving to Regroup phase on site 9"); diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java index fa1330e1f..eb975a41a 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java @@ -49,7 +49,7 @@ public class GameRecorder { final ZonedDateTime startDate = ZonedDateTime.now(ZoneOffset.UTC); final Map recordingChannels = new HashMap<>(); for (String playerId : lotroGame.getPlayersPlaying()) { - var recordChannel = new GameCommunicationChannel(playerId, 0); + var recordChannel = new GameCommunicationChannel(playerId, 0, format); lotroGame.addGameStateListener(playerId, recordChannel); recordingChannels.put(playerId, recordChannel); } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java index 45ea334f5..d09181441 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java @@ -403,7 +403,7 @@ public class LotroGameMediator { int number = _channelNextIndex; _channelNextIndex++; - GameCommunicationChannel participantCommunicationChannel = new GameCommunicationChannel(playerName, number); + GameCommunicationChannel participantCommunicationChannel = new GameCommunicationChannel(playerName, number, _lotroGame.getFormat()); _communicationChannels.put(playerName, participantCommunicationChannel); _lotroGame.addGameStateListener(playerName, participantCommunicationChannel); diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/formats/DefaultLotroFormat.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/formats/DefaultLotroFormat.java index ac05d32fc..609fb374a 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/formats/DefaultLotroFormat.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/formats/DefaultLotroFormat.java @@ -24,6 +24,7 @@ public class DefaultLotroFormat implements LotroFormat { private final boolean _canCancelRingBearerSkirmish; private final boolean _hasRuleOfFour; private final boolean _winAtEndOfRegroup; + private final boolean _discardPileIsPublic; private final boolean _winOnControlling5Sites; private int _minimumDeckSize = 60; private final List _bannedCards = new ArrayList<>(); @@ -42,7 +43,7 @@ public class DefaultLotroFormat implements LotroFormat { public DefaultLotroFormat(AdventureLibrary adventureLibrary, LotroCardBlueprintLibrary library, JSONDefs.Format def) throws InvalidPropertiesFormatException{ this(library, adventureLibrary.getAdventure(def.adventure), def.name, def.code, def.order, def.surveyUrl, SitesBlock.valueOf(def.sites), def.validateShadowFPCount, def.minimumDeckSize, def.maximumSameName, def.mulliganRule, def.cancelRingBearerSkirmish, - def.ruleOfFour, def.winAtEndOfRegroup, def.winOnControlling5Sites, def.playtest, def.hall); + def.ruleOfFour, def.winAtEndOfRegroup, def.discardPileIsPublic, def.winOnControlling5Sites, def.playtest, def.hall); if(def.set != null) def.set.forEach(this::addValidSet); @@ -71,8 +72,8 @@ public class DefaultLotroFormat implements LotroFormat { Adventure adventure, String name, String code, int order, String surveyUrl, SitesBlock siteBlock, boolean validateShadowFPCount, int minimumDeckSize, int maximumSameName, boolean mulliganRule, - boolean canCancelRingBearerSkirmish, boolean hasRuleOfFour, boolean winAtEndOfRegroup, boolean winOnControlling5Sites, - boolean playtest, boolean hallVisible) { + boolean canCancelRingBearerSkirmish, boolean hasRuleOfFour, boolean winAtEndOfRegroup, boolean discardPileIsPublic, + boolean winOnControlling5Sites, boolean playtest, boolean hallVisible) { _adventure = adventure; _library = library; _name = name; @@ -87,6 +88,7 @@ public class DefaultLotroFormat implements LotroFormat { _canCancelRingBearerSkirmish = canCancelRingBearerSkirmish; _hasRuleOfFour = hasRuleOfFour; _winAtEndOfRegroup = winAtEndOfRegroup; + _discardPileIsPublic = discardPileIsPublic; _winOnControlling5Sites = winOnControlling5Sites; _isPlaytest = playtest; _hallVisible = hallVisible; @@ -129,6 +131,9 @@ public class DefaultLotroFormat implements LotroFormat { return _winAtEndOfRegroup; } + @Override + public boolean discardPileIsPublic() { return _discardPileIsPublic; } + @Override public boolean canCancelRingBearerSkirmish() { return _canCancelRingBearerSkirmish; @@ -717,6 +722,7 @@ public class DefaultLotroFormat implements LotroFormat { cancelRingBearerSkirmish = _canCancelRingBearerSkirmish; ruleOfFour = _hasRuleOfFour; winAtEndOfRegroup = _winAtEndOfRegroup; + discardPileIsPublic = _discardPileIsPublic; winOnControlling5Sites = _winOnControlling5Sites; playtest = _isPlaytest; validateShadowFPCount = _validateShadowFPCount; diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_055_ErrataTests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_055_ErrataTests.java index 2296edb63..70df8ee18 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_055_ErrataTests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_055_ErrataTests.java @@ -94,6 +94,42 @@ public class Card_01_055_ErrataTests scn.FreepsMoveCharToTable(galadriel); scn.FreepsMoveCardToSupportArea(mirror); + PhysicalCardImpl card1 = scn.GetShadowCard("allyHome3_1"); + PhysicalCardImpl card2 = scn.GetShadowCard("allyHome6_1"); + PhysicalCardImpl card3 = scn.GetShadowCard("galadriel"); + scn.ShadowMoveCardToHand(card1, card2, card3); + scn.ShadowMoveCharToTable("runner"); + + scn.StartGame(); + + scn.SkipToPhase(Phase.MANEUVER); + assertTrue(scn.FreepsActionAvailable(mirror)); + assertEquals(0, scn.GetWoundsOn(galadriel)); + assertEquals(3, scn.GetShadowHandCount()); + assertEquals(1, scn.GetShadowDeckCount()); + assertEquals(Zone.HAND, card1.getZone()); + + scn.FreepsUseCardAction(mirror); + assertEquals(3, scn.GetFreepsCardChoiceCount()); + scn.FreepsDismissRevealedCards(); + scn.ShadowDismissRevealedCards(); + scn.FreepsChooseCardBPFromSelection(card1); + assertEquals(1, scn.GetWoundsOn(galadriel)); + assertEquals(2, scn.GetShadowHandCount()); + assertEquals(2, scn.GetShadowDeckCount()); + assertEquals(Zone.DECK, card1.getZone()); + } + + @Test + public void ManeuverAbilityReveals3RandomCardsFromShadowHand() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + PhysicalCardImpl mirror = scn.GetFreepsCard("mirror"); + PhysicalCardImpl galadriel = scn.GetFreepsCard("galadriel"); + scn.FreepsMoveCharToTable(galadriel); + scn.FreepsMoveCardToSupportArea(mirror); + PhysicalCardImpl card1 = scn.GetShadowCard("allyHome3_1"); PhysicalCardImpl card2 = scn.GetShadowCard("allyHome6_1"); PhysicalCardImpl card3 = scn.GetShadowCard("galadriel"); @@ -114,11 +150,6 @@ public class Card_01_055_ErrataTests assertEquals(3, scn.GetFreepsCardChoiceCount()); scn.FreepsDismissRevealedCards(); scn.ShadowDismissRevealedCards(); - scn.FreepsChooseCardBPFromSelection(card1); - assertEquals(1, scn.GetWoundsOn(galadriel)); - assertEquals(3, scn.GetShadowHandCount()); - assertEquals(1, scn.GetShadowDeckCount()); - assertEquals(Zone.DECK, card1.getZone()); } }