Characters: campaigns, "Spielen" main menu, story unlocks
- One world per campaign and difficulty in a character file (sections ancaria<d> / underworld<d>; old world<d> renamed when read). Difficulty unlocks by the shared hero's story completions (hero header +0xed..), shared by both campaigns. - Main menu: "Kampagne Ancaria", "Kampagne Underworld" and "Spielstaende" become "Spielen" (mods/mainmenu.cpp). The load window lists all characters, the world dialog offers both campaigns, "Neuer Charakter" asks for the campaign and opens character creation. - Character window: no Underworld import mode (new Underworld heroes, all classes, slots light up), no "Importieren"; load window without "Speichern". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5bccb0734e
commit
618288e310
7 files changed
+498
-109
No files matched your search
+77
-13
@@ -78,8 +78,11 @@ Decided (2026-10-19, follow-up):
|
||||
0x425160) +0x5704, one byte counter per difficulty (6 entries).
|
||||
`0x424bc0` (k) increments, `0x424be0` (k) reads, `0x424c00` (k, value) sets.
|
||||
The script opcode at 0x4aed26 increments counter[`g_gameLevel`]: the story
|
||||
end. The counters travel in the save header (loader 0x61bb19..) and in the
|
||||
hero file (loadHero 0x606632.., saveHero 0x60444c..). The console cheats
|
||||
end. The counters travel in the save header (dwords at +0x88.., loader
|
||||
0x61bb19..) and in the hero header chunk 0xc3 (bytes at +0xed.., written by
|
||||
0x604360, read by loadHero 0x606632..; +0xec is `g_gameLevel`). Checked in
|
||||
a character file (2026-10-20): Silber and Gold completed, played on Platin =
|
||||
[0, 1, 1, 0, 0] in the hero and the Platin world. The console cheats
|
||||
`bronze` / `silber` / ... (0x615d00..) increment them too.
|
||||
- Vanilla offers a difficulty by **hero level**, not by completion:
|
||||
thresholds `0xad2dc4..0xad2dd4` (5 dwords, parsed from the balance data key
|
||||
@@ -123,6 +126,28 @@ Decided (2026-10-19, follow-up):
|
||||
without a world starts a new game with the shared hero.
|
||||
- The save key and the Esc menu ("Spielstände" becomes "Speichern") save
|
||||
directly, without the save window.
|
||||
- Main menu: "Kampagne Ancaria", "Kampagne Underworld" and "Spielstände"
|
||||
become one "Spielen": the characters, loaded or started in either campaign
|
||||
on any difficulty; new characters from there too. A character file holds a
|
||||
world per campaign and difficulty; the format must take more difficulties
|
||||
later. Decided: no level requirement for the Underworld (vanilla: hero level
|
||||
25, 0x70c48b), story completions open the next difficulty in both campaigns,
|
||||
"Neuer Charakter" is a button in the Spielen window.
|
||||
|
||||
### Campaigns (measured / read 2026-10-20)
|
||||
|
||||
- `g_campaign` 0x182cdc8 (u16): 0 Ancaria, 1 Underworld. The main menu's
|
||||
event handler (0x711fa0) sets it by entry name before an entry handles an
|
||||
event (UI_LABEL_NEWGAME 0, UI_LABEL_NEWADDON 1); both entries show
|
||||
UI_CHARACTER. `cEngine::Save` writes it to the save header +0x24 (2
|
||||
Ancaria, 3 Underworld, 0x61a5c5), `cEngine::Load` sets it from there
|
||||
(0x61bafd). The load window lists only saves of the running campaign
|
||||
(0x71e2b9).
|
||||
- Story completions are counted per difficulty only (the same counter for
|
||||
both campaigns).
|
||||
- Main menu (cUI_MainMenu, entries built by 0x712f30): cUI_StaticText64FX
|
||||
entries (constructor 0x753d70), name +0x30, rect +0x24 (0x20 apart), text
|
||||
id +0x78, command +0x88; main page vector +0x158, children +0x90.
|
||||
|
||||
## Implementation (`src/mods/characters.cpp`, `charfile.{h,cpp}`)
|
||||
|
||||
@@ -130,24 +155,64 @@ Decided (2026-10-19, follow-up):
|
||||
|
||||
- `Documents\Sacred\save\characters\CharacterNN.sacredK`: "SKCH", version 1,
|
||||
section count, then `{name[16], offset, size}` entries. Sections: `meta`
|
||||
(JSON: format, name UTF-8, class, level, lastDifficulty, migratedFrom,
|
||||
heroFrom), `hero` (AMH as `cEngine::SaveHero` writes it), `world<d>` (AMS as
|
||||
`cEngine::Save` writes it). Written to a temp file, then moved over.
|
||||
- `CharacterNN.autosave.sacredK`: same layout, one world (`meta.difficulty`).
|
||||
(JSON: format, name UTF-8, class, level, lastCampaign, lastDifficulty,
|
||||
migratedFrom, heroFrom, heroFromCampaign), `hero` (AMH as
|
||||
`cEngine::SaveHero` writes it), one world per campaign and difficulty,
|
||||
`ancaria<d>` / `underworld<d>` with d = `g_gameLevel` (AMS as
|
||||
`cEngine::Save` writes it). Sections are named, not slotted: a higher
|
||||
difficulty (or another campaign) is one more section. Files of the first
|
||||
format named Ancaria's worlds `world<d>`; they are renamed when read
|
||||
(charfile.cpp) and written under the new name at the next save. Written to
|
||||
a temp file, then moved over.
|
||||
- `CharacterNN.autosave.sacredK`: same layout, one world (`meta.campaign`,
|
||||
`meta.difficulty`).
|
||||
- The game's save folder is redirected to `characters\_work` (userfolders);
|
||||
the game reads and writes `world.PAK` / `hero.pax` there.
|
||||
- Deleted characters move to `characters\deleted\`.
|
||||
|
||||
### Main menu (`src/mods/mainmenu.cpp`)
|
||||
|
||||
After the build (0x712f30) the NEWGAME and NEWADDON entries are removed (out
|
||||
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)
|
||||
|
||||
- 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 row's second line (call
|
||||
0x71db2a to SetText 0x736f00) shows "Stufe N - difficulty".
|
||||
- Laden (action 4 / 0xb) opens the difficulty dialog when there is more than
|
||||
one choice: worlds, newer autosaves "(Autosave)", and "(neu)" for every
|
||||
difficulty up to max(highest world, Silber) without a world (needs the
|
||||
shared hero; not in multiplayer, byte 0x182cb6c).
|
||||
"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
|
||||
exported heroes its slots were empty, and slots below level 25 never lit
|
||||
up (render 0x70f4df / 0x70f57c) (reported 2026-10-20). All eight campaign
|
||||
checks of the window (0x70c473, 0x70d199, 0x70e3e8, 0x70eade, 0x70eeb2,
|
||||
0x70f4df, 0x70f57c, 0x70f6aa) read a constant Ancaria instead: creation
|
||||
with all classes in both campaigns; the start (ExecuteAction 0x70d0f0,
|
||||
action 1) uses `g_campaign` as set.
|
||||
- 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
|
||||
@@ -202,7 +267,6 @@ kept, the newest file overall is the shared hero.
|
||||
|
||||
## Open
|
||||
|
||||
- Unlocking by story completion (counters at save header +0x88..).
|
||||
- Reset a difficulty.
|
||||
- Remove the export / import UI.
|
||||
- Silent autosave every 10 minutes.
|
||||
|
||||
Reference in new issue
Block a user