diff --git a/docs/screens.md b/docs/screens.md index c273648..4c67e94 100644 --- a/docs/screens.md +++ b/docs/screens.md @@ -75,3 +75,16 @@ window's own pictures removed. - Spielen (the load window, cUI_Savegame): docs/characters.md, "Spielen screen". + +## Fixes (2026-10-21) + +- The main menu came back unframed (old pictures, frame at the left) after a + game: the set of shown 16:9 screens kept a window of the in-game UI that + was destroyed while shown. Now only screens drawn on the menu thread count, + and an entry expires after 2 menu frames without being drawn + (`screen::BeginMenuFrame`). +- Dialogs over a 16:9 screen in the main menu (e.g. "Spielstand laden?") sat + at the left with the dimming 1024 wide: `hud::OffsetFor` gave every window + offset 0 while the menu showed. Now only while the menu frame is drawn + centred (it shifts the windows itself); otherwise dialogs are centred and + their full-layout dimming stretched, as in a game. diff --git a/docs/shared-chest.md b/docs/shared-chest.md index 4ce191d..8bb22b0 100644 --- a/docs/shared-chest.md +++ b/docs/shared-chest.md @@ -70,3 +70,18 @@ was empty after loading another character (in-game saves don't call saveHero). - loadHero's extra fix-up for some item classes (`0x5beaa0` / `0x5d3a20`, item classes 5, 6, 8, 0xd, 0x11..0x19, 0x1b, 0x1d) is not repeated. - Multiplayer: not supported. + +## Items replaced by a world's (fixed 2026-10-21) + +Reported: the shared chest's items were gone, other items (potions, gear) in +their cells. The grid holds object ids. A save game loaded into the running +engine (Esc "Laden", cEngine::Load from the render loop 0x60e68d) replaces +all objects, so until the chest reloads on the next tick its ids name the new +world's objects. A hero saved inside that load (characters.cpp Engine_Load: a +migrated character's first load makes its world hero the shared hero with +SaveHero) wrote the shared chest, i.e. those objects, into +shared_chest.json; the next tick loaded them as the chest. Now the chest is +emptied and marked unloaded as soon as a save game is read or a game starts +(RequestLoad), so no save writes it before it is loaded again. The user's +chest was restored from the 2026-10-19 backup (the broken file kept as +backup/shared_chest.broken-2026-10-21.json). diff --git a/src/mods/hud.cpp b/src/mods/hud.cpp index 55f3671..c0a04ac 100644 --- a/src/mods/hud.cpp +++ b/src/mods/hud.cpp @@ -53,14 +53,21 @@ thread_local bool g_eventShifted = false; // Canvas: a 16:9 screen (kCanvasWidth), centred. enum class Anchor { Center, Right, Screen, Canvas }; +// Offset of the main menu's 1024 frame while it is drawn centred (set each +// menu frame by RenderMenuFrame; 0 while a 16:9 screen draws itself). +std::atomic g_menuFrameDx = 0; + int OffsetFor(Anchor anchor) { static const bool enabled = re::ConfigInt("hud", "Enabled", 1) != 0; const int width = mods::widescreen::RenderWidth(); if (anchor == Anchor::Canvas) { return width > kCanvasWidth ? (width - kCanvasWidth) / 2 : 0; } - if (!enabled || width <= kLayoutWidth || mods::widescreen::IsClassicFrame() || - mods::widescreen::MenuIsShowing() || anchor == Anchor::Screen) { + // The main menu's frame shifts the windows in it; over a 16:9 screen + // (no frame) dialogs are centred like in a game (user, 2026-10-21). + const bool inMenuFrame = mods::widescreen::MenuIsShowing() && g_menuFrameDx != 0; + if (!enabled || width <= kLayoutWidth || mods::widescreen::IsClassicFrame() || inMenuFrame || + anchor == Anchor::Screen) { return 0; } return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth; @@ -868,10 +875,6 @@ int HandleCentred(void* event, int (*body)(void* context), void* context) { return body(context); } -// Offset of the main menu's 1024 frame while it is drawn centred (set each -// menu frame by RenderMenuFrame; 0 while a 16:9 screen draws itself). -std::atomic g_menuFrameDx = 0; - void SetHiddenTextures(void* const* surfaces, int count) { for (int i = 0; i < kHiddenTextureMax; ++i) { g_hiddenTextures[i].store(i < count ? surfaces[i] : nullptr, std::memory_order_relaxed); diff --git a/src/mods/screen.cpp b/src/mods/screen.cpp index 23cbde8..1289616 100644 --- a/src/mods/screen.cpp +++ b/src/mods/screen.cpp @@ -4,8 +4,7 @@ #include #include -#include -#include +#include #include #include @@ -46,15 +45,22 @@ constexpr int kHeadingHeight = 30; // frames). std::atomic g_frame{1}; -// The windows whose screen is shown: the main menu's frame is not drawn -// centred while any is (the screen draws itself across the width). -std::mutex g_shownLock; -std::set g_shown; +// The windows whose screen is shown on the menu thread, with the menu frame +// they were last drawn in: the main menu's frame is not drawn centred while +// any is (the screen draws itself across the width). Entries not drawn for +// kShownFrames frames are dropped: the UI is rebuilt when a game starts or +// ends, and a window destroyed while shown kept the menu from being framed +// (user, 2026-10-21: "the old main menu is rendering again"). Menu thread only. +constexpr unsigned kShownFrames = 2; +unsigned g_menuFrame = 0; +std::map g_shown; void NoteShown(const void* window, bool shown) { - std::lock_guard guard(g_shownLock); + if (!widescreen::IsMenuThread()) { + return; + } if (shown) { - g_shown.insert(window); + g_shown[window] = g_menuFrame; } else { g_shown.erase(window); } @@ -91,11 +97,6 @@ void HidePictures() { hud::SetHiddenTextures(surfaces.data(), static_cast(surfaces.size())); } -bool ShownAny() { - std::lock_guard guard(g_shownLock); - return !g_shown.empty(); -} - // The game's logo top left (user, 2026-10-21: "the bright blueish one"), // kLogoMargin from the edges: FX_SACREDLOGO.TGA, the emblem (horns around a // ring of runes) glowing blue on black, no alpha, added onto the background as @@ -134,8 +135,10 @@ void DrawRockWall(void* device, float width, float height) { } // namespace -bool AnyShown() { - return ShownAny(); +bool BeginMenuFrame() { + ++g_menuFrame; + std::erase_if(g_shown, [](const auto& entry) { return g_menuFrame - entry.second > kShownFrames; }); + return !g_shown.empty(); } void DrawBackground(void* device) { @@ -261,7 +264,7 @@ uint32_t __fastcall UiManager_Render(void* self, void* edx, uint32_t argument) { } auto* driver = *reinterpret_cast(addr::g_dxDriver); void* device = driver ? driver->device : nullptr; - const bool framed = !screen::AnyShown(); + const bool framed = !screen::BeginMenuFrame(); if (framed && device) { screen::DrawBackground(device); } diff --git a/src/mods/screen.h b/src/mods/screen.h index 18df739..eaa460b 100644 --- a/src/mods/screen.h +++ b/src/mods/screen.h @@ -50,8 +50,9 @@ constexpr uint32_t kRowSelectedColour = 0xc0604818; // The background of the menu screens across the whole render target (also // beside the main menu's centred frame and full-screen windows in a game). void DrawBackground(void* device); -// Whether a screen is shown (the main menu then does not draw centred). -bool AnyShown(); +// Called once per main menu frame (menu thread): whether a screen is shown +// (the main menu then does not draw centred). +bool BeginMenuFrame(); // Draws the screen of `window`: the background across the whole render // target, then body(context) in canvas coordinates. diff --git a/src/mods/sharedchest.cpp b/src/mods/sharedchest.cpp index 23fd51c..67111c7 100644 --- a/src/mods/sharedchest.cpp +++ b/src/mods/sharedchest.cpp @@ -166,9 +166,17 @@ void Load() { } // A session started or a save game was loaded into it (its objects replace -// the old ones): (re)load on the next tick with a hero. +// the old ones): (re)load on the next tick with a hero. Until then the grid's +// ids are stale (they name the new world's objects) and nothing is saved: a +// hero saved inside the load (characters.cpp, Engine_Load, a migrated +// character's first load) wrote them into the file, the chest's items +// replaced by the world's (user, 2026-10-21). void RequestLoad() { + g_loaded = false; g_loadPending = true; + if (g_constructed) { + ClearGrid(); + } } // The chest grid's place and size, as the cUI_Chest constructor sets them for