- CharacterNN.sacredK holds the shared hero and one world per difficulty, plus an autosave file; the game works in save\characters\_work - Load window: one row per character, a difficulty dialog on Laden (worlds, newer autosaves, "(neu)" starts a new game with the shared hero), Umbenennen, delete moves the character to characters\deleted - The world keeps its own portals and hero position when the shared hero is put on - K and the Esc menu's "Speichern" save directly (quicksave path) - One-time migration of the old save rows and exported heroes, after a backup - Startup no longer hangs in the game's foreground loop (0x810af0) - Text overrides apply to our own text ids too; shared cUI_Button2 helpers in game/ui/button.h Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
67 lines
2.8 KiB
C++
67 lines
2.8 KiB
C++
#pragma once
|
|
// cUI_Button2 (vtable 0x895308, 0xbc bytes, constructor 0x72a180, plain ret).
|
|
// Windows set their buttons up as cUI_Options 0x7184e0 does: images
|
|
// (+0x54..+0x87), sounds (0x72acf0 -> +0x90..+0x9f), caption (0x72ad60 (text
|
|
// id, font 1) -> +0xa4 / +0xa0), caption offset (0x72ad40 -> +0xb0), name
|
|
// (0x732090).
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#include "game/ui/tabs.h"
|
|
#include "game/ui/text.h"
|
|
|
|
namespace game {
|
|
|
|
constexpr size_t kButtonSize = 0xbc;
|
|
constexpr size_t kButtonLook = 0x54;
|
|
constexpr size_t kButtonLookEnd = 0x88;
|
|
constexpr size_t kButtonSounds = 0x90;
|
|
constexpr size_t kButtonSoundsEnd = 0xa0;
|
|
constexpr size_t kButtonCaptionOffset = 0xb0; // x, y (int16)
|
|
constexpr uint32_t kButtonFlags = 5; // 0x731e70, as 0x7184e0 passes
|
|
|
|
// Control vtable entries: vf10 handles an event (ret 4), vf14 renders (ret 4).
|
|
constexpr size_t kVfHandleEvent = 0x10 / 4;
|
|
constexpr size_t kVfRender = 0x14 / 4;
|
|
|
|
// A new button with `model`'s look and sounds at `rect` (relative to the
|
|
// parent it is added to, or the screen frame without one), captioned with text
|
|
// id `caption`.
|
|
inline uint8_t* NewButtonLike(const uint8_t* model, const UiRect& rect, uint32_t caption, const char* name) {
|
|
void* memory = GameNew(kButtonSize);
|
|
if (!memory) {
|
|
return nullptr;
|
|
}
|
|
using ConstructFn = uint8_t*(__fastcall*)(void*, void*);
|
|
uint8_t* button = reinterpret_cast<ConstructFn>(0x0072a180)(memory, nullptr);
|
|
using FlagsFn = void(__fastcall*)(void*, void*, uint32_t);
|
|
reinterpret_cast<FlagsFn>(0x00731e70)(button, nullptr, kButtonFlags);
|
|
std::copy(model + kButtonLook, model + kButtonLookEnd, button + kButtonLook);
|
|
std::copy(model + kButtonSounds, model + kButtonSoundsEnd, button + kButtonSounds);
|
|
std::copy(model + kButtonCaptionOffset, model + kButtonCaptionOffset + 4, button + kButtonCaptionOffset);
|
|
*reinterpret_cast<UiRect*>(button + kControlRect) = rect;
|
|
using CaptionFn = void(__fastcall*)(void*, void*, uint32_t, uint32_t);
|
|
reinterpret_cast<CaptionFn>(0x0072ad60)(button, nullptr, caption, 1);
|
|
using NameFn = void(__fastcall*)(void*, void*, const char*);
|
|
reinterpret_cast<NameFn>(0x00732090)(button, nullptr, name);
|
|
return button;
|
|
}
|
|
|
|
inline int ControlHandleEvent(void* control, void* event) {
|
|
using Fn = int(__fastcall*)(void*, void*, void*);
|
|
return reinterpret_cast<Fn>((*static_cast<void***>(control))[kVfHandleEvent])(control, nullptr, event);
|
|
}
|
|
|
|
inline int ControlRender(void* control, void* device) {
|
|
using Fn = int(__fastcall*)(void*, void*, void*);
|
|
return reinterpret_cast<Fn>((*static_cast<void***>(control))[kVfRender])(control, nullptr, device);
|
|
}
|
|
|
|
inline bool Inside(const UiRect& rect, int x, int y) {
|
|
return x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h;
|
|
}
|
|
|
|
} // namespace game
|