From 38bff5faa6d6c43750618afd0f68ff90b6dee08b Mon Sep 17 00:00:00 2001 From: coder111 Date: Sat, 30 May 2020 22:36:12 +0100 Subject: [PATCH 1/3] Separation between UI and model. Start new game now works headless. Preparation for multiplayer support. --- src/rotp/model/game/GameListener.java | 17 ++++++ src/rotp/model/game/GameSession.java | 49 ++++++++-------- src/rotp/ui/BasePanel.java | 7 --- src/rotp/ui/GNNUI.java | 2 - src/rotp/ui/GalacticCouncilUI.java | 2 - src/rotp/ui/RotPUI.java | 69 ++++++++++++++++------- src/rotp/ui/combat/ShipBattleUI.java | 2 - src/rotp/ui/game/LoadGameUI.java | 5 +- src/rotp/ui/game/SetupGalaxyUI.java | 2 + src/rotp/ui/main/GalaxyMapPanel.java | 2 - src/rotp/ui/planets/ColonizePlanetUI.java | 2 - src/rotp/ui/planets/GroundBattleUI.java | 3 - src/rotp/ui/races/SabotageUI.java | 3 +- src/rotp/ui/tech/DiplomaticMessageUI.java | 2 - src/rotp/ui/tech/DiscoverTechUI.java | 3 - src/rotp/util/Logger.java | 27 +++++++++ 16 files changed, 124 insertions(+), 73 deletions(-) create mode 100644 src/rotp/model/game/GameListener.java create mode 100644 src/rotp/util/Logger.java diff --git a/src/rotp/model/game/GameListener.java b/src/rotp/model/game/GameListener.java new file mode 100644 index 000000000..06817e5cd --- /dev/null +++ b/src/rotp/model/game/GameListener.java @@ -0,0 +1,17 @@ +package rotp.model.game; + +import rotp.ui.notifications.TurnNotification; + +import java.util.List; + +/** + * Callbacks for game state changes + */ +public interface GameListener { + default void clearAdvice() { + } + default void processNotifications(List notifications) { + } + default void allocateSystems() { + } +} diff --git a/src/rotp/model/game/GameSession.java b/src/rotp/model/game/GameSession.java index 54c384253..23de9ab00 100644 --- a/src/rotp/model/game/GameSession.java +++ b/src/rotp/model/game/GameSession.java @@ -93,10 +93,18 @@ public final class GameSession implements Base, Serializable { private Galaxy galaxy; private final GameStatus status = new GameStatus(); private long id; + private transient List gameListeners = new ArrayList<>(); public GameStatus status() { return status; } public long id() { return id; } public ExecutorService smallSphereService() { return smallSphereService; } + public void addGameListener(GameListener gameListener) { + gameListeners.add(gameListener); + } + public void removeGameListener(GameListener gameListener) { + gameListeners.remove(gameListener); + } + public void pauseNextTurnProcessing(String s) { log("Pausing Next Turn: ", s); suspendNextTurn = true; @@ -213,14 +221,12 @@ public void startGame() { galaxy().startGame(); saveRecentSession(false); } - RotPUI.instance().mainUI().checkMapInitialized(); - RotPUI.instance().selectIntroPanel(); } private void startExecutors() { smallSphereService = Executors.newSingleThreadExecutor(); } private void stopCurrentGame() { - RotPUI.instance().mainUI().clearAdvice(); + gameListeners.forEach(gl -> gl.clearAdvice()); vars().clear(); clearAlerts(); // shut down any threads running from previous game @@ -296,7 +302,7 @@ private Runnable nextTurnProcess() { gal.moveShipsInTransit(); gal.events().nextTurn(); - + gal.council().nextTurn(); GNNRankingNoticeCheck.nextTurn(); GNNExpansionEvent.nextTurn(); @@ -308,7 +314,7 @@ private Runnable nextTurnProcess() { if (!inProgress()) return; - + if (processNotifications()) { log("Notifications processed 1 - back to MainPanel"); //RotPUI.instance().selectMainPanel(); @@ -316,13 +322,13 @@ private Runnable nextTurnProcess() { gal.postNextTurn1(); if (!inProgress()) return; - + processNotifications(); - + RotPUI.instance().selectMainPanel(); log("Notifications processed 2 - back to MainPanel"); gal.postNextTurn2(); - + if (!inProgress()) return; if (processNotifications()) { @@ -331,25 +337,25 @@ private Runnable nextTurnProcess() { } // all diplomatic fallout: praise, warnings, treaty offers, war declarations gal.assessTurn(); - + processNotifications(); gal.refreshAllEmpireViews(); gal.makeNextTurnDecisions(); - + if (!systemsToAllocate().isEmpty()) - RotPUI.instance().allocateSystems(); - + gameListeners.forEach(l -> l.allocateSystems()); + log("Refreshing Player Views"); NoticeMessage.resetSubstatus(text("TURN_REFRESHING")); validate(); gal.refreshEmpireViews(player()); - + log("Autosaving post-turn"); log("NEXT TURN PROCESSING TIME: ", str(timeMs()-startMs)); NoticeMessage.resetSubstatus(text("TURN_SAVING")); instance.saveRecentSession(true); - + log("Reselecting main panel"); RotPUI.instance().mainUI().showDisplayPanel(); RotPUI.instance().selectMainPanel(); @@ -383,23 +389,14 @@ public boolean processNotifications() { if (!session().systemsScouted().isEmpty()) session().addTurnNotification(new SystemsScoutedNotification()); - boolean prevNotice = RotPUI.drawNextTurnNotice; - waitUntilNextTurnCanProceed(); - boolean notificationsHandled = false; // received a concurrent modification here... iterate over temp array List notifs = new ArrayList<>(notifications()); Collections.sort(notifs); notifications().clear(); - for (TurnNotification notif: notifs) { - RotPUI.drawNextTurnNotice = false; - log("Notifying player: ", notif.toString()); - notificationsHandled = true; - notif.notifyPlayer(); - waitUntilNextTurnCanProceed(); - } - RotPUI.drawNextTurnNotice = prevNotice; + + gameListeners.forEach(l -> l.processNotifications(notifs)); systemsScouted().clear(); - return notificationsHandled; + return true; } public void startGroundCombat() { for (EmpireView v : player().empireViews()) { diff --git a/src/rotp/ui/BasePanel.java b/src/rotp/ui/BasePanel.java index d30334286..7560a86f6 100644 --- a/src/rotp/ui/BasePanel.java +++ b/src/rotp/ui/BasePanel.java @@ -195,13 +195,6 @@ protected void drawStars(Graphics g, int w, int h) { g.drawImage(stars, w-scroll, 0, w, h, 0, 0, scroll, h, null); } } - public void drawNextTurnNotice(Graphics g) { - // sets a global flag to true so that the parent UI will draw - // the notice on its next paint - if (session().performingTurn()) - RotPUI.drawNextTurnNotice = true; - } - protected BufferedImage newStarBackground() { initializeStarBackgroundImage(this,getWidth(),getHeight()); return starBackground; diff --git a/src/rotp/ui/GNNUI.java b/src/rotp/ui/GNNUI.java index d9791e090..9ab59292d 100644 --- a/src/rotp/ui/GNNUI.java +++ b/src/rotp/ui/GNNUI.java @@ -124,8 +124,6 @@ public Image paintToImage() { } g.drawImage(hostImg, 0, 0, resizedW, resizedH, 0, 0, hostImg.getWidth(), hostImg.getHeight(), null); drawTitle(g, w, h); - if (exited) - drawNextTurnNotice(g); g.dispose(); return screenImg; } diff --git a/src/rotp/ui/GalacticCouncilUI.java b/src/rotp/ui/GalacticCouncilUI.java index 136229c91..30c36a86f 100644 --- a/src/rotp/ui/GalacticCouncilUI.java +++ b/src/rotp/ui/GalacticCouncilUI.java @@ -123,8 +123,6 @@ public void paintComponent(Graphics g) { case ACCEPT_RULING: paintAcceptRulingMessage(g2); break; } } - if (exited) - drawNextTurnNotice(g2); drawOverlay(g2); } diff --git a/src/rotp/ui/RotPUI.java b/src/rotp/ui/RotPUI.java index 0e765aeb5..86411c5bf 100644 --- a/src/rotp/ui/RotPUI.java +++ b/src/rotp/ui/RotPUI.java @@ -37,6 +37,8 @@ import rotp.model.empires.SabotageMission; import rotp.model.galaxy.ShipFleet; import rotp.model.galaxy.Transport; +import rotp.model.game.GameListener; +import rotp.model.game.GameSession; import rotp.model.planet.PlanetFactory; import rotp.model.ships.ShipDesign; import rotp.model.ships.ShipLibrary; @@ -57,6 +59,7 @@ import rotp.ui.game.SetupRaceUI; import rotp.ui.main.MainUI; import rotp.ui.notifications.DiplomaticNotification; +import rotp.ui.notifications.TurnNotification; import rotp.ui.planets.ColonizePlanetUI; import rotp.ui.planets.GroundBattleUI; import rotp.ui.planets.PlanetsUI; @@ -70,15 +73,17 @@ import rotp.util.AnimationManager; import rotp.util.ImageManager; import rotp.util.LanguageManager; +import rotp.util.Logger; import rotp.util.sound.SoundManager; -public class RotPUI extends BasePanel implements ActionListener, KeyListener { +public class RotPUI extends BasePanel implements ActionListener, KeyListener, GameListener { private static final long serialVersionUID = 1L; private static int FPS = 10; private static int ANIMATION_TIMER = 100; - public static boolean drawNextTurnNotice = false; + private boolean drawNextTurnNotice = false; private static Throwable startupException; static { + Logger.registerLogListener(Logger::logToFile); // needed for opening ui try { UserPreferences.load(); } catch (Throwable t) { startupException = t; System.out.println("Err: UserPreferences init "+t.getMessage()); } @@ -227,7 +232,29 @@ public MainUI mainUI() { public RotPUI() { timer = new Timer(ANIMATION_TIMER, this); init(); + registerOnSession(session()); } + // should be called ONCE on any time new game session is created/loaded + public final void registerOnSession(GameSession gameSession) { + gameSession.removeGameListener(this); + gameSession.addGameListener(this); + } + @Override + public void clearAdvice() { + RotPUI.this.mainUI().clearAdvice(); + } + @Override + public void processNotifications(List notifications) { + for (TurnNotification tn: notifications) { + try { + drawNextTurnNotice = false; + tn.notifyPlayer(); + } finally { + drawNextTurnNotice = true; + } + } + } + private void resetTimer() { if (timer != null) timer.stop(); @@ -259,9 +286,8 @@ public void toggleAnimations() { @Override public void paint(Graphics g) { super.paint(g); - if (drawNextTurnNotice) { + if (drawNextTurnNotice && session().performingTurn()) { drawNotice(g, 28, -s100); - drawNextTurnNotice = false; } requestFocusInWindow(); } @@ -352,13 +378,15 @@ public void promptForBombardment(int sysId, ShipFleet fl) { session().waitUntilNextTurnCanProceed(); } public void promptForShipCombat(ShipCombatManager mgr) { - boolean prevNotice = drawNextTurnNotice; - drawNextTurnNotice = false; - session().pauseNextTurnProcessing("Show Ship Combat Prompt"); - mainUI().showShipCombatPrompt(mgr); - selectMainPanel(); - session().waitUntilNextTurnCanProceed(); - drawNextTurnNotice = prevNotice; + try { + drawNextTurnNotice = false; + session().pauseNextTurnProcessing("Show Ship Combat Prompt"); + mainUI().showShipCombatPrompt(mgr); + selectMainPanel(); + session().waitUntilNextTurnCanProceed(); + } finally { + drawNextTurnNotice = true; + } } public void selectShipBattlePanel(ShipCombatManager mgr) { shipBattleUI.init(mgr); @@ -445,15 +473,18 @@ public void selectDiplomaticReplyModalPanel(DiplomacyRequestReply reply) { public void showTransportAlert(String title, String subtitle, String text) { } public void showSpyAlert(String title, String subtitle, String text) { } public void showRandomEventAlert(String title, String subtitle, String text, ImageIcon splash) { } + @Override public void allocateSystems() { - boolean prevNotice = RotPUI.drawNextTurnNotice; - RotPUI.drawNextTurnNotice = false; - session().pauseNextTurnProcessing("Show Allocate Systems"); - log("==MAIN UI== allocate systems"); - mainUI().allocateSystems(session().systemsToAllocate()); - selectMainPanel(); - session().waitUntilNextTurnCanProceed(); - RotPUI.drawNextTurnNotice = prevNotice; + try { + drawNextTurnNotice = false; + session().pauseNextTurnProcessing("Show Allocate Systems"); + log("==MAIN UI== allocate systems"); + mainUI().allocateSystems(session().systemsToAllocate()); + selectMainPanel(); + session().waitUntilNextTurnCanProceed(); + } finally { + drawNextTurnNotice = true; + } } public void showSystemsScouted() { session().pauseNextTurnProcessing("Show Systems Scouted"); diff --git a/src/rotp/ui/combat/ShipBattleUI.java b/src/rotp/ui/combat/ShipBattleUI.java index 9a4e5cfaf..f73d2f0c6 100644 --- a/src/rotp/ui/combat/ShipBattleUI.java +++ b/src/rotp/ui/combat/ShipBattleUI.java @@ -476,8 +476,6 @@ private void paintShipsToImage(Graphics2D g, int x, int y, int w, int h, int hov // draw any overlying messages if (mode != Display.INTRO) drawResults(g, 0, 0, w, h); - if (exited) - drawNextTurnNotice(g); } private void paintStackActions(Graphics2D g, int hoveringX, int hoveringY) { if (mgr.performingStackTurn) diff --git a/src/rotp/ui/game/LoadGameUI.java b/src/rotp/ui/game/LoadGameUI.java index 2581ca75f..1b797cf9d 100644 --- a/src/rotp/ui/game/LoadGameUI.java +++ b/src/rotp/ui/game/LoadGameUI.java @@ -247,7 +247,10 @@ public void loadGame(String s) { GameUI.gameName = fileBaseName(s); repaint(); buttonClick(); - final Runnable load = () -> { GameSession.instance().loadSession(s, false); }; + final Runnable load = () -> { + GameSession.instance().loadSession(s, false); + RotPUI.instance().registerOnSession(session()); + }; SwingUtilities.invokeLater(load); } public void cancelLoad() { diff --git a/src/rotp/ui/game/SetupGalaxyUI.java b/src/rotp/ui/game/SetupGalaxyUI.java index 4f0eede55..dfc9d29bf 100644 --- a/src/rotp/ui/game/SetupGalaxyUI.java +++ b/src/rotp/ui/game/SetupGalaxyUI.java @@ -394,6 +394,8 @@ public void startGame() { final Runnable save = () -> { long start = System.currentTimeMillis(); GameSession.instance().startGame(); + RotPUI.instance().mainUI().checkMapInitialized(); + RotPUI.instance().selectIntroPanel(); log("TOTAL GAME START TIME:" +(System.currentTimeMillis()-start)); log("Game Name; "+GameUI.gameName); starting = false; diff --git a/src/rotp/ui/main/GalaxyMapPanel.java b/src/rotp/ui/main/GalaxyMapPanel.java index 3025b5145..5aa0130e5 100644 --- a/src/rotp/ui/main/GalaxyMapPanel.java +++ b/src/rotp/ui/main/GalaxyMapPanel.java @@ -319,8 +319,6 @@ public void paintToImage(Image img) { drawControlSprites(g2); drawNextTurnSprites(g2); drawRangeSelect(g2); - if (parent.displayNextTurnNotice()) - drawNextTurnNotice(g2); g2.dispose(); } private Image mapBuffer() { diff --git a/src/rotp/ui/planets/ColonizePlanetUI.java b/src/rotp/ui/planets/ColonizePlanetUI.java index 4e6da0120..5ba9af106 100644 --- a/src/rotp/ui/planets/ColonizePlanetUI.java +++ b/src/rotp/ui/planets/ColonizePlanetUI.java @@ -233,8 +233,6 @@ public void paintComponent(Graphics g) { } drawMapBuffer(g); - if (exited) - drawNextTurnNotice(g); // draw name box over image if necesary if (displayMode == Display.NAMING) { diff --git a/src/rotp/ui/planets/GroundBattleUI.java b/src/rotp/ui/planets/GroundBattleUI.java index ebca15eea..fc0c1f6d1 100644 --- a/src/rotp/ui/planets/GroundBattleUI.java +++ b/src/rotp/ui/planets/GroundBattleUI.java @@ -462,9 +462,6 @@ private void paintCombatScene(Image img) { y0 += s30; drawBorderedString(g, subtitle, 1, x0, y0, Color.black, Color.yellow); - if (exited) - drawNextTurnNotice(g); - drawSkipText(g, !battleInProgress()); g.dispose(); diff --git a/src/rotp/ui/races/SabotageUI.java b/src/rotp/ui/races/SabotageUI.java index b462141c3..2ecfcbc4f 100644 --- a/src/rotp/ui/races/SabotageUI.java +++ b/src/rotp/ui/races/SabotageUI.java @@ -703,8 +703,7 @@ else if (mission.isDestroyBases()) drawBorderedString(g, msg, x0, y0, Color.black, Color.white); } drawSkipText(g, (currentState == SHOW_RESULTS)); - if (exited) - drawNextTurnNotice(g); + g.dispose(); } private Image panelBuffer() { diff --git a/src/rotp/ui/tech/DiplomaticMessageUI.java b/src/rotp/ui/tech/DiplomaticMessageUI.java index 3ce95a777..8c5511ce8 100644 --- a/src/rotp/ui/tech/DiplomaticMessageUI.java +++ b/src/rotp/ui/tech/DiplomaticMessageUI.java @@ -226,8 +226,6 @@ public Image paintToImage() { drawText(g, receiving, textBoxX, textBoxY, textBoxW, textBoxH); - if (exited) - drawNextTurnNotice(g); g.dispose(); return screenBuffer(); } diff --git a/src/rotp/ui/tech/DiscoverTechUI.java b/src/rotp/ui/tech/DiscoverTechUI.java index 0b2af2b93..655304439 100644 --- a/src/rotp/ui/tech/DiscoverTechUI.java +++ b/src/rotp/ui/tech/DiscoverTechUI.java @@ -295,9 +295,6 @@ else if (mode == MODE_REALLOCATE) else if (mode == MODE_FRAME_EMPIRE) drawFrameEmpire(g); - if (finished) - drawNextTurnNotice(g); - g.dispose(); } private void drawTechDiscovery(Graphics2D g, String title) { diff --git a/src/rotp/util/Logger.java b/src/rotp/util/Logger.java new file mode 100644 index 000000000..5a48302be --- /dev/null +++ b/src/rotp/util/Logger.java @@ -0,0 +1,27 @@ +package rotp.util; + +import rotp.ui.RotPUI; + +import java.io.PrintWriter; +import java.util.function.Consumer; + +public class Logger { + public static Consumer logListener = null; + + public static Consumer registerLogListener(Consumer logListener) { + Consumer oldListener = Logger.logListener; + Logger.logListener = logListener; + return oldListener; + } + + public static void logToFile(String line) { + if (RotPUI.useDebugFile) { + PrintWriter debugFile = RotPUI.debugFile(); + if (debugFile != null) { + debugFile.println(line); + debugFile.flush(); + } + } + } + +} From 559b3fb68db240780b1caa9ee7adde09e3ed191d Mon Sep 17 00:00:00 2001 From: coder111 Date: Wed, 3 Jun 2020 05:38:06 +0100 Subject: [PATCH 2/3] Potential serialization issue fix for GameSession listener list. --- src/rotp/model/game/GameSession.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rotp/model/game/GameSession.java b/src/rotp/model/game/GameSession.java index 23de9ab00..d9affd278 100644 --- a/src/rotp/model/game/GameSession.java +++ b/src/rotp/model/game/GameSession.java @@ -99,6 +99,11 @@ public final class GameSession implements Base, Serializable { public ExecutorService smallSphereService() { return smallSphereService; } public void addGameListener(GameListener gameListener) { + // workaround around deserialization not calling constructors. I could override readObject but + // I don't think that works well on the web version. + if (this.gameListeners == null) { + this.gameListeners = new ArrayList<>(); + } gameListeners.add(gameListener); } public void removeGameListener(GameListener gameListener) { From 7931a43774770b066b01b70faa561adeb7132f7a Mon Sep 17 00:00:00 2001 From: coder111 Date: Wed, 10 Jun 2020 22:18:47 +0100 Subject: [PATCH 3/3] Fix the "continue" problem. Make sure UI always registers itself as a listener on game load. --- src/rotp/Rotp.java | 5 ++++- src/rotp/ui/RotPUI.java | 3 +++ src/rotp/ui/game/GameUI.java | 5 ++++- src/rotp/ui/game/LoadGameUI.java | 7 ++++++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/rotp/Rotp.java b/src/rotp/Rotp.java index 93ddffab0..3b7941c46 100644 --- a/src/rotp/Rotp.java +++ b/src/rotp/Rotp.java @@ -72,8 +72,11 @@ public void windowClosing(WindowEvent e) { setFrameSize(); - if (reloadRecentSave) + if (reloadRecentSave) { + RotPUI.instance().unregisterOnSession(GameSession.instance()); GameSession.instance().loadRecentSession(false); + RotPUI.instance().registerOnSession(GameSession.instance()); + } frame.setResizable(false); frame.setVisible(true); } diff --git a/src/rotp/ui/RotPUI.java b/src/rotp/ui/RotPUI.java index 86411c5bf..f84dbafde 100644 --- a/src/rotp/ui/RotPUI.java +++ b/src/rotp/ui/RotPUI.java @@ -239,6 +239,9 @@ public final void registerOnSession(GameSession gameSession) { gameSession.removeGameListener(this); gameSession.addGameListener(this); } + public final void unregisterOnSession(GameSession gameSession) { + gameSession.removeGameListener(this); + } @Override public void clearAdvice() { RotPUI.this.mainUI().clearAdvice(); diff --git a/src/rotp/ui/game/GameUI.java b/src/rotp/ui/game/GameUI.java index 5825e1765..742095de7 100644 --- a/src/rotp/ui/game/GameUI.java +++ b/src/rotp/ui/game/GameUI.java @@ -539,8 +539,11 @@ private void openRedditPage() { public void continueGame() { if (canContinue()) { buttonClick(); - if (!session().status().inProgress()) + if (!session().status().inProgress()) { + RotPUI.instance().unregisterOnSession(session()); session().loadRecentSession(true); + RotPUI.instance().registerOnSession(session()); + } RotPUI.instance().selectMainPanel(); } } diff --git a/src/rotp/ui/game/LoadGameUI.java b/src/rotp/ui/game/LoadGameUI.java index 1b797cf9d..83313bfc4 100644 --- a/src/rotp/ui/game/LoadGameUI.java +++ b/src/rotp/ui/game/LoadGameUI.java @@ -237,7 +237,11 @@ public void loadRecentGame() { loading = true; repaint(); buttonClick(); - final Runnable load = () -> { GameSession.instance().loadRecentSession(false); }; + final Runnable load = () -> { + RotPUI.instance().unregisterOnSession(session()); + GameSession.instance().loadRecentSession(false); + RotPUI.instance().registerOnSession(session()); + }; SwingUtilities.invokeLater(load); } public void loadGame(String s) { @@ -248,6 +252,7 @@ public void loadGame(String s) { repaint(); buttonClick(); final Runnable load = () -> { + RotPUI.instance().unregisterOnSession(session()); GameSession.instance().loadSession(s, false); RotPUI.instance().registerOnSession(session()); };