diff --git a/src/rwe/LoadingScene.cpp b/src/rwe/LoadingScene.cpp index 33c14c29..754bee9b 100644 --- a/src/rwe/LoadingScene.cpp +++ b/src/rwe/LoadingScene.cpp @@ -472,6 +472,8 @@ namespace rwe sounds.okToBuild = lookUpSound("OKTOBUILD"); sounds.notOkToBuild = lookUpSound("NOTOKTOBUILD"); sounds.selectMultipleUnits = lookUpSound("SelectMultipleUnits"); + sounds.panel = lookUpSound("Panel"); + sounds.options = lookUpSound("Options"); auto consoleFont = sceneContext.textureService->getFont("fonts/CONSOLE.FNT"); // The original loads exactly two in-game fonts (0x42A320): COMIX for diff --git a/src/rwe/game/GameScene.cpp b/src/rwe/game/GameScene.cpp index 7a8d307f..6599e3e3 100644 --- a/src/rwe/game/GameScene.cpp +++ b/src/rwe/game/GameScene.cpp @@ -342,6 +342,7 @@ namespace rwe } updateMusic(); + updatePanelSlide(); // Pause halts simulation tick dispatch by not advancing the // scaled-time accumulator. Speed scales the accumulator using @@ -415,7 +416,7 @@ namespace rwe // convert that to the world, then set the camera's position to there // (clamped to map bounds) - auto minimapToWorld = minimapToWorldMatrix(simulation.terrain, minimapRect); + auto minimapToWorld = minimapToWorldMatrix(simulation.terrain, slidMinimapRect()); auto mousePos = getMousePosition(); auto worldPos = minimapToWorld * Vector3f(static_cast(mousePos.x) + 0.5f, static_cast(mousePos.y) + 0.5, 0.0f); diff --git a/src/rwe/game/GameScene.h b/src/rwe/game/GameScene.h index db65ff8f..6665a1b0 100644 --- a/src/rwe/game/GameScene.h +++ b/src/rwe/game/GameScene.h @@ -298,6 +298,22 @@ namespace rwe bool leftShiftDown{false}; bool rightShiftDown{false}; + /** + * The side panel's slide, 0 with the panel in place and + * PanelSlideExtent with it slid off the left edge. F4 latches it + * away; holding Space slides it away while the key is down and the + * cursor is not on the panel itself. The original's updater + * (0x4948E0, TOTALA-EXE.md "the panel-slide updater") moves it a + * quarter of the remaining distance a frame and never less than a + * pixel, plays the Panel sound as it leaves an end and Options as it + * arrives at one, and 125 is the panel's width less its border. + * Presentation only: the world viewport grows under it. + */ + static constexpr int PanelSlideExtent = 125; + int panelSlide{0}; + bool panelSlideLatched{false}; + bool spaceDown{false}; + struct CameraControlStateFree { }; @@ -845,6 +861,11 @@ namespace rwe void addBattlePoints(int points); void updateMusic(); + /** Steps the side panel's slide one frame; see panelSlide. */ + void updatePanelSlide(); + /** The minimap's rectangle where it is drawn this frame, slid with the panel. */ + Rectangle2f slidMinimapRect() const; + /** What size the world render textures were made at, so a window resize remakes them. */ std::pair worldRenderTextureSize{0, 0}; diff --git a/src/rwe/game/GameScene_commands.cpp b/src/rwe/game/GameScene_commands.cpp index 88e7c779..68e197a7 100644 --- a/src/rwe/game/GameScene_commands.cpp +++ b/src/rwe/game/GameScene_commands.cpp @@ -106,7 +106,7 @@ namespace rwe bool GameScene::isCursorOverMinimap() const { auto mousePos = getMousePosition(); - return minimapRect.contains(mousePos.x, mousePos.y); + return slidMinimapRect().contains(mousePos.x, mousePos.y); } bool GameScene::isCursorOverWorld() const diff --git a/src/rwe/game/GameScene_input.cpp b/src/rwe/game/GameScene_input.cpp index 6c83451e..f5412f28 100644 --- a/src/rwe/game/GameScene_input.cpp +++ b/src/rwe/game/GameScene_input.cpp @@ -113,6 +113,19 @@ namespace rwe { showDebugWindow = !showDebugWindow; } + else if (keysym.key == SDLK_F4) + { + // Toggles bit 7 of the display-options word (0x49639D), the one + // bit of it with no registry name: the side panel slides away + // and stays away until the next press. See panelSlide. + panelSlideLatched = !panelSlideLatched; + } + else if (keysym.key == SDLK_SPACE) + { + // Polled, not dispatched, in the original (0x494942): while it + // is held the panel slides away, and back when it is let go. + spaceDown = true; + } else if (keysym.key == SDLK_F1) { helpVisible = !helpVisible; @@ -425,6 +438,70 @@ namespace rwe { rightShiftDown = false; } + else if (keysym.key == SDLK_SPACE) + { + spaceDown = false; + } + } + + void GameScene::updatePanelSlide() + { + // 0x4948E0. With the F4 bit set the position runs to the extent; + // with it clear it runs to zero, unless Space is held and the cursor + // is not on the panel's own gadget, which runs it to the extent + // again. Each frame moves a quarter of what is left, never less + // than a pixel, so the panel starts fast and eases in. + auto mouse = getMousePosition(); + auto overPanel = mouse.x >= 0 && mouse.x < GuiSizeLeft - panelSlide; + auto wantHidden = panelSlideLatched || (spaceDown && !overPanel); + + auto before = panelSlide; + if (wantHidden && panelSlide < PanelSlideExtent) + { + if (panelSlide == 0 && sounds.panel) + { + playUiSound(*sounds.panel); + } + panelSlide += std::max(1, (PanelSlideExtent - panelSlide) / 4); + if (panelSlide >= PanelSlideExtent) + { + panelSlide = PanelSlideExtent; + if (sounds.options) + { + playUiSound(*sounds.options); + } + } + } + else if (!wantHidden && panelSlide > 0) + { + if (panelSlide == PanelSlideExtent && sounds.panel) + { + playUiSound(*sounds.panel); + } + panelSlide -= std::max(1, panelSlide / 4); + if (panelSlide <= 0) + { + panelSlide = 0; + if (sounds.options) + { + playUiSound(*sounds.options); + } + } + } + + if (panelSlide != before && guiVisible) + { + worldViewport.setInset(GuiSizeLeft - panelSlide, GuiSizeTop, GuiSizeRight, GuiSizeBottom); + } + if (currentPanel) + { + currentPanel->setPosition(-panelSlide, currentPanel->getY()); + } + } + + Rectangle2f GameScene::slidMinimapRect() const + { + return Rectangle2f::fromTopLeft(minimapRect.left() - static_cast(panelSlide), minimapRect.top(), minimapRect.width(), minimapRect.height()); } void GameScene::onMouseDown(MouseButtonEvent event) diff --git a/src/rwe/game/GameScene_render.cpp b/src/rwe/game/GameScene_render.cpp index 4b5615df..3cb9629b 100644 --- a/src/rwe/game/GameScene_render.cpp +++ b/src/rwe/game/GameScene_render.cpp @@ -528,15 +528,16 @@ namespace rwe void GameScene::renderMinimap() { + auto minimapDrawRect = slidMinimapRect(); // draw minimap - chromeUiRenderService.drawSpriteAbs(minimapRect, *minimap); + chromeUiRenderService.drawSpriteAbs(minimapDrawRect, *minimap); if (fogSprite) { - chromeUiRenderService.drawSpriteAbs(minimapRect, *fogSprite); + chromeUiRenderService.drawSpriteAbs(minimapDrawRect, *fogSprite); } auto cameraInverse = computeInverseViewProjectionMatrix(worldCameraState, worldViewport.width(), worldViewport.height()); - auto worldToMinimap = worldToMinimapMatrix(simulation.terrain, minimapRect); + auto worldToMinimap = worldToMinimapMatrix(simulation.terrain, minimapDrawRect); // draw minimap dots for (const auto& [unitId, unit] : simulation.units) @@ -743,7 +744,7 @@ namespace rwe } chromeUiRenderService.drawLines( - buildMinimapRing(Vector2f(centre.x, centre.y), radius, minimapRect, false, 0), + buildMinimapRing(Vector2f(centre.x, centre.y), radius, slidMinimapRect(), false, 0), color); } } @@ -804,7 +805,7 @@ namespace rwe auto parity = dashed && ((sceneTime.value / minimapBlinkTicks) % 2 == 1) ? 1 : 0; chromeUiRenderService.drawLines( - buildMinimapRing(Vector2f(centre.x, centre.y), radius, minimapRect, dashed, parity), + buildMinimapRing(Vector2f(centre.x, centre.y), radius, slidMinimapRect(), dashed, parity), Color(255, 255, 255)); } } diff --git a/src/rwe/game/InGameSoundsInfo.h b/src/rwe/game/InGameSoundsInfo.h index 6a86746c..fa1fc569 100644 --- a/src/rwe/game/InGameSoundsInfo.h +++ b/src/rwe/game/InGameSoundsInfo.h @@ -21,5 +21,8 @@ namespace rwe std::optional notOkToBuild; std::optional selectMultipleUnits; + /** The side panel leaving an end of its slide, and arriving at one. */ + std::optional panel; + std::optional options; }; } diff --git a/src/rwe/ui/UiComponent.h b/src/rwe/ui/UiComponent.h index faa81203..ec8e6496 100644 --- a/src/rwe/ui/UiComponent.h +++ b/src/rwe/ui/UiComponent.h @@ -68,6 +68,13 @@ namespace rwe int getX() { return posX; } int getY() { return posY; } + /** Moves the component; a panel's children move with it, drawn and hit-tested alike. */ + void setPosition(int x, int y) + { + posX = x; + posY = y; + } + virtual void mouseDown(MouseButtonEvent /*event*/) {} virtual void mouseUp(MouseButtonEvent /*event*/) {}