HUD: centre all UI windows, shift mouse events (not UI commands)
Generic cUI_Window2 hooks centre dialogs, inventory, maps, traders and the ESC menu. Event shifting now targets the cursor position of mouse events (move/down/up, x at +8) instead of category 5 UI events, whose +0x48 is not a point for commands such as opening the ESC menu. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ed3163d312
commit
a640f2fb17
3 files changed
+139
-46
No files matched your search
+125
-46
@@ -10,8 +10,9 @@
|
|||||||
// - while its render method runs, every screen-space vertex (XYZRHW) passed
|
// - while its render method runs, every screen-space vertex (XYZRHW) passed
|
||||||
// to DrawPrimitive / DrawIndexedPrimitive is shifted by dx (3D items such
|
// to DrawPrimitive / DrawIndexedPrimitive is shifted by dx (3D items such
|
||||||
// as belt contents already line up: their projection is screen-centred);
|
// as belt contents already line up: their projection is screen-centred);
|
||||||
// - while its event handler runs, the point of UI events (category 5,
|
// - while its event handler runs, the cursor position of mouse events
|
||||||
// point at +0x48) is shifted by -dx. Children are handled inside both.
|
// (move / button down / up) is shifted by -dx. Children are handled
|
||||||
|
// inside both.
|
||||||
// The world never sees these offsets: they are only active inside the hooks.
|
// The world never sees these offsets: they are only active inside the hooks.
|
||||||
//
|
//
|
||||||
// sacred_re.ini:
|
// sacred_re.ini:
|
||||||
@@ -35,49 +36,68 @@ namespace {
|
|||||||
|
|
||||||
constexpr int kLayoutWidth = 1024;
|
constexpr int kLayoutWidth = 1024;
|
||||||
|
|
||||||
// Horizontal offset applied to the current thread's UI drawing/input.
|
// Horizontal offset applied to the current thread's UI drawing.
|
||||||
thread_local int g_offsetX = 0;
|
thread_local int g_offsetX = 0;
|
||||||
|
// Whether the UI event currently being handled has already been shifted.
|
||||||
|
thread_local bool g_eventShifted = false;
|
||||||
|
|
||||||
enum class Anchor { Center, Right };
|
enum class Anchor { Center, Right };
|
||||||
|
|
||||||
int OffsetFor(Anchor anchor) {
|
int OffsetFor(Anchor anchor) {
|
||||||
|
static const bool enabled = re::ConfigInt("hud", "Enabled", 1) != 0;
|
||||||
const int width = mods::widescreen::RenderWidth();
|
const int width = mods::widescreen::RenderWidth();
|
||||||
if (width <= kLayoutWidth || mods::widescreen::IsClassicFrame() || !re::ConfigInt("hud", "Enabled", 1)) {
|
if (!enabled || width <= kLayoutWidth || mods::widescreen::IsClassicFrame() ||
|
||||||
|
mods::widescreen::MenuIsShowing()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth;
|
return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RAII: shift drawing by dx for the scope.
|
// RAII: shift drawing by dx for the scope. Windows nest (children, shared base
|
||||||
|
// methods), so only the outermost window decides.
|
||||||
class DrawOffset {
|
class DrawOffset {
|
||||||
public:
|
public:
|
||||||
explicit DrawOffset(int dx) : previous_(g_offsetX) { g_offsetX = dx; }
|
explicit DrawOffset(int dx) : previous_(g_offsetX) {
|
||||||
|
if (g_offsetX == 0) {
|
||||||
|
g_offsetX = dx;
|
||||||
|
}
|
||||||
|
}
|
||||||
~DrawOffset() { g_offsetX = previous_; }
|
~DrawOffset() { g_offsetX = previous_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int previous_;
|
int previous_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// UI events: vf04 returns the category, 5 = UI event with its point at +0x48.
|
// Mouse events (cEvent_mouseMove / _mouseButton_down / _mouseButton_up): vf04
|
||||||
bool IsUiPointEvent(void* event) {
|
// returns 1 / 2 / 3, the cursor position in render pixels is at +8 / +0xc.
|
||||||
|
// Returns the x field, or nullptr for other events (keys, UI commands, ...).
|
||||||
|
int32_t* MouseEventX(void* event) {
|
||||||
using CategoryFn = int(__fastcall*)(void* self, void* edx);
|
using CategoryFn = int(__fastcall*)(void* self, void* edx);
|
||||||
auto category = reinterpret_cast<CategoryFn>((*static_cast<void***>(event))[1]);
|
auto category = reinterpret_cast<CategoryFn>((*static_cast<void***>(event))[1]);
|
||||||
return category(event, nullptr) == 5;
|
const int value = category(event, nullptr);
|
||||||
|
if (value < 1 || value > 3) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return reinterpret_cast<int32_t*>(static_cast<uint8_t*>(event) + 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// RAII: shift the event point by -dx for the scope.
|
// RAII: shift the mouse position by -dx for the scope (outermost window only).
|
||||||
class EventOffset {
|
class EventOffset {
|
||||||
public:
|
public:
|
||||||
EventOffset(void* event, int dx) {
|
EventOffset(void* event, int dx) {
|
||||||
if (dx != 0 && IsUiPointEvent(event)) {
|
if (dx != 0 && !g_eventShifted) {
|
||||||
x_ = reinterpret_cast<int32_t*>(static_cast<uint8_t*>(event) + 0x48);
|
x_ = MouseEventX(event);
|
||||||
dx_ = dx;
|
if (x_) {
|
||||||
*x_ -= dx_;
|
dx_ = dx;
|
||||||
|
*x_ -= dx_;
|
||||||
|
g_eventShifted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
~EventOffset() {
|
~EventOffset() {
|
||||||
if (x_) {
|
if (x_) {
|
||||||
*x_ += dx_;
|
*x_ += dx_;
|
||||||
|
g_eventShifted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,43 +191,102 @@ void OnDevice(void* device) {
|
|||||||
} // namespace mods::hud
|
} // namespace mods::hud
|
||||||
|
|
||||||
// --- HUD windows ---------------------------------------------------------------
|
// --- HUD windows ---------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Each hook wraps one window method; nested windows and shared base methods are
|
||||||
|
// handled by "outermost wins" in DrawOffset/EventOffset. Addresses are the
|
||||||
|
// vf10 (event, ret 4), vf14 (render, ret 4) and vf44 (render, ret 0Ch) entries
|
||||||
|
// of the cUI_* vtables (see tools/rtti.mjs).
|
||||||
|
|
||||||
|
#define HUD_EVENT(address, name, anchor) \
|
||||||
|
int __fastcall name(void* self, void* edx, void* event); \
|
||||||
|
RE_HOOK(address, name); \
|
||||||
|
int __fastcall name(void* self, void* edx, void* event) { \
|
||||||
|
mods::hud::EventOffset offset(event, mods::hud::OffsetFor(anchor)); \
|
||||||
|
return name##_original(self, edx, event); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define HUD_RENDER(address, name, anchor) \
|
||||||
|
int __fastcall name(void* self, void* edx, void* device); \
|
||||||
|
RE_HOOK(address, name); \
|
||||||
|
int __fastcall name(void* self, void* edx, void* device) { \
|
||||||
|
mods::hud::DrawOffset offset(mods::hud::OffsetFor(anchor)); \
|
||||||
|
return name##_original(self, edx, device); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define HUD_RENDER3(address, name, anchor) \
|
||||||
|
int __fastcall name(void* self, void* edx, void* device, int a, int b); \
|
||||||
|
RE_HOOK(address, name); \
|
||||||
|
int __fastcall name(void* self, void* edx, void* device, int a, int b) { \
|
||||||
|
mods::hud::DrawOffset offset(mods::hud::OffsetFor(anchor)); \
|
||||||
|
return name##_original(self, edx, device, a, b); \
|
||||||
|
}
|
||||||
|
|
||||||
namespace game {
|
namespace game {
|
||||||
|
|
||||||
using mods::hud::Anchor;
|
using mods::hud::Anchor;
|
||||||
|
|
||||||
// cUI_Taskbar2 (vtable 0x00894ac0): vf10 events, vf14 render.
|
// Bottom bar: orbs, belt, skills.
|
||||||
int __fastcall UiTaskbar_HandleEvent(void* self, void* edx, void* event);
|
HUD_EVENT(0x006e1970, UiTaskbar_HandleEvent, Anchor::Center)
|
||||||
void __fastcall UiTaskbar_Render(void* self, void* edx, void* device);
|
HUD_RENDER(0x006e3ac0, UiTaskbar_Render, Anchor::Center)
|
||||||
// Portrait/health panel (cUI_Mercenary).
|
// Portrait and health at the top: cUI_Mercenary (party panel), not
|
||||||
int __fastcall UiPortrait_HandleEvent(void* self, void* edx, void* event);
|
// cUI_NetPortrait.
|
||||||
void __fastcall UiPortrait_Render(void* self, void* edx, void* device);
|
HUD_EVENT(0x006d69f0, UiPortrait_HandleEvent, Anchor::Right)
|
||||||
|
HUD_RENDER(0x006d6e70, UiPortrait_Render, Anchor::Right)
|
||||||
|
|
||||||
RE_HOOK(0x006e1970, UiTaskbar_HandleEvent);
|
// Everything else keeps its 1024x768 layout, centred: dialogs, inventory,
|
||||||
RE_HOOK(0x006e3ac0, UiTaskbar_Render);
|
// character, maps, traders, ESC menu, ...
|
||||||
// The portrait with health at the top is drawn by cUI_Mercenary (party panel,
|
HUD_EVENT(0x00726880, UiWindow_HandleEvent, Anchor::Center) // cUI_Window2 (shared)
|
||||||
// vtable 0x008948cc), not cUI_NetPortrait.
|
HUD_RENDER(0x00727040, UiWindow_Render, Anchor::Center) // cUI_Window2 (shared)
|
||||||
RE_HOOK(0x006d69f0, UiPortrait_HandleEvent);
|
HUD_EVENT(0x006a4140, UiBlacksmith_HandleEvent, Anchor::Center)
|
||||||
RE_HOOK(0x006d6e70, UiPortrait_Render);
|
HUD_RENDER3(0x006a3160, UiBlacksmith_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006a5dd0, UiStatisticsChar_HandleEvent, Anchor::Center)
|
||||||
int __fastcall UiTaskbar_HandleEvent(void* self, void* edx, void* event) {
|
HUD_RENDER(0x006a9be0, UiStatisticsChar_Render, Anchor::Center)
|
||||||
mods::hud::EventOffset offset(event, mods::hud::OffsetFor(Anchor::Center));
|
HUD_EVENT(0x006acc70, UiChest_HandleEvent, Anchor::Center)
|
||||||
return UiTaskbar_HandleEvent_original(self, edx, event);
|
HUD_RENDER(0x006ac8b0, UiChest_Render, Anchor::Center)
|
||||||
}
|
HUD_EVENT(0x006adc70, UiConsole_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006adb40, UiConsole_Render, Anchor::Center)
|
||||||
void __fastcall UiTaskbar_Render(void* self, void* edx, void* device) {
|
HUD_EVENT(0x006aea20, UiDiary_HandleEvent, Anchor::Center)
|
||||||
mods::hud::DrawOffset offset(mods::hud::OffsetFor(Anchor::Center));
|
HUD_RENDER3(0x006aeba0, UiDiary_Render, Anchor::Center)
|
||||||
UiTaskbar_Render_original(self, edx, device);
|
HUD_EVENT(0x006b3620, UiBook_HandleEvent, Anchor::Center)
|
||||||
}
|
HUD_EVENT(0x006b6330, UiEquipment_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006b73a0, UiEquipment_Render, Anchor::Center)
|
||||||
int __fastcall UiPortrait_HandleEvent(void* self, void* edx, void* event) {
|
HUD_EVENT(0x006bb720, UiEscMenu_HandleEvent, Anchor::Center)
|
||||||
mods::hud::EventOffset offset(event, mods::hud::OffsetFor(Anchor::Right));
|
HUD_RENDER(0x006bb030, UiEscMenu_Render, Anchor::Center)
|
||||||
return UiPortrait_HandleEvent_original(self, edx, event);
|
HUD_EVENT(0x006be0d0, UiHorse_HandleEvent, Anchor::Center)
|
||||||
}
|
HUD_RENDER3(0x006bd180, UiHorse_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006c5f40, UiInventory_HandleEvent, Anchor::Center)
|
||||||
void __fastcall UiPortrait_Render(void* self, void* edx, void* device) {
|
HUD_RENDER(0x006c6140, UiInventory_Render, Anchor::Center)
|
||||||
mods::hud::DrawOffset offset(mods::hud::OffsetFor(Anchor::Right));
|
HUD_EVENT(0x006c70e0, UiOverviewMap_HandleEvent, Anchor::Center)
|
||||||
UiPortrait_Render_original(self, edx, device);
|
HUD_RENDER(0x006c69d0, UiOverviewMap_Render, Anchor::Center)
|
||||||
}
|
HUD_EVENT(0x006c7ec0, UiMegamap_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006c9ce0, UiMegamap_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006d1080, UiMaster_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006d0f00, UiMaster_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006d2450, UiMasterCombo_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006d2b70, UiMasterCombo_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006d54e0, UiMasterLotto_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006d4720, UiMasterLotto_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006d5e20, UiMasterPortal_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006d5e00, UiMasterPortal_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006dab10, UiMerchandise_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006da7f0, UiMerchandise_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006dbcd0, UiMerchant_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER3(0x006db840, UiMerchant_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006dd720, UiPurchase_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER3(0x006dcc80, UiPurchase_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006e8860, UiCube_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006e8870, UiCube_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006e9ff0, UiTrade_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006ea750, UiTrade_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x006f8960, UiNetworkInfo_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x006f73f0, UiNetworkInfo_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x0070e860, UiCharacter_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER3(0x0070f160, UiCharacter_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x00716b30, UiOptions_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER3(0x00716640, UiOptions_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x0071d130, UiSavegame_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x0071dd80, UiSavegame_Render, Anchor::Center)
|
||||||
|
HUD_EVENT(0x00720c10, UiBusyDlg_HandleEvent, Anchor::Center)
|
||||||
|
HUD_RENDER(0x007213a0, UiBusyDlg_Render, Anchor::Center)
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
@@ -65,6 +65,9 @@ int g_renderHeight = 0;
|
|||||||
// Frames from the main menu thread and the loading screen are laid out for
|
// Frames from the main menu thread and the loading screen are laid out for
|
||||||
// 1024x768 ("classic"); everything else is the widened in-game view.
|
// 1024x768 ("classic"); everything else is the widened in-game view.
|
||||||
std::atomic<DWORD> g_menuThread = 0;
|
std::atomic<DWORD> g_menuThread = 0;
|
||||||
|
// Whether the most recent scene (on any thread) was a classic one: the main
|
||||||
|
// menu is showing. UI input is processed outside the menu thread.
|
||||||
|
std::atomic<bool> g_lastSceneClassic = false;
|
||||||
thread_local int g_loadingScreenDepth = 0;
|
thread_local int g_loadingScreenDepth = 0;
|
||||||
|
|
||||||
// D3DVIEWPORT7 (d3d.h is not in current Windows SDKs).
|
// D3DVIEWPORT7 (d3d.h is not in current Windows SDKs).
|
||||||
@@ -290,6 +293,10 @@ int RenderWidth() {
|
|||||||
return g_renderWidth;
|
return g_renderWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MenuIsShowing() {
|
||||||
|
return g_lastSceneClassic;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsClassicFrame() {
|
bool IsClassicFrame() {
|
||||||
if (g_renderWidth == 0) {
|
if (g_renderWidth == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -324,6 +331,9 @@ void __fastcall DxDriver7_LoadingScreen(DxDriver7* self, void* edx, uint32_t pro
|
|||||||
// world the full render size.
|
// world the full render size.
|
||||||
int __fastcall DxDriver7_BeginScene(DxDriver7* self, void* edx) {
|
int __fastcall DxDriver7_BeginScene(DxDriver7* self, void* edx) {
|
||||||
mods::hud::OnDevice(self->device);
|
mods::hud::OnDevice(self->device);
|
||||||
|
if (self->sceneDepth == 0) {
|
||||||
|
g_lastSceneClassic = IsClassicFrame();
|
||||||
|
}
|
||||||
if (g_renderWidth != 0 && self->sceneDepth == 0 && self->device) {
|
if (g_renderWidth != 0 && self->sceneDepth == 0 && self->device) {
|
||||||
const bool classic = IsClassicFrame();
|
const bool classic = IsClassicFrame();
|
||||||
Viewport7 viewport = {0, 0, classic ? 1024u : static_cast<DWORD>(g_renderWidth),
|
Viewport7 viewport = {0, 0, classic ? 1024u : static_cast<DWORD>(g_renderWidth),
|
||||||
|
|||||||
@@ -18,4 +18,8 @@ bool IsClassicFrame();
|
|||||||
// Applied render width (0 while the original 1024x768 is in use).
|
// Applied render width (0 while the original 1024x768 is in use).
|
||||||
int RenderWidth();
|
int RenderWidth();
|
||||||
|
|
||||||
|
// True while the main menu / loading screen (classic layout) is on screen,
|
||||||
|
// regardless of the calling thread.
|
||||||
|
bool MenuIsShowing();
|
||||||
|
|
||||||
} // namespace mods::widescreen
|
} // namespace mods::widescreen
|
||||||
Reference in new issue
Block a user