The Internet-Optionen frame becomes Bildschirm with a Monitor dropdown (Hauptmonitor or a connected monitor, kept by device path in [display] Monitor=). The borderless window and the render size use that monitor, falling back to the main monitor when it is not connected. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
9.6 KiB
Options window: tabs and key bindings
Request (2026-10-18): the settings screen gets three tabs: Allgemein,
Beute (loot filter; first iteration: the game's loot options moved there
from the common page) and Tastenbelegung (key bindings, so far only
settable in sacredK.ini). Code: src/mods/options.cpp; bindings in
src/mods/input.cpp.
The window (cUI_Options)
- vtable
0x89505c, constructor0x716570(name, rect, parent, flags;ret 0x18), destructor0x7165f0; controls built by0x7184e0. - vf44
0x716640(renderer, ?, ?;ret 0xc): window render (0x727040), then drives the sliders while the mouse is held. vf100x716b30: events. 0x717ea0loads the settings into the controls when the window is shown;0x717460(thiscall, plainret) writes them back — called for OK.- Controls: vector at
+0x90(begin) /+0x94(end); the 16 background tiles are in the window's own list+0x78. Buttons:+0x158Cancel,+0x15cOK. Pickup: radio buttons+0x16c/+0x170/+0x174(PICKUPAUTO0/1/2), checkbox+0x178(PICKUPANIM). - Layout (1024x768): frames at x
0x40/0x220, y0x40/0x120/0x200, borders0x14outside, labels above (y0x20/0x100/0x1e0). Left: graphics, auto-pickup (AUTOCOLLECT), general; right: sound volume, 3D sound, internet. OK / Cancel at y0x284. - cUI_Frame (vtable
0x8955ac): its render0x73a540does not test the visible flag, only+0xdc(set to 1 by its constructors).
How the mod works
- After the constructor: every control in
+0x90is put on a page by its position — left column middle row → Beute, everything else → Allgemein, OK / Cancel on all pages. The auto-pickup group moves up one row (to the top of its page), the general group below it moves up into its place. - A cUI_TabSheet (
game/ui/tabs.h) at the top frames' border; its headers (default row layout) sit above the frame labels. - Each frame (vf44) the controls not on the active page are hidden (visible
flag, frames also
+0xdc); controls hidden by the mod are shown again on their page. The game re-shows controls when the window opens, so this runs every frame. - Tastenbelegung is drawn by the mod: rows per binding, grouped
(Kampfkünste, Waffensets, Bewegung, Aktionen), a key box per row. A click on
a box captures the next key press (
input::SetKeyCapture, swallowed before the game sees it): Esc cancels, Backspace unbinds, a bindable key is taken (and removed from any other action). The page works on a copy loaded when the window opens; OK (0x717460) saves it to[keys]insacredK.ini, Cancel drops it. - Movement keys (W/A/S/D) are bindings now (
MoveUp/Left/Down/Right);mods/wasd.cpppolls the bound keys.
Follow-ups (2026-10-18)
- Pages moved down 24 px (
kContentShift) for room below the tab headers. - Alle zurücksetzen (left of OK / Cancel) sets every binding back to its default; saved with OK like any other change.
- Potion bindings Trank 1–5 and Trank 1 für Gefährten (default: no key).
They call
cEngine::UseActionSlot(0x613040, thiscall (kind, on ally),ret 8) on the engine tick. The game's own potion hotkeys are off: its engine hotkey switch (cEngine::ReceiveEvent, index bytes0x619adcper upper-case character − 0x20, targets0x619ac0) had Space → kind 1 (Shift: 5), B → kind 1 on the weakest ally, Q 2, W 3, E 4, R 5; those six entries now point at index 6 (no hotkey).ActionSlot3(W's action on G) is gone. - The window is drawn like the save game / export screens: black beside the
1024 frame (
hud::RenderFramed). - Key bindings page: a backdrop (
0x724ed0) and a frame (0x739f90) per column, created with the window's controls and shown only on that page. (Drawing the window's existing ones at other rects did not fit: a frame's border pieces keep the place they were made at.) - Pages move down 24 px under the tab headers. A slider (cUI_Scrollbar,
vtable
0x895664) moves with its parts: minus, plus, track and knob are embedded controls (+0x94 / +0x110 / +0x18c / +0x208) with rects in window coordinates, and the knob is placed from +0x78 / +0x7c each frame. - Key page layout (2026-10-19): the two frames span the other pages' frame area (top row's top to the bottom of Internet-Optionen); the hints sit in the bottom row next to Alle zurücksetzen, since they hold for the whole page.
- Potion kinds are named by the bottom bar's tooltip texts:
PotionTooltipText0x43fa90(kind) → text id by key, e.g.<color=FFFFFF80>Heiltrank <colorw=FFFFFFFF>[SPC](kinds 1–5: Heiltrank, Sog der Hölle, Trank des Mentors, Antidot der Viper, Trank der Konzentration / Teleportationstrank; read inscripts/de/SRglbl.res). Shown, the tags are colour codes (backslash, letter, 8 hex digits:\dFFFFFF80Heiltrank \wFFFFFFFF[SPC]);game::PotionKindNamestrips them and the key; the tooltips get the bound key in the brackets, or none (game::SetTextOverride, mods/potioncounts.cpp). - No posted game keys any more: Spiel speichern calls what the UI manager's
S case (
0x757775) does: save window built (manager slot0x2b),CanOpenWindow0x75a690, engine event (vtable0x88eb38, 0x13, 5, 1), then window command show (0x11) forUI_WND_SAVEGAME(game/ui/windows.h, shared with escape.cpp's hide 0x12).
Karte, Minikarte, Beschriftungen (2026-10-19)
Request: M (map), Tab (minimap) and Alt (text overlay) bindable too.
- M: the UI manager's letter switch (
cUI_Manager::ReceiveEvent0x757120, upper-case character − 'C', index bytes 0x75868c, targets 0x758660) showsUI_WND_MEGAMAP(slot 0x25) unless a blocking window is open; while the map is open its own branch (cmp 'M' at 0x757f86) hides it. Switched off (M → index 10, the compare → -1); the Karte binding does the same checks and sends the window command. - Tab: the in-game window procedure 0x8110d0 shows
UI_WND_OVERVIEWMAPon key down (switch on vk − 9: index bytes 0x812124, case 0x81146d) and hides it on key up (0x812270, case 0x8117f8). Switched off (Tab → the "other key" indices 4 / 3); Minikarte (halten) shows / hides it on the bound key's down / up. - Alt: no message, the game polls
GetAsyncKeyState(VK_MENU)for its name overlay in seven places (DrawObjectLabels 0x627260, PickObjectAt 0x626d30, cEngine 0x60f1d0 / 0x611f10 / 0x617360, cCreature 0x599880, the map's debug teleport 0x6c7ec0). Theircall [GetAsyncKeyState]read our pointer instead:LabelsKeyStatereturns the bound key's state. - Alt and F10 come as WM_SYSKEYDOWN: the key capture takes those too.
- Key page: 20 lines per column (line 25 px) to fit the three rows.
Beute: loot filter (2026-10-19)
The page holds the loot filter (docs/loot-filter.md): the game's pickup
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.
Allgemein: preferred monitor (2026-10-25)
Request: a setting for the monitor the game starts on; with none set, or the preferred monitor not connected, the system's main monitor.
src/mods/monitor.cpp: the monitors (EnumDisplayMonitors), named and told apart through the display configuration (QueryDisplayConfig,DisplayConfigGetDeviceInfo: the GDI name\.\DISPLAYnof each path's source, the monitor's EDID name and device path of its target). The device path is the id: it stays the same across restarts and when monitors are rearranged, unlike\.\DISPLAYn. Kept insacredK.ini[display] Monitor=(empty: the main monitor).GameMonitorRectis asked where the screen is decided at startup: the borderless window covers that monitor (borderless.cpp), and the render size follows its aspect ratio (widescreen.cppApplyRenderSize). The log linemonitor: <name> WxH at (x,y)says which one was used and why. A change applies at the next start.- In the options (user's choice of place): the Internet-Optionen frame
(Datendurchsatz, multiplayer only) becomes Bildschirm. Its controls
(
UI_CFG_LANSPEEDlabel, DSL / ISDN radio buttons, everything insideUI_FRAME_INTERNET) and the title labelUI_LABEL_WANare hidden; the game keeps the saved Datendurchsatz. The title text (keyUI_CFG_WAN) is overridden with "Bildschirm" before the window is made. - One row: Monitor and a box with the choice (Hauptmonitor, then each monitor as "name (WxH)", "Nicht verbunden" for a preferred monitor that is missing). A click opens the list upwards (the frame is the lowest one); a click on an entry picks it, any click closes the list. A changed choice shows "Ab dem nächsten Spielstart" below the box. OK saves, Cancel drops.