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:
1 parent
f721d2a486
commit
1fd89524d3
41 files changed
+2347
-214
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user