Skills, auto-attack, aura and chain lightning rework, Esc, cache fix

- Skill bar: no art selection; slot 0 is the right-click art, keys 0-6 cast
  slots directly; cooldown labels in exact seconds; slot 0 set apart.
- Arts regenerate on their own (casting no longer refills the group).
- Auto-attack after targeting or casting at a hostile creature, at the speed
  of a held button.
- Auras can be recast while active (renew, no second visual).
- Blicke wie Klingen hits every enemy in range, physical, with life leech.
- Blitzschlag steers the game's own chain: 3 + level/5 hits, 1 + level/10
  chains, re-hits a lone enemy.
- Weapon sets: click selects, drag only from the selected set; the leftmost
  set is clickable (signed hit tests).
- Esc closes open windows before opening the game menu.
- HUD: standalone buttons and texts are centred.
- Record cache sized for widened views (town lag while walking).
- Shared game code: orders, fx events, text drawing, cooldowns, effects.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-16 00:11:38 +02:00
1 parent f721d2a486
commit 1fd89524d3
41 files changed
+2347 -214

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
// Esc closes open windows before it opens the game menu.
//
// cUI_Manager::HandleEsc (0x756ba0) closes the ESC menu, the large map, the
// quest book or the inventory, hides two more windows, and otherwise opens
// the ESC menu. With a trader (and others) open it only closed the inventory,
// which the trader window opened again. Now every open window of the table
// below is sent the command the game itself uses to close the quest book: a
// cEventUI2 "hide" (code 0x12, subtype 5, the window's name) to the UI
// manager's event handler. The game's handling stays for the ESC menu itself and when
// none of these windows is open. See docs/escape.md.
#include <cstdint>
#include <cstring>
#include "re/hook.h"
#include "re/log.h"
namespace mods::escape {
namespace {
// cUI_Manager: window pointers by slot (dwords).
struct ClosableWindow {
int slot;
const char* name;
};
constexpr ClosableWindow kClosable[] = {
{0x21, "UI_WND_INVENTORY"}, {0x22, "UI_WND_EQUIPMENT"}, {0x23, "UI_WND_BLACKSMITH"},
{0x24, "UI_WND_MERCHANT"}, {0x25, "UI_WND_MEGAMAP"}, {0x28, "UI_WND_STATS_CHAR"},
{0x2a, "UI_WND_QUESTBOOK"}, {0x2b, "UI_WND_SAVEGAME"}, {0x2d, "UI_WND_PURCHASE"},
{0x2e, "UI_WND_MASTER"}, {0x2f, "UI_WND_OPTIONS"}, {0x30, "UI_WND_CHEST"},
{0x31, "UI_WND_HORSE"}, {0x35, "UI_WND_CUBE"},
};
constexpr int kEscMenuSlot = 0x2c;
constexpr size_t kControlFlags = 0x10; // bit 0: visible
// cEventUI2 as HandleEsc builds it (event at [esp+10h] there).
constexpr uint32_t kEventUiVtable = 0x0088edd0;
constexpr uint32_t kEventUiWindowCommand = 0x12;
constexpr uint32_t kWindowCommandHide = 5;
constexpr size_t kEventUiSize = 0x80;
// Names, written by 0x5904e0 (sender at +0, window at +0x20 of this): measured
// in the UI commands the game sends when a window is closed with its button or
// hotkey (window name at +0x48).
constexpr size_t kEventUiNames = 0x28;
constexpr size_t kEventUiText = 0x68; // a string, 0x423920 / 0x416660
bool Visible(const uint8_t* window) {
return window && (window[kControlFlags] & 1) != 0;
}
// The window command goes to the UI manager's event handler (vf00,
// 0x757120), which routes it by name, as HandleEsc does for the inventory.
// (Sent to the window's own handler it was not understood the same way: the
// trader changed its stock instead of closing.)
void Hide(uint32_t* manager, const char* name) {
alignas(4) uint8_t event[kEventUiSize] = {};
*reinterpret_cast<uint32_t*>(event) = kEventUiVtable;
*reinterpret_cast<uint32_t*>(event + 4) = kEventUiWindowCommand;
*reinterpret_cast<uint32_t*>(event + 8) = kWindowCommandHide;
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
}
} // namespace
// True if Esc closed windows (the game's handling is skipped).
bool CloseWindows(uint32_t* manager) {
if (Visible(reinterpret_cast<uint8_t*>(manager[kEscMenuSlot]))) {
return false;
}
bool closed = false;
for (const ClosableWindow& w : kClosable) {
auto* window = reinterpret_cast<uint8_t*>(manager[w.slot]);
if (Visible(window)) {
Hide(manager, w.name);
closed = true;
}
}
return closed;
}
} // namespace mods::escape
namespace game {
// cUI_Manager::HandleEsc (0x756ba0): 1 if handled.
uint32_t __fastcall UiManager_HandleEsc(uint32_t* self, void* edx);
RE_HOOK(0x00756ba0, UiManager_HandleEsc);
uint32_t __fastcall UiManager_HandleEsc(uint32_t* self, void* edx) {
if (self && mods::escape::CloseWindows(self)) {
return 1;
}
return UiManager_HandleEsc_original(self, edx);
}
} // namespace game