Characters: 16:9 "Spielen" screen, autosave interval option

- Shared screen kit (mods/screen, docs/screens.md): a 1365x768 canvas
  centred in the render width on our own dark background, the game's
  frames and buttons; the main menu renders full width while it is shown.
- The load window (cUI_Savegame) is replaced by the Spielen screen:
  character list, hero panel, campaign x difficulty grid, save/autosave
  choice, rename/delete/reset/new character dialogs.
- Migrated characters without a shared hero load their hero's world
  first, then start the new world.
- Options, Allgemein: autosave interval (Aus/5/10/15/30 minutes).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-20 23:20:45 +02:00
1 parent 732d9c2abb
commit 34a119433a
16 files changed
+1391 -468

No files matched your search

+28 -2
View File
@@ -30,6 +30,7 @@
#include "game/addresses.h"
#include "game/render/dx_driver7.h"
#include "game/ui/events.h"
#include "mods/hud.h"
#include "mods/widescreen.h"
#include "re/config.h"
#include "re/hook.h"
@@ -47,11 +48,15 @@ thread_local bool g_eventShifted = false;
// Screen: laid out across the whole render width by its mod (drawn unshifted).
enum class Anchor { Center, Right, Screen };
// Canvas: a 16:9 screen (kCanvasWidth), centred.
enum class Anchor { Center, Right, Screen, Canvas };
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) {
return 0;
@@ -341,6 +346,9 @@ size_t VertexSize(DWORD fvf) {
return size;
}
// A 16:9 screen draws (RenderCanvas): no 1024 layout to stretch.
thread_local bool g_canvasScope = false;
// Returns vertices shifted by g_offsetX, or the input when nothing to do.
// Primitives covering the whole 1024x768 layout (e.g. the dimming behind
// modal dialogs) are stretched across the render width instead.
@@ -359,7 +367,7 @@ void* ShiftVertices(DWORD fvf, void* vertices, DWORD count) {
minY = std::min(minY, position[1]);
maxY = std::max(maxY, position[1]);
}
const bool fullLayout = minX <= 0.5f && maxX >= kLayoutWidth - 0.5f && minY <= 0.5f && maxY >= 767.5f;
const bool fullLayout = !g_canvasScope && minX <= 0.5f && maxX >= kLayoutWidth - 0.5f && minY <= 0.5f && maxY >= 767.5f;
const float scale = static_cast<float>(mods::widescreen::RenderWidth()) / kLayoutWidth;
for (DWORD i = 0; i < count; ++i) {
auto* x = reinterpret_cast<float*>(buffer.data() + stride * i);
@@ -788,6 +796,24 @@ int HandleCentred(void* event, int (*body)(void* context), void* context) {
return body(context);
}
int CanvasOffsetX() {
return OffsetFor(Anchor::Canvas);
}
int RenderCanvas(void* window, int (*body)(void* context), void* context) {
DrawOffset offset(window, OffsetFor(Anchor::Canvas));
const bool outer = !g_canvasScope;
g_canvasScope = true;
const int result = body(context);
g_canvasScope = !outer;
return result;
}
int HandleCanvas(void* event, int (*body)(void* context), void* context) {
EventOffset offset(event, OffsetFor(Anchor::Canvas));
return body(context);
}
} // namespace mods::hud
namespace game {