Shared chest, menu after a game, dialogs over 16:9 screens
- Shared chest: emptied and not saved from the moment a save game is read until it reloads; a hero saved inside a load wrote the new world's objects (stale ids) as the chest's items. - Main menu after a game: shown 16:9 screens are tracked on the menu thread only and expire after 2 frames; a destroyed in-game window kept the menu unframed (old pictures, frame at the left). - Dialogs over a 16:9 screen in the main menu are centred, their dimming covers the screen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c4aacbcdd2
commit
0ef3cf4bbd
6 files changed
+68
-25
No files matched your search
@@ -75,3 +75,16 @@ window's own pictures removed.
|
|||||||
|
|
||||||
- Spielen (the load window, cUI_Savegame): docs/characters.md, "Spielen
|
- Spielen (the load window, cUI_Savegame): docs/characters.md, "Spielen
|
||||||
screen".
|
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.
|
||||||
@@ -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`,
|
- loadHero's extra fix-up for some item classes (`0x5beaa0` / `0x5d3a20`,
|
||||||
item classes 5, 6, 8, 0xd, 0x11..0x19, 0x1b, 0x1d) is not repeated.
|
item classes 5, 6, 8, 0xd, 0x11..0x19, 0x1b, 0x1d) is not repeated.
|
||||||
- Multiplayer: not supported.
|
- 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).
|
||||||
+9
-6
@@ -53,14 +53,21 @@ thread_local bool g_eventShifted = false;
|
|||||||
// Canvas: a 16:9 screen (kCanvasWidth), centred.
|
// Canvas: a 16:9 screen (kCanvasWidth), centred.
|
||||||
enum class Anchor { Center, Right, Screen, Canvas };
|
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<int> g_menuFrameDx = 0;
|
||||||
|
|
||||||
int OffsetFor(Anchor anchor) {
|
int OffsetFor(Anchor anchor) {
|
||||||
static const bool enabled = re::ConfigInt("hud", "Enabled", 1) != 0;
|
static const bool enabled = re::ConfigInt("hud", "Enabled", 1) != 0;
|
||||||
const int width = mods::widescreen::RenderWidth();
|
const int width = mods::widescreen::RenderWidth();
|
||||||
if (anchor == Anchor::Canvas) {
|
if (anchor == Anchor::Canvas) {
|
||||||
return width > kCanvasWidth ? (width - kCanvasWidth) / 2 : 0;
|
return width > kCanvasWidth ? (width - kCanvasWidth) / 2 : 0;
|
||||||
}
|
}
|
||||||
if (!enabled || width <= kLayoutWidth || mods::widescreen::IsClassicFrame() ||
|
// The main menu's frame shifts the windows in it; over a 16:9 screen
|
||||||
mods::widescreen::MenuIsShowing() || anchor == Anchor::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 0;
|
||||||
}
|
}
|
||||||
return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth;
|
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);
|
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<int> g_menuFrameDx = 0;
|
|
||||||
|
|
||||||
void SetHiddenTextures(void* const* surfaces, int count) {
|
void SetHiddenTextures(void* const* surfaces, int count) {
|
||||||
for (int i = 0; i < kHiddenTextureMax; ++i) {
|
for (int i = 0; i < kHiddenTextureMax; ++i) {
|
||||||
g_hiddenTextures[i].store(i < count ? surfaces[i] : nullptr, std::memory_order_relaxed);
|
g_hiddenTextures[i].store(i < count ? surfaces[i] : nullptr, std::memory_order_relaxed);
|
||||||
|
|||||||
+19
-16
@@ -4,8 +4,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <map>
|
||||||
#include <set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -46,15 +45,22 @@ constexpr int kHeadingHeight = 30;
|
|||||||
// frames).
|
// frames).
|
||||||
std::atomic<unsigned> g_frame{1};
|
std::atomic<unsigned> g_frame{1};
|
||||||
|
|
||||||
// The windows whose screen is shown: the main menu's frame is not drawn
|
// The windows whose screen is shown on the menu thread, with the menu frame
|
||||||
// centred while any is (the screen draws itself across the width).
|
// they were last drawn in: the main menu's frame is not drawn centred while
|
||||||
std::mutex g_shownLock;
|
// any is (the screen draws itself across the width). Entries not drawn for
|
||||||
std::set<const void*> g_shown;
|
// 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<const void*, unsigned> g_shown;
|
||||||
|
|
||||||
void NoteShown(const void* window, bool shown) {
|
void NoteShown(const void* window, bool shown) {
|
||||||
std::lock_guard guard(g_shownLock);
|
if (!widescreen::IsMenuThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (shown) {
|
if (shown) {
|
||||||
g_shown.insert(window);
|
g_shown[window] = g_menuFrame;
|
||||||
} else {
|
} else {
|
||||||
g_shown.erase(window);
|
g_shown.erase(window);
|
||||||
}
|
}
|
||||||
@@ -91,11 +97,6 @@ void HidePictures() {
|
|||||||
hud::SetHiddenTextures(surfaces.data(), static_cast<int>(surfaces.size()));
|
hud::SetHiddenTextures(surfaces.data(), static_cast<int>(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"),
|
// 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
|
// 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
|
// 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
|
} // namespace
|
||||||
|
|
||||||
bool AnyShown() {
|
bool BeginMenuFrame() {
|
||||||
return ShownAny();
|
++g_menuFrame;
|
||||||
|
std::erase_if(g_shown, [](const auto& entry) { return g_menuFrame - entry.second > kShownFrames; });
|
||||||
|
return !g_shown.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawBackground(void* device) {
|
void DrawBackground(void* device) {
|
||||||
@@ -261,7 +264,7 @@ uint32_t __fastcall UiManager_Render(void* self, void* edx, uint32_t argument) {
|
|||||||
}
|
}
|
||||||
auto* driver = *reinterpret_cast<DxDriver7**>(addr::g_dxDriver);
|
auto* driver = *reinterpret_cast<DxDriver7**>(addr::g_dxDriver);
|
||||||
void* device = driver ? driver->device : nullptr;
|
void* device = driver ? driver->device : nullptr;
|
||||||
const bool framed = !screen::AnyShown();
|
const bool framed = !screen::BeginMenuFrame();
|
||||||
if (framed && device) {
|
if (framed && device) {
|
||||||
screen::DrawBackground(device);
|
screen::DrawBackground(device);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -50,8 +50,9 @@ constexpr uint32_t kRowSelectedColour = 0xc0604818;
|
|||||||
// The background of the menu screens across the whole render target (also
|
// 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).
|
// beside the main menu's centred frame and full-screen windows in a game).
|
||||||
void DrawBackground(void* device);
|
void DrawBackground(void* device);
|
||||||
// Whether a screen is shown (the main menu then does not draw centred).
|
// Called once per main menu frame (menu thread): whether a screen is shown
|
||||||
bool AnyShown();
|
// (the main menu then does not draw centred).
|
||||||
|
bool BeginMenuFrame();
|
||||||
|
|
||||||
// Draws the screen of `window`: the background across the whole render
|
// Draws the screen of `window`: the background across the whole render
|
||||||
// target, then body(context) in canvas coordinates.
|
// target, then body(context) in canvas coordinates.
|
||||||
|
|||||||
@@ -166,9 +166,17 @@ void Load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A session started or a save game was loaded into it (its objects replace
|
// 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() {
|
void RequestLoad() {
|
||||||
|
g_loaded = false;
|
||||||
g_loadPending = true;
|
g_loadPending = true;
|
||||||
|
if (g_constructed) {
|
||||||
|
ClearGrid();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The chest grid's place and size, as the cUI_Chest constructor sets them for
|
// The chest grid's place and size, as the cUI_Chest constructor sets them for
|
||||||
|
|||||||
Reference in new issue
Block a user