Menu screens: rock background, centred frame, logo, 3D hero
- Spielen hero panel shows the selected hero in 3D (cUI_NetGranny, game/ui/granny.h). - All menu screens draw on the options' rock wall; the main menu's 1024 frame is drawn centred over it, mouse and cursor shifted to match. - The main menu's and options' own pictures and the main menu's 3D scene are left out (textures hidden by surface, scene call redirected). - Logo top left: FX_SACREDLOGO.TGA, drawn added. - Game textures by name (game/ui/texture.h), textured and added quads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
34a119433a
commit
db87e276bf
16 files changed
+620
-69
No files matched your search
+111
-13
@@ -21,6 +21,7 @@
|
||||
// Enabled=1
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "game/render/dx_driver7.h"
|
||||
#include "game/ui/events.h"
|
||||
#include "mods/hud.h"
|
||||
#include "mods/screen.h"
|
||||
#include "mods/widescreen.h"
|
||||
#include "re/config.h"
|
||||
#include "re/hook.h"
|
||||
@@ -268,6 +270,29 @@ private:
|
||||
CursorOffset cursor_;
|
||||
};
|
||||
|
||||
// RAII: shift drawing only (the cursor is not seen shifted), for things
|
||||
// already in the shifted layout's coordinates (the main menu's cursor).
|
||||
class DrawShift {
|
||||
public:
|
||||
explicit DrawShift(int dx) : active_(!g_drawScoped && dx != 0) {
|
||||
if (active_) {
|
||||
g_drawScoped = true;
|
||||
g_offsetX = dx;
|
||||
}
|
||||
}
|
||||
~DrawShift() {
|
||||
if (active_) {
|
||||
g_drawScoped = false;
|
||||
g_offsetX = 0;
|
||||
}
|
||||
}
|
||||
DrawShift(const DrawShift&) = delete;
|
||||
DrawShift& operator=(const DrawShift&) = delete;
|
||||
|
||||
private:
|
||||
bool active_;
|
||||
};
|
||||
|
||||
// Stores the offsets of a mouse event's x fields (game/ui/events.h); returns
|
||||
// their count (0: no mouse event).
|
||||
int MouseEventXOffsets(void* event, int (&offsets)[2]) {
|
||||
@@ -383,13 +408,47 @@ using DrawIndexedPrimitiveFn = HRESULT(__stdcall*)(void* device, DWORD type, DWO
|
||||
DrawPrimitiveFn g_drawPrimitive = nullptr;
|
||||
DrawIndexedPrimitiveFn g_drawIndexedPrimitive = nullptr;
|
||||
|
||||
// Textures not drawn (SetHiddenTextures): the surface bound to stage 0 is
|
||||
// tracked per thread (each thread draws with its own device calls).
|
||||
constexpr int kHiddenTextureMax = 64;
|
||||
std::atomic<void*> g_hiddenTextures[kHiddenTextureMax] = {};
|
||||
thread_local void* g_boundTexture = nullptr;
|
||||
|
||||
using SetTextureFn = HRESULT(__stdcall*)(void* device, DWORD stage, void* surface);
|
||||
SetTextureFn g_setTexture = nullptr;
|
||||
|
||||
HRESULT __stdcall SetTextureTracked(void* device, DWORD stage, void* surface) {
|
||||
if (stage == 0) {
|
||||
g_boundTexture = surface;
|
||||
}
|
||||
return g_setTexture(device, stage, surface);
|
||||
}
|
||||
|
||||
bool BoundTextureHidden() {
|
||||
if (!g_boundTexture) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& hidden : g_hiddenTextures) {
|
||||
if (hidden.load(std::memory_order_relaxed) == g_boundTexture) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT __stdcall DrawPrimitiveShifted(void* device, DWORD type, DWORD fvf, void* vertices, DWORD count,
|
||||
DWORD flags) {
|
||||
if (BoundTextureHidden()) {
|
||||
return S_OK;
|
||||
}
|
||||
return g_drawPrimitive(device, type, fvf, ShiftVertices(fvf, vertices, count), count, flags);
|
||||
}
|
||||
|
||||
HRESULT __stdcall DrawIndexedPrimitiveShifted(void* device, DWORD type, DWORD fvf, void* vertices, DWORD count,
|
||||
WORD* indices, DWORD indexCount, DWORD flags) {
|
||||
if (BoundTextureHidden()) {
|
||||
return S_OK;
|
||||
}
|
||||
return g_drawIndexedPrimitive(device, type, fvf, ShiftVertices(fvf, vertices, count), count, indices,
|
||||
indexCount, flags);
|
||||
}
|
||||
@@ -440,30 +499,25 @@ HRESULT __stdcall ClearShifted(void* device, DWORD count, Rect7* rects, DWORD fl
|
||||
|
||||
using GetViewportFn = HRESULT(__stdcall*)(void* device, Viewport7* viewport);
|
||||
constexpr int kVtableGetViewport = 15; // IDirect3DDevice7 (13 SetViewport, 14 MultiplyTransform)
|
||||
constexpr DWORD kClearTarget = 1; // D3DCLEAR_TARGET
|
||||
|
||||
// RAII for full-screen windows (character export, save games) that fill the
|
||||
// 1024 frame with a picture: the render target left and right of the frame is
|
||||
// cleared black, and the window draws with a viewport on the frame. The
|
||||
// RAII for full-screen windows (options in a game) that fill the 1024 frame
|
||||
// with a picture: the menu screens' background (mods/screen.h) goes under
|
||||
// it, and the window draws with a viewport on the frame. The
|
||||
// export screen's 3D scene (heroes, background) uses a projection for a
|
||||
// 1024x768 viewport; across the widened one it was stretched 1.8x wide, away
|
||||
// from the slot labels and hit areas (measured on a screenshot, 2026-10-18).
|
||||
// Clear clips to the viewport, so the sides are cleared before it is set.
|
||||
class FrameScope {
|
||||
public:
|
||||
FrameScope(void* window, void* device, int dx) {
|
||||
const auto* w = static_cast<const uint8_t*>(window);
|
||||
if (dx == 0 || g_drawScoped || !device || !g_setViewport || !g_clear || (w[kControlFlags] & 1) == 0) {
|
||||
if (dx == 0 || g_drawScoped || !device || !g_setViewport || (w[kControlFlags] & 1) == 0) {
|
||||
return;
|
||||
}
|
||||
auto getViewport = reinterpret_cast<GetViewportFn>((*static_cast<void***>(device))[kVtableGetViewport]);
|
||||
if (FAILED(getViewport(device, &previous_))) {
|
||||
return;
|
||||
}
|
||||
const LONG height = static_cast<LONG>(previous_.height);
|
||||
Rect7 sides[] = {{0, 0, dx, height},
|
||||
{dx + kLayoutWidth, 0, mods::widescreen::RenderWidth(), height}};
|
||||
g_clear(device, 2, sides, kClearTarget, 0, 1.0f, 0);
|
||||
mods::screen::DrawBackground(device);
|
||||
Viewport7 frame = {static_cast<DWORD>(dx), 0, kLayoutWidth, 768, previous_.minZ, previous_.maxZ};
|
||||
device_ = device;
|
||||
g_setViewport(device, &frame);
|
||||
@@ -512,6 +566,10 @@ void OnDevice(void* device) {
|
||||
g_drawIndexedPrimitive = reinterpret_cast<DrawIndexedPrimitiveFn>(vtable[26]);
|
||||
re::AttachRuntimeHook("D3D7 DrawIndexedPrimitive", reinterpret_cast<void**>(&g_drawIndexedPrimitive),
|
||||
reinterpret_cast<void*>(&DrawIndexedPrimitiveShifted));
|
||||
// SetTexture 35.
|
||||
g_setTexture = reinterpret_cast<SetTextureFn>(vtable[35]);
|
||||
re::AttachRuntimeHook("D3D7 SetTexture", reinterpret_cast<void**>(&g_setTexture),
|
||||
reinterpret_cast<void*>(&SetTextureTracked));
|
||||
// Clear 10, SetViewport 13.
|
||||
g_clear = reinterpret_cast<ClearFn>(vtable[10]);
|
||||
re::AttachRuntimeHook("D3D7 Clear", reinterpret_cast<void**>(&g_clear), reinterpret_cast<void*>(&ClearShifted));
|
||||
@@ -796,6 +854,41 @@ int HandleCentred(void* event, int (*body)(void* context), void* context) {
|
||||
return body(context);
|
||||
}
|
||||
|
||||
// Offset of the main menu's 1024 frame while it is drawn centred (set each
|
||||
// menu frame by RenderMenuFrame; 0 while a 16:9 screen draws itself).
|
||||
std::atomic<int> g_menuFrameDx = 0;
|
||||
|
||||
void SetHiddenTextures(void* const* surfaces, int count) {
|
||||
for (int i = 0; i < kHiddenTextureMax; ++i) {
|
||||
g_hiddenTextures[i].store(i < count ? surfaces[i] : nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
int MenuFrameOffsetX() {
|
||||
return mods::widescreen::MenuIsShowing() ? g_menuFrameDx.load() : 0;
|
||||
}
|
||||
|
||||
int RenderMenuFrame(void* device, bool framed, int (*body)(void* context), void* context) {
|
||||
const int width = mods::widescreen::RenderWidth();
|
||||
const int dx = framed && width > kLayoutWidth ? (width - kLayoutWidth) / 2 : 0;
|
||||
g_menuFrameDx = dx;
|
||||
Viewport7 previous;
|
||||
auto getViewport =
|
||||
device ? reinterpret_cast<GetViewportFn>((*static_cast<void***>(device))[kVtableGetViewport]) : nullptr;
|
||||
if (dx == 0 || g_drawScoped || !g_setViewport || !getViewport || FAILED(getViewport(device, &previous))) {
|
||||
return body(context);
|
||||
}
|
||||
Viewport7 frame = {static_cast<DWORD>(dx), 0, kLayoutWidth, 768, previous.minZ, previous.maxZ};
|
||||
g_setViewport(device, &frame);
|
||||
g_drawScoped = true; // the windows inside draw in the frame's coordinates
|
||||
g_offsetX = dx;
|
||||
const int result = body(context);
|
||||
g_offsetX = 0;
|
||||
g_drawScoped = false;
|
||||
g_setViewport(device, &previous);
|
||||
return result;
|
||||
}
|
||||
|
||||
int CanvasOffsetX() {
|
||||
return OffsetFor(Anchor::Canvas);
|
||||
}
|
||||
@@ -830,12 +923,17 @@ int __fastcall UiTaskbar_HitTest(void* self, void* edx, int x, int y) {
|
||||
return UiTaskbar_HitTest_original(self, edx, mods::hud::HitTestX(self, x), y);
|
||||
}
|
||||
|
||||
// The item carried by the mouse cursor (0x6555e0, drawn by the render loop
|
||||
// after the UI) is placed like UI items: relative to x = 512 of the layout.
|
||||
// Items are only picked up from centred windows.
|
||||
// The cursor and the item it carries (0x6555e0, drawn by the render loop
|
||||
// after the UI) are placed like UI items: relative to x = 512 of the layout.
|
||||
// Items are only picked up from centred windows. In the main menu the cursor
|
||||
// is in the centred frame's coordinates (MenuFrameOffsetX).
|
||||
int __fastcall MouseCursor_DrawItem(void* self, void* edx, int a, int b);
|
||||
RE_HOOK(0x006555e0, MouseCursor_DrawItem);
|
||||
int __fastcall MouseCursor_DrawItem(void* self, void* edx, int a, int b) {
|
||||
if (mods::widescreen::MenuIsShowing()) {
|
||||
mods::hud::DrawShift shift(mods::hud::MenuFrameOffsetX());
|
||||
return MouseCursor_DrawItem_original(self, edx, a, b);
|
||||
}
|
||||
mods::hud::DrawOffset offset(nullptr, mods::hud::OffsetFor(Anchor::Center));
|
||||
return MouseCursor_DrawItem_original(mods::hud::CursorFor(self), edx, a, b);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user