Options: tabs Allgemein / Beute / Tastenbelegung with key bindings
- Options window in three tabs; the auto-pickup settings move to Beute; sliders move with their page; black sides like the save game screen. - Tastenbelegung: every binding settable (click, press key; Esc cancels, Backspace unbinds), "Alle zurücksetzen" as a game button, saved on OK. - Potions bindable (default unbound), named by the game's potion texts; the game's own potion hotkeys are off; bar tooltips show the bound key. - Map (M), overview map (Tab, held) and labels (Alt, held) bindable; the game's own M / Tab / Alt handling is switched off. - Save game key calls the game's save window code instead of a posted key. - Teleportationstrank: fixed text replacement from startup (also the Ausdauertrank items, potion kind 5 as well). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
72525cd5ba
commit
838b1aa491
27 files changed
+1423
-184
No files matched your search
@@ -1,6 +1,7 @@
|
||||
#include "game/engine/items.h"
|
||||
|
||||
#include "game/engine/stream.h"
|
||||
#include "game/ui/texts.h"
|
||||
|
||||
namespace game {
|
||||
namespace {
|
||||
@@ -48,4 +49,25 @@ uint32_t CloneItem(uint32_t id) {
|
||||
return item ? CreateItem(ItemType(item), SerializeItem(item), item[kItemOwner]) : 0;
|
||||
}
|
||||
|
||||
std::wstring PotionKindName(int kind) {
|
||||
// Shown as "\dFFFFFF80Heiltrank \wFFFFFFFF[SPC]" (the resource's
|
||||
// <color=..> tags become a backslash, a letter and 8 hex digits): without
|
||||
// the colour codes and the key.
|
||||
constexpr size_t kColourCodeLength = 10;
|
||||
const std::wstring text = ShownText(PotionTooltipText(kind));
|
||||
std::wstring name;
|
||||
for (size_t i = 0; i < text.size(); ++i) {
|
||||
if (text[i] == L'\\') {
|
||||
i += kColourCodeLength - 1;
|
||||
} else if (text[i] == L'[') {
|
||||
break;
|
||||
} else {
|
||||
name += text[i];
|
||||
}
|
||||
}
|
||||
const size_t first = name.find_first_not_of(L' ');
|
||||
const size_t last = name.find_last_not_of(L' ');
|
||||
return first == std::wstring::npos ? std::wstring() : name.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
@@ -128,3 +128,23 @@ uint32_t CreateItem(uint32_t type, const std::vector<uint8_t>& data, uint32_t ow
|
||||
uint32_t CloneItem(uint32_t id);
|
||||
|
||||
} // namespace game
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace game {
|
||||
|
||||
// 0x43fa90 (ret 4; ecx unused, it asks the text manager): the text id of the
|
||||
// bottom bar's tooltip for potion kind `kind` (1..kPotionKinds), by text key
|
||||
// (0x94a8d0 ...), e.g. "<color=FFFFFF80>Heiltrank <colorw=FFFFFFFF>[SPC]";
|
||||
// 0 for other kinds. Kinds: Heiltrank, Sog der Hölle, Trank des Mentors,
|
||||
// Antidot der Viper, Trank der Konzentration (Teleportationstrank here).
|
||||
inline uint32_t PotionTooltipText(int kind) {
|
||||
using Fn = uint32_t(__fastcall*)(void*, void*, int);
|
||||
return reinterpret_cast<Fn>(0x0043fa90)(*reinterpret_cast<void**>(kItemTypeTable), nullptr, kind);
|
||||
}
|
||||
|
||||
// The name of potion kind `kind` as the bar's tooltip shows it (with our text
|
||||
// changes), without its colour codes and key.
|
||||
std::wstring PotionKindName(int kind);
|
||||
|
||||
} // namespace game
|
||||
@@ -73,11 +73,13 @@ inline void* GetPlayerHero() {
|
||||
}
|
||||
|
||||
// cKernel::PostEvent (0x808930) on Kernel_Instance() (0x808490): the kernel
|
||||
// copies the event and dispatches it (key events right away).
|
||||
inline void PostEvent(void* event) {
|
||||
// copies the event and dispatches it (key events right away). `a`: 1 as the
|
||||
// window procedure posts key events, 0 as the UI manager's S hotkey posts its
|
||||
// engine event.
|
||||
inline void PostEvent(void* event, int a = 1) {
|
||||
using InstanceFn = void*(__cdecl*)();
|
||||
using PostFn = void(__fastcall*)(void* kernel, void* edx, void* event, int a, int b);
|
||||
reinterpret_cast<PostFn>(0x00808930)(reinterpret_cast<InstanceFn>(0x00808490)(), nullptr, event, 1, 0);
|
||||
reinterpret_cast<PostFn>(0x00808930)(reinterpret_cast<InstanceFn>(0x00808490)(), nullptr, event, a, 0);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
// Untextured quads on the UI's Direct3D 7 device (the argument of the UI's
|
||||
// render methods, vf14).
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace game {
|
||||
|
||||
constexpr int kDeviceDrawPrimitive = 25; // IDirect3DDevice7 vtable slots
|
||||
constexpr int kDeviceSetTexture = 35;
|
||||
constexpr DWORD kTriangleStrip = 5;
|
||||
constexpr DWORD kFvfScreenColourTexture = 0x1c4; // XYZRHW | DIFFUSE | SPECULAR | TEX1
|
||||
|
||||
struct ScreenVertex {
|
||||
float x, y, z, rhw;
|
||||
uint32_t diffuse, specular;
|
||||
float u, v;
|
||||
};
|
||||
|
||||
// Fills (x0, y0)-(x1, y1) with the ARGB colour `color`.
|
||||
inline void FillRect(void* device, float x0, float y0, float x1, float y1, uint32_t color) {
|
||||
void** vtable = *static_cast<void***>(device);
|
||||
using SetTextureFn = HRESULT(__stdcall*)(void*, DWORD, void*);
|
||||
using DrawPrimitiveFn = HRESULT(__stdcall*)(void*, DWORD, DWORD, void*, DWORD, DWORD);
|
||||
reinterpret_cast<SetTextureFn>(vtable[kDeviceSetTexture])(device, 0, nullptr);
|
||||
ScreenVertex quad[4] = {{x0, y0, 0, 1, color, 0, 0, 0},
|
||||
{x1, y0, 0, 1, color, 0, 1, 0},
|
||||
{x0, y1, 0, 1, color, 0, 0, 1},
|
||||
{x1, y1, 0, 1, color, 0, 1, 1}};
|
||||
reinterpret_cast<DrawPrimitiveFn>(vtable[kDeviceDrawPrimitive])(device, kTriangleStrip, kFvfScreenColourTexture,
|
||||
quad, 4, 0);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
@@ -57,6 +57,13 @@ inline uint8_t* TabControl(void* sheet) {
|
||||
return *reinterpret_cast<uint8_t**>(static_cast<uint8_t*>(sheet) + kTabSheetControl);
|
||||
}
|
||||
|
||||
constexpr size_t kTabControlActive = 0x84; // u16
|
||||
|
||||
inline uint16_t ActiveTab(void* sheet) {
|
||||
const uint8_t* control = TabControl(sheet);
|
||||
return control ? *reinterpret_cast<const uint16_t*>(control + kTabControlActive) : 0;
|
||||
}
|
||||
|
||||
// The tab entries of a cUI_TabControl.
|
||||
inline uint8_t* TabEntries(uint8_t* control) {
|
||||
return *reinterpret_cast<uint8_t**>(control + kTabControlTabs);
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// First, so its DrawText macro renames game::DrawText the same way everywhere.
|
||||
#include <windows.h>
|
||||
|
||||
namespace game {
|
||||
|
||||
// Screen rect as the UI uses it (cUI_Control2::GetScreenRect 0x731c70,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <cwchar>
|
||||
#include <cwctype>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -57,6 +58,15 @@ std::vector<std::pair<std::wstring, std::wstring>>& Replacements() {
|
||||
// By id; nodes stay put, so the strings handed out stay valid.
|
||||
std::unordered_map<uint32_t, Shown> g_shown;
|
||||
|
||||
// SetTextOverride: by id. A replaced text is kept (the game may still hold
|
||||
// the string it got); overrides change rarely (key bindings).
|
||||
struct Override {
|
||||
std::wstring text;
|
||||
GameString string;
|
||||
};
|
||||
std::unordered_map<uint32_t, std::unique_ptr<Override>> g_overrides;
|
||||
std::vector<std::unique_ptr<Override>> g_retiredOverrides;
|
||||
|
||||
std::wstring Capitalize(std::wstring text) {
|
||||
bool wordStart = true;
|
||||
for (wchar_t& c : text) {
|
||||
@@ -126,6 +136,26 @@ std::wstring GameText(uint32_t id) {
|
||||
: std::wstring();
|
||||
}
|
||||
|
||||
std::wstring ShownText(uint32_t id) {
|
||||
std::lock_guard guard(g_lock);
|
||||
return Transform(id, GameText(id));
|
||||
}
|
||||
|
||||
void SetTextOverride(uint32_t id, const std::wstring& text) {
|
||||
std::lock_guard guard(g_lock);
|
||||
std::unique_ptr<Override>& entry = g_overrides[id];
|
||||
if (entry && entry->text == text) {
|
||||
return;
|
||||
}
|
||||
if (entry) {
|
||||
g_retiredOverrides.push_back(std::move(entry));
|
||||
}
|
||||
entry = std::make_unique<Override>();
|
||||
entry->text = text;
|
||||
const wchar_t* begin = entry->text.c_str();
|
||||
entry->string = {begin, begin + entry->text.size(), begin + entry->text.size()};
|
||||
}
|
||||
|
||||
std::wstring GameTextByKey(const char* key) {
|
||||
// The key's hash (0x80e010, cdecl), looked up "by key" as GetTextByKey does.
|
||||
using HashFn = uint32_t(__cdecl*)(const char*);
|
||||
@@ -159,6 +189,9 @@ const void* __fastcall TextManager_GetText(void* self, void* edx, uint32_t id) {
|
||||
}
|
||||
const void* original = TextManager_GetText_original(self, edx, id);
|
||||
std::lock_guard guard(g_lock);
|
||||
if (const auto found = g_overrides.find(id); found != g_overrides.end()) {
|
||||
return &found->second->string;
|
||||
}
|
||||
if (!original || (Replacements().empty() && !CapitalizedIds().count(id))) {
|
||||
return original;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,13 @@ void ReplaceInTexts(const std::wstring& from, const std::wstring& to);
|
||||
// The game's text `id` without our changes (empty if there is none).
|
||||
std::wstring GameText(uint32_t id);
|
||||
|
||||
// Text `id` as the game shows it, with our changes (without overrides).
|
||||
std::wstring ShownText(uint32_t id);
|
||||
|
||||
// Shows `text` for the game's text `id` from now on (e.g. a tooltip naming a
|
||||
// key binding); setting the same text again costs nothing.
|
||||
void SetTextOverride(uint32_t id, const std::wstring& text);
|
||||
|
||||
// The game's text under key `key` (TextManager::GetTextByKey 0x672fd0), e.g.
|
||||
// "UI_ATTRIB_STRENGTH"; empty if there is none.
|
||||
std::wstring GameTextByKey(const char* key);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
// Opening and closing the UI manager's windows by name, as the game's own
|
||||
// hotkeys and buttons do.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace game {
|
||||
|
||||
constexpr uint32_t kUiManager = 0x017ecb3c; // cUI_Manager*
|
||||
|
||||
inline uint32_t* UiManager() {
|
||||
return *reinterpret_cast<uint32_t**>(kUiManager);
|
||||
}
|
||||
|
||||
// cEventUI2 window commands (code at +4, subtype 5 at +8, names at +0x28): the
|
||||
// UI manager's event handler (vf00, 0x757120) routes them by window name.
|
||||
// Measured: HandleEsc hides with 0x12 (docs/escape.md); the S hotkey
|
||||
// (0x757775) shows the save game window with 0x11. (Sent to the window's own
|
||||
// handler a command was not understood the same way: the trader changed its
|
||||
// stock instead of closing.)
|
||||
enum class WindowCommand : uint32_t { Show = 0x11, Hide = 0x12 };
|
||||
|
||||
inline void SendWindowCommand(uint32_t* manager, const char* name, WindowCommand command) {
|
||||
constexpr uint32_t kEventUiVtable = 0x0088edd0;
|
||||
constexpr uint32_t kWindowSubtype = 5;
|
||||
constexpr size_t kEventUiSize = 0x80;
|
||||
// Names, written by 0x5904e0 (sender at +0, window at +0x20 of this).
|
||||
constexpr size_t kEventUiNames = 0x28;
|
||||
constexpr size_t kEventUiText = 0x68; // a string, 0x423920 / 0x416660
|
||||
alignas(4) uint8_t event[kEventUiSize] = {};
|
||||
*reinterpret_cast<uint32_t*>(event) = kEventUiVtable;
|
||||
*reinterpret_cast<uint32_t*>(event + 4) = static_cast<uint32_t>(command);
|
||||
*reinterpret_cast<uint32_t*>(event + 8) = kWindowSubtype;
|
||||
using SetNamesFn = void(__fastcall*)(void*, void*, const char* window, const char* sender);
|
||||
reinterpret_cast<SetNamesFn>(0x005904e0)(event + kEventUiNames, nullptr, name, "");
|
||||
using StringFn = void(__fastcall*)(void*, void*);
|
||||
reinterpret_cast<StringFn>(0x00423920)(event + kEventUiText, nullptr); // construct
|
||||
using HandleFn = int(__fastcall*)(void*, void*, void*);
|
||||
reinterpret_cast<HandleFn>((*reinterpret_cast<void***>(manager))[0])(manager, nullptr, event);
|
||||
reinterpret_cast<StringFn>(0x00416660)(event + kEventUiText, nullptr); // destroy
|
||||
}
|
||||
|
||||
// cUI_Manager 0x75a690 (thiscall (1), ret 4): false while a window that
|
||||
// blocks others is open (the S hotkey asks it first).
|
||||
inline bool UiCanOpenWindow(uint32_t* manager) {
|
||||
using Fn = bool(__fastcall*)(void*, void*, int);
|
||||
return reinterpret_cast<Fn>(0x0075a690)(manager, nullptr, 1);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
Reference in new issue
Block a user