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

+52 -45
View File
@@ -177,26 +177,55 @@ of both vectors, deleted) and UI_LABEL_LOADGAME ("Spielstände", shows
UI_WND_SAVEGAME) moves to the top with the text "Spielen"; the others move up,
keeping the gap before "Beenden".
### Load window (cUI_Savegame)
### Spielen screen (cUI_Savegame)
Request 2026-10-20: redesign the load window, 16:9, using the space (layout
agreed: characters left, the character in the middle, worlds right, buttons
below; our own background). Built on the shared screen kit
(docs/screens.md); the window's own drawing and mouse handling are not used.
- The window still scans: before each scan (0x71e330) one preview
`GAMEnn.PAK` per character is written into `_work`; row nn = character nn.
Its entries are only needed for the load.
- Left: the characters, newest save first (name, class, level; "im Spiel" for
the one being played). Click selects, double click plays.
- Middle: name, class and level, the world played last with its date. (A
render of the hero is planned.)
- Right: a grid campaigns x difficulties. A cell shows the save's date,
"+ Autosave" when the autosave is newer, "Story abgeschlossen", "Neu" for a
difficulty that is open but has no world, "–" when closed. Open: Bronze and
Silber, every difficulty up to the highest with a world in either campaign,
and the one after each difficulty whose story the shared hero completed
(hero header +0xed..); new worlds need the shared hero, not in multiplayer
(byte 0x182cb6c). Click selects, double click plays. With a newer autosave
"Laden von:" picks the save or the autosave (default: the autosave).
"Welt zurücksetzen" for a saved world (not the character being played).
- Buttons: Neuer Charakter (main menu only), Umbenennen, Löschen (not for the
character being played), Zurück (action 0xc), Spielen.
- Spielen: a saved world loads through the window's load action (4, with its
"really load?" confirmation in a game): the window's selection is set to
its first entry and `g_loadRow` names the row, which `Engine_Load` takes
instead of the entry's. A new world starts a game (below; in a game after
stopping the running one). The window's own load / default actions (Enter)
run Spielen too; its delete actions (7, 8) are ignored.
- Dialogs over the screen: rename (keys go to the field through the input
key capture; the name is only in the hero header chunk 0xc3 (+8, UTF-16,
0x40 chars): rewritten in the hero, every world and the autosave, plus
`meta.name`), campaign of a new character, confirmations of reset and
delete. Buttons are cUI_Button2 copies of the load button
(`game::NewButtonLike`) with our own text ids and overrides.
- Delete moves the character file and its autosave to `charactersdeleted`.
- Neuer Charakter: sets `g_campaign`, hides the window and shows UI_CHARACTER
(the game's character creation, as the main menu's campaign entries did).
The first save makes the character file.
- Reset (decided 2026-10-20) removes the world section (a copy goes to
`charactersdeletedCharacterNN.sacredK.<section>.PAK`) and an autosave of
that world, and clears the shared hero's story completion of that
difficulty (hero header +0xed + d); the game counts it per difficulty, so
the next difficulty closes in both campaigns unless a world keeps it open.
### Character window (cUI_Character)
- Before each scan (0x71e330) one preview `GAMEnn.PAK` per character is
written into `_work` (header, thumbnail, world time, description
"Name - Klasse"); row nn = character nn. The preview's campaign field is
the running campaign, so the window lists every character. The row's
second line (call 0x71db2a to SetText 0x736f00) shows
"Stufe N - Campaign - difficulty".
- Laden (action 4 / 0xb) opens the world dialog when there is more than one
choice: per campaign worlds, newer autosaves "(Autosave)", and "(neu)" for
every difficulty without a world up to the highest open one (needs the
shared hero; not in multiplayer, byte 0x182cb6c). Open: Bronze and Silber,
every difficulty up to the highest with a world in either campaign, and the
one after each difficulty whose story the shared hero completed (hero
header +0xed..). `g_campaign` is set to the chosen world's before loading
or starting.
- "Neuer Charakter" (two places below Löschen; main menu only) asks for the
campaign, sets `g_campaign`, hides the window and shows UI_CHARACTER (the
game's character creation, as the main menu's campaign entries did). The
first save makes the character file.
- Vanilla made no new heroes for the Underworld: `cUI_Character::Show`
(vf24 0x70e2a0) switches to mode 7 in single player with `g_campaign` 1
(0x70e3e8), the import of an exported hero of level 25+ (0x70eedc); with no
@@ -209,30 +238,6 @@ keeping the gap before "Beenden".
- Class selection without "Importieren" (request 2026-10-20): button +0x3a0
(caption UI_IMPORT in modes 0/1); its modes-0/1 draw (0x70f67e) is removed,
its click (0x70eab4 / 0x70eac7, SetMode(7)) jumps to the handler's end.
- Load window without "Speichern" (request 2026-10-20; saving is direct):
button +0x820 (action 1, disabled in load mode, so its click falls through)
is not drawn (jump over 0x71ddbc); Löschen (+0x8dc) takes its rect, our
buttons follow below it.
- "Umbenennen" (below Löschen; not for the character being played) asks for
a name; keys go to the field through the input key capture. The name is
only in the hero header chunk 0xc3 (+8, UTF-16, 0x40 chars): rewritten in
the hero, every world and the autosave, plus `meta.name`. Confirmed: the
game loads renamed characters without complaint.
- Dialog buttons are cUI_Button2 copies of the load button
(`game::NewButtonLike`); their captions are our own text ids with
overrides (texts.cpp checks overrides for our ids too).
- "Zurücksetzen" (below Umbenennen; a selected character with the shared
hero and a world, not the one being played) lists the character's worlds,
then asks "<Welt> zurücksetzen?". A reset (decided 2026-10-20) removes the
world section (a copy goes to `charactersdeletedCharacterNN.sacredK.<section>.PAK`)
and an autosave of that world, and clears the shared hero's story
completion of that difficulty (hero header +0xed + d); the game counts it
per difficulty, so the next difficulty closes in both campaigns unless a
world keeps it open. The hero keeps everything else.
- The dialog is one generic list: each kind fills a title and entries
(FillDialog), "Abbrechen" last.
### Load
`cEngine::Load` (0x61b790) of a listed row: the chosen world and the hero are
@@ -265,7 +270,8 @@ Silent autosave: 10 minutes after the last save of any kind (or the game's
start), `SaveOnTick` does the direct save marked as an autosave, so it goes
into the autosave file. While a window is open or a save runs it waits for
the next tick. A character never saved yet gets its character file from it.
Interval: `sacredK.ini` `[characters] AutosaveMinutes` (default 10, 0 off).
Interval: options window, *Allgemein*, *Autosave (Min.)* (docs/options.md); kept in
`sacredK.ini` `[characters] AutosaveMinutes` (default 10, 0 off).
Direct save (K key, Esc menu "Speichern"): on the next engine tick, as the
quicksave does (0x618ebb): `cUI_Manager::CanOpenWindow` (0x75a690),
@@ -299,5 +305,6 @@ kept, the newest file overall is the shared hero.
## Open
- Quickload (row 0) does not know characters.
- Quickload (row 0) does not know characters (not needed, user 2026-10-20).
- Spielen screen: a render of the hero; scrolling the list beyond 9 characters.
- Multiplayer (see `todo.md`).
+15
View File
@@ -117,3 +117,18 @@ choice (PICKUPAUTO radio buttons, their labels, the separator; matched by
pointer and control name at +0x30) is never shown, a framed table below the
auto-pickup frame sets a mode per category. Its frame is made like the key
page's (`AddFrame`).
## Allgemein: autosave interval (2026-10-20)
Request: the silent autosave's interval (docs/characters.md, Save) settable
in the options, not only in `sacredK.ini`. The general frame moved up into
the auto-pickup frame's place; it is taller than the frames beside it, so the
new frame goes below the lowest frame of the left column. It copies the
right column's lowest frame (Internet-Optionen): its height and the gap the
right column leaves between its frames, measured from the window's frames
when it is made (a lower frame broke its side ornaments, a smaller gap joined
the borders: screenshots 2026-10-20). One row *Autosave (Min.)*: Aus / 5 /
10 / 15 / 30 (the key page's box colour is not seen on the dark backdrop: a
lighter one). A click selects, OK writes
`[characters] AutosaveMinutes` (`characters::SetAutosaveMinutes`, the timer
uses it at once), Cancel drops it.
+39
View File
@@ -0,0 +1,39 @@
# 16:9 menu screens
Request (2026-10-20): the game's menu screens are laid out for 1024x768; we
design our own UIs for them. There is no 16:9 artwork for the game, so the
background is our own (option 3 of the ones discussed: dark, no picture), and
the approach is shared by all menu screens. Target: 16:9, 1920x1080, which is
1365x768 in UI units; wider screens only get more background at the sides
(not designed for ultrawide).
## How a screen works
- `mods/screen.{h,cpp}`: a screen replaces a full-screen window's drawing and
mouse handling. Its render hook calls `screen::Render(window, device, body,
context)`: the background across the whole render target, then the body in
canvas coordinates (1365x768, centred: `hud::RenderCanvas`, the HUD
layer's offset machinery with `Anchor::Canvas`; drawing, the cursor and the
window's hit tests shifted alike). Mouse events go through `screen::Handle`
(`hud::HandleCanvas`). Keys stay the window's.
- In the main menu the game renders a classic 1024x768 frame (presented 4:3).
While a screen's window is shown, `widescreen::SetWideMenuScreen` makes the
menu thread render and present the full width.
- Parts: `screen::Panel` (dark backdrop and the game's frame, `cUI_Frame`,
game/ui/frame.h, made anew when its rect changes), `screen::Button` (a
button of the game's with the look of one of the window's,
`game::NewButtonLike`), `screen::Text`, `screen::Fill`,
`screen::DoubleClick`. Spacing constants from measurements: frames reach
0x20 beyond their rects and need a height of about 0x40, buttons reach 0x21.
Frames are built of 64-unit pieces: widths and heights in steps of 64
(`screen::kFrameStep`), other sizes leave gaps (screenshot 2026-10-20).
- Fonts (logged 2026-10-20): font 0 is another class (no line height field);
the TrueType fonts 1..7 have line heights 20, 19, 25, 31, 25, 28, 13.
Screens use font 1; 4 or 6 would suit headings.
- Inside the canvas scope the HUD layer does not stretch "full 1024 layout"
quads (`g_canvasScope`).
## Screens
- Spielen (the load window, cUI_Savegame): docs/characters.md, "Spielen
screen".
+17 -9
View File
@@ -184,12 +184,10 @@ From the characters rework (`docs/characters.md`, request of 2026-10-19):
are one byte per difficulty in cStatsManager +0x5704 (read 0x424be0) and
travel in the save header (+0x88..+0x98) and the hero file. Done
2026-10-20 (docs/characters.md).
- **Autosave interval in the options window** (now only
`sacredK.ini` `[characters] AutosaveMinutes`).
- **Quickload (row 0) does not know characters.** Ctrl+Z saves into the
current character, but the quickload event (0x116, engine +0x68 = row,
flag 0x200) loads `GAME00.PAK`, which is not listed; make it reload the
current character's world (or its newer autosave).
- **Autosave interval in the options window.** Done 2026-10-20: Allgemein
page, "Spielstände" frame (docs/options.md).
- Quickload (row 0, `GAME00.PAK`) does not know characters; not needed
(user, 2026-10-20).
## Two saves in a row (2026-10-20, not reproduced)
@@ -200,10 +198,20 @@ flag 0x40 is set in cEngine::ReceiveEvent at 0x618876 (quicksave),
log of the event that sets the flag (dwords 0..9) did not catch it again;
add it back if it shows.
Once (2026-10-20, not reproduced) an autosave seemed to fire right after a
new world started through the hero chain (Character08: hero world loaded,
then the new world). The log of that run had no save at all; the timed
autosave counts from the game start (OnGameStart).
## Savegames screen redesign ("Spielen")
Asked 2026-10-20: redesign the load window (cUI_Savegame, the "Spielen"
screen, characters.cpp): show a render of the selected character, use the
space far better. The user looks for 16:9 artwork so the screens can be
widened beyond the 1024 frame (now drawn in a centred 1024x768 viewport with
black sides, hud.cpp FrameScope).
space far better. No 16:9 artwork exists for the game (user, 2026-10-20):
we design our own UIs, using the width beyond the 1024 frame (now drawn in
a centred 1024x768 viewport with black sides, hud.cpp FrameScope). Design
for 16:9 (1920x1080, 1365x768 UI units), not ultrawide (user). Screen done
2026-10-20 (docs/characters.md, "Spielen screen"; docs/screens.md). Open:
the render of the selected hero (the hero panel shows text only; reuse
cUI_Character's 3D hero?), list scrolling beyond 9 characters, larger
heading font.