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
+11
-3
@@ -189,8 +189,16 @@ below; our own background). Built on the shared screen kit
|
|||||||
Its entries are only needed for the load.
|
Its entries are only needed for the load.
|
||||||
- Left: the characters, newest save first (name, class, level; "im Spiel" for
|
- Left: the characters, newest save first (name, class, level; "im Spiel" for
|
||||||
the one being played). Click selects, double click plays.
|
the one being played). Click selects, double click plays.
|
||||||
- Middle: name, class and level, the world played last with its date. (A
|
- Middle: name, class and level, the world played last with its date, and
|
||||||
render of the hero is planned.)
|
the hero in 3D with its equipment (2026-10-20): the game's cUI_NetGranny
|
||||||
|
(game/ui/granny.h), the control the export screen and the multiplayer
|
||||||
|
lobby showed heroes with. The character's hero file goes to
|
||||||
|
`_workpreview.pax`, LoadHeroFile (path, 0, info) reads its hero info
|
||||||
|
(class, equipment looks) without touching the player hero. Drawn without
|
||||||
|
its name label, with the white light set, size factor 3.8 (game 2.5); its
|
||||||
|
x is moved by (1365 - 1024) / 2, as it centres the model as in a 1024
|
||||||
|
layout. Migrated characters without the shared hero show none until
|
||||||
|
played.
|
||||||
- Right: a grid campaigns x difficulties. A cell shows the save's date,
|
- Right: a grid campaigns x difficulties. A cell shows the save's date,
|
||||||
"+ Autosave" when the autosave is newer, "Story abgeschlossen", "Neu" for a
|
"+ Autosave" when the autosave is newer, "Story abgeschlossen", "Neu" for a
|
||||||
difficulty that is open but has no world, "–" when closed. Open: Bronze and
|
difficulty that is open but has no world, "–" when closed. Open: Bronze and
|
||||||
@@ -306,5 +314,5 @@ kept, the newest file overall is the shared hero.
|
|||||||
## Open
|
## Open
|
||||||
|
|
||||||
- Quickload (row 0) does not know characters (not needed, user 2026-10-20).
|
- Quickload (row 0) does not know characters (not needed, user 2026-10-20).
|
||||||
- Spielen screen: a render of the hero; scrolling the list beyond 9 characters.
|
- Spielen screen: scrolling the list beyond 9 characters.
|
||||||
- Multiplayer (see `todo.md`).
|
- Multiplayer (see `todo.md`).
|
||||||
+43
-5
@@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
Request (2026-10-20): the game's menu screens are laid out for 1024x768; we
|
Request (2026-10-20): the game's menu screens are laid out for 1024x768; we
|
||||||
design our own UIs for them. There is no 16:9 artwork for the game, so the
|
design our own UIs for them. There is no 16:9 artwork for the game, so the
|
||||||
background is our own (option 3 of the ones discussed: dark, no picture), and
|
background is our own, and the approach is shared by all menu screens. The
|
||||||
the approach is shared by all menu screens. Target: 16:9, 1920x1080, which is
|
background (user, 2026-10-20: "textured dark", the flat gradient looked
|
||||||
|
black): the options window's rock wall, `GUI_OPTION_BACKGROUND<row><column>.TGA`
|
||||||
|
(4 x 3 tiles of 256), scaled to cover the render target, tinted down,
|
||||||
|
shaded towards the bottom and the sides. Game textures by name:
|
||||||
|
`game/ui/texture.h`, drawn with `game::DrawTextured` (game/ui/draw.h). Target: 16:9, 1920x1080, which is
|
||||||
1365x768 in UI units; wider screens only get more background at the sides
|
1365x768 in UI units; wider screens only get more background at the sides
|
||||||
(not designed for ultrawide).
|
(not designed for ultrawide).
|
||||||
|
|
||||||
@@ -16,9 +20,7 @@ the approach is shared by all menu screens. Target: 16:9, 1920x1080, which is
|
|||||||
layer's offset machinery with `Anchor::Canvas`; drawing, the cursor and the
|
layer's offset machinery with `Anchor::Canvas`; drawing, the cursor and the
|
||||||
window's hit tests shifted alike). Mouse events go through `screen::Handle`
|
window's hit tests shifted alike). Mouse events go through `screen::Handle`
|
||||||
(`hud::HandleCanvas`). Keys stay the window's.
|
(`hud::HandleCanvas`). Keys stay the window's.
|
||||||
- In the main menu the game renders a classic 1024x768 frame (presented 4:3).
|
- The main menu thread renders the full width (no 4:3 frame any more).
|
||||||
While a screen's window is shown, `widescreen::SetWideMenuScreen` makes the
|
|
||||||
menu thread render and present the full width.
|
|
||||||
- Parts: `screen::Panel` (dark backdrop and the game's frame, `cUI_Frame`,
|
- Parts: `screen::Panel` (dark backdrop and the game's frame, `cUI_Frame`,
|
||||||
game/ui/frame.h, made anew when its rect changes), `screen::Button` (a
|
game/ui/frame.h, made anew when its rect changes), `screen::Button` (a
|
||||||
button of the game's with the look of one of the window's,
|
button of the game's with the look of one of the window's,
|
||||||
@@ -33,6 +35,42 @@ the approach is shared by all menu screens. Target: 16:9, 1920x1080, which is
|
|||||||
- Inside the canvas scope the HUD layer does not stretch "full 1024 layout"
|
- Inside the canvas scope the HUD layer does not stretch "full 1024 layout"
|
||||||
quads (`g_canvasScope`).
|
quads (`g_canvasScope`).
|
||||||
|
|
||||||
|
## Main menu frame (2026-10-20)
|
||||||
|
|
||||||
|
Request: the background on all screens, the main menu's and the options
|
||||||
|
window's own pictures removed.
|
||||||
|
|
||||||
|
- cUI_Manager's UI render (`0x7587b0`, hooked in mods/screen.cpp) on the
|
||||||
|
menu thread: the background first, then `hud::RenderMenuFrame` runs the
|
||||||
|
render with drawing shifted by (render width - 1024) / 2 and a viewport on
|
||||||
|
the frame (3D parts). Windows inside draw in frame coordinates (their own
|
||||||
|
HUD scopes are nested, so they do nothing).
|
||||||
|
- Mouse positions are given to the game in frame coordinates: the
|
||||||
|
borderless mod's `ToGame` subtracts `hud::MenuFrameOffsetX`. The cursor
|
||||||
|
sprite (`cMouseCursor::DrawItem` 0x6555e0, drawn after the UI) is shifted
|
||||||
|
by drawing only.
|
||||||
|
- While a 16:9 screen is shown the frame is not shifted (offset 0): the
|
||||||
|
screen draws itself across the width.
|
||||||
|
- Full-screen windows in a game (options, `hud::RenderFramed`) get the
|
||||||
|
background beside the frame instead of black.
|
||||||
|
- The windows' pictures are left out by texture: the HUD layer tracks the
|
||||||
|
texture bound to stage 0 (`SetTexture`, device vtable 35) and skips quads
|
||||||
|
drawn with a hidden one (`hud::SetHiddenTextures`). Logged on the menu
|
||||||
|
thread (texture ids requested from the cache, by caller; names by trying
|
||||||
|
the names in the texture pak): main menu GUI_UW_OPTION1..12 + UI_MAIN_SKY,
|
||||||
|
options UW_OPTION_1_01..12. This install uses the Underworld versions; the
|
||||||
|
Ancaria ones (GUI_OPTION1..12, ...) were not seen and are not listed.
|
||||||
|
- The main menu's 3D scene (sky UI_UW_MAIN_CENTER, water with the SACRED
|
||||||
|
logo UI_MAIN_REF; render 0x75e620 on the scene object 0x75df10) is called
|
||||||
|
from cUI_MainMenu::Render (0x7122c0) at 0x712bd9; that call is redirected
|
||||||
|
to an empty function. cUI_Character (0x70f41b) still draws it.
|
||||||
|
- Logo top left (2026-10-21), 48 from the edges, 124 high: FX_SACREDLOGO.TGA,
|
||||||
|
the emblem (horns around a ring of runes) glowing blue on black without
|
||||||
|
alpha, drawn added (blend ONE/ONE, `game::DrawTexturedAdded`) as the
|
||||||
|
game's effects draw it. Other candidates: GUI_MAIN_LOGO (plain golden
|
||||||
|
lettering), SACREDSYMBOL01/02 (model atlases), the emblem on the loading
|
||||||
|
pictures (PAK\loading*.bmp, not in the paks). Screens may overlap it.
|
||||||
|
|
||||||
## Screens
|
## Screens
|
||||||
|
|
||||||
- Spielen (the load window, cUI_Savegame): docs/characters.md, "Spielen
|
- Spielen (the load window, cUI_Savegame): docs/characters.md, "Spielen
|
||||||
|
|||||||
+11
-3
@@ -211,7 +211,15 @@ space far better. No 16:9 artwork exists for the game (user, 2026-10-20):
|
|||||||
we design our own UIs, using the width beyond the 1024 frame (now drawn in
|
we design our own UIs, using the width beyond the 1024 frame (now drawn in
|
||||||
a centred 1024x768 viewport with black sides, hud.cpp FrameScope). Design
|
a centred 1024x768 viewport with black sides, hud.cpp FrameScope). Design
|
||||||
for 16:9 (1920x1080, 1365x768 UI units), not ultrawide (user). Screen done
|
for 16:9 (1920x1080, 1365x768 UI units), not ultrawide (user). Screen done
|
||||||
2026-10-20 (docs/characters.md, "Spielen screen"; docs/screens.md). Open:
|
2026-10-20 (docs/characters.md, "Spielen screen"; docs/screens.md). Hero
|
||||||
the render of the selected hero (the hero panel shows text only; reuse
|
in 3D done 2026-10-20. Open: list scrolling beyond 9 characters, larger
|
||||||
cUI_Character's 3D hero?), list scrolling beyond 9 characters, larger
|
|
||||||
heading font.
|
heading font.
|
||||||
|
|
||||||
|
## Fine-tuning the Spielen and options screens (2026-10-21)
|
||||||
|
|
||||||
|
The user is very satisfied with the menu rework for now (rock background on
|
||||||
|
all menu screens, centred menu frame, logo, Spielen screen with the 3D
|
||||||
|
hero), but both the Spielen screen and the options window still need a lot
|
||||||
|
of fine-tuning. Details to be collected when we get to it; known so far:
|
||||||
|
list scrolling beyond 9 characters, larger heading font, the options
|
||||||
|
window's layout in the width beyond its 1024 frame.
|
||||||
+4
-2
@@ -67,9 +67,11 @@ Elsewhere (unknown / UI, leave alone for now): `0x40694a`, `0x4ec036`,
|
|||||||
| `0x006469d0` | loading screen: GDI BitBlt of bitmap + progress bar + text into the back buffer (fixed 1024x768) | render thread |
|
| `0x006469d0` | loading screen: GDI BitBlt of bitmap + progress bar + text into the back buffer (fixed 1024x768) | render thread |
|
||||||
| `0x006a0c60` | video/intro playback (unexamined) | |
|
| `0x006a0c60` | video/intro playback (unexamined) | |
|
||||||
|
|
||||||
The widescreen mod gives these frames a 1024x768 D3D viewport (set at
|
The widescreen mod gives the loading screen a 1024x768 D3D viewport (set at
|
||||||
`BeginScene` when the scene depth `+0x1d8` is 0) and the borderless mod
|
`BeginScene` when the scene depth `+0x1d8` is 0) and the borderless mod
|
||||||
presents only that area, scaled to the monitor.
|
presents only that area, scaled to the monitor. The main menu thread renders
|
||||||
|
the full width since 2026-10-20: its 1024x768 frame is drawn centred on the
|
||||||
|
menu screens' background (docs/screens.md, "Main menu frame").
|
||||||
|
|
||||||
## Direct3D 7 render target limit
|
## Direct3D 7 render target limit
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ namespace game {
|
|||||||
|
|
||||||
constexpr int kDeviceDrawPrimitive = 25; // IDirect3DDevice7 vtable slots
|
constexpr int kDeviceDrawPrimitive = 25; // IDirect3DDevice7 vtable slots
|
||||||
constexpr int kDeviceSetTexture = 35;
|
constexpr int kDeviceSetTexture = 35;
|
||||||
|
constexpr int kDeviceSetRenderState = 20;
|
||||||
|
constexpr int kDeviceGetRenderState = 21;
|
||||||
|
constexpr DWORD kStateSourceBlend = 19; // D3DRENDERSTATETYPE
|
||||||
|
constexpr DWORD kStateDestinationBlend = 20;
|
||||||
|
constexpr DWORD kStateAlphaBlendEnable = 27;
|
||||||
|
constexpr DWORD kBlendOne = 2; // D3DBLEND
|
||||||
constexpr DWORD kTriangleStrip = 5;
|
constexpr DWORD kTriangleStrip = 5;
|
||||||
constexpr DWORD kFvfScreenColourTexture = 0x1c4; // XYZRHW | DIFFUSE | SPECULAR | TEX1
|
constexpr DWORD kFvfScreenColourTexture = 0x1c4; // XYZRHW | DIFFUSE | SPECULAR | TEX1
|
||||||
|
|
||||||
@@ -19,6 +25,45 @@ struct ScreenVertex {
|
|||||||
float u, v;
|
float u, v;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Draws (x0, y0)-(x1, y1) with `surface` (a texture's Direct3D surface,
|
||||||
|
// game/ui/texture.h) from (u0, v0) to (u1, v1), tinted by `colour`.
|
||||||
|
inline void DrawTextured(void* device, void* surface, float x0, float y0, float x1, float y1, float u0, float v0,
|
||||||
|
float u1, float v1, uint32_t colour = 0xffffffff) {
|
||||||
|
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, surface);
|
||||||
|
ScreenVertex quad[4] = {{x0, y0, 0, 1, colour, 0, u0, v0},
|
||||||
|
{x1, y0, 0, 1, colour, 0, u1, v0},
|
||||||
|
{x0, y1, 0, 1, colour, 0, u0, v1},
|
||||||
|
{x1, y1, 0, 1, colour, 0, u1, v1}};
|
||||||
|
reinterpret_cast<DrawPrimitiveFn>(vtable[kDeviceDrawPrimitive])(device, kTriangleStrip, kFvfScreenColourTexture,
|
||||||
|
quad, 4, 0);
|
||||||
|
reinterpret_cast<SetTextureFn>(vtable[kDeviceSetTexture])(device, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawTextured added onto what is drawn (source and destination blend ONE):
|
||||||
|
// for the effect textures, drawn on black without alpha.
|
||||||
|
inline void DrawTexturedAdded(void* device, void* surface, float x0, float y0, float x1, float y1, float u0,
|
||||||
|
float v0, float u1, float v1) {
|
||||||
|
void** vtable = *static_cast<void***>(device);
|
||||||
|
using SetRenderStateFn = HRESULT(__stdcall*)(void*, DWORD, DWORD);
|
||||||
|
using GetRenderStateFn = HRESULT(__stdcall*)(void*, DWORD, DWORD*);
|
||||||
|
const auto set = reinterpret_cast<SetRenderStateFn>(vtable[kDeviceSetRenderState]);
|
||||||
|
const auto get = reinterpret_cast<GetRenderStateFn>(vtable[kDeviceGetRenderState]);
|
||||||
|
DWORD blending = 0, source = 0, destination = 0;
|
||||||
|
get(device, kStateAlphaBlendEnable, &blending);
|
||||||
|
get(device, kStateSourceBlend, &source);
|
||||||
|
get(device, kStateDestinationBlend, &destination);
|
||||||
|
set(device, kStateAlphaBlendEnable, TRUE);
|
||||||
|
set(device, kStateSourceBlend, kBlendOne);
|
||||||
|
set(device, kStateDestinationBlend, kBlendOne);
|
||||||
|
DrawTextured(device, surface, x0, y0, x1, y1, u0, v0, u1, v1);
|
||||||
|
set(device, kStateAlphaBlendEnable, blending);
|
||||||
|
set(device, kStateSourceBlend, source);
|
||||||
|
set(device, kStateDestinationBlend, destination);
|
||||||
|
}
|
||||||
|
|
||||||
// Fills (x0, y0)-(x1, y1), ARGB colours per corner: top left, top right,
|
// Fills (x0, y0)-(x1, y1), ARGB colours per corner: top left, top right,
|
||||||
// bottom left, bottom right (blended between them).
|
// bottom left, bottom right (blended between them).
|
||||||
inline void FillCorners(void* device, float x0, float y0, float x1, float y1, uint32_t topLeft, uint32_t topRight,
|
inline void FillCorners(void* device, float x0, float y0, float x1, float y1, uint32_t topLeft, uint32_t topRight,
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
// cUI_NetGranny (vtable 0x894c8c, 0x634 bytes, window name UI_WND_GRANNY): the
|
||||||
|
// game's 3D view of a hero with its equipment, as the export screen's slots
|
||||||
|
// (cUI_Character +0x1b4, 0x710f31) and the multiplayer lobby showed them.
|
||||||
|
//
|
||||||
|
// The model comes from a hero info (0x22c bytes: class at +4, the equipment's
|
||||||
|
// looks), which LoadHeroFile (0x61d190, cdecl (path, 0, info)) reads from a
|
||||||
|
// hero file without touching the player hero (0x70bf2c).
|
||||||
|
//
|
||||||
|
// Render (vf14 -> vf44 0x6f17f0 (device, 0, 1), only while visible; the last
|
||||||
|
// argument adds the name and level below the model, 0x6f1260). With +0x53d
|
||||||
|
// clear the model stands at the rect's horizontal centre, its feet at
|
||||||
|
// rect.y + h * k, projected with the creatures' ortho camera (x -512..512
|
||||||
|
// around the screen centre, widened by the widescreen mod); size factor
|
||||||
|
// +0x630 (2.5 from the constructor). Two lights are made on the first render
|
||||||
|
// (0x6f0f10): a dim blue set (the lobby) with +0x53f clear, a white one with
|
||||||
|
// it set. With +0x53d set (cUI_Character) the model is placed in a
|
||||||
|
// perspective scene instead.
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "game/ui/tabs.h"
|
||||||
|
#include "game/ui/text.h"
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
constexpr size_t kNetGrannySize = 0x634;
|
||||||
|
constexpr size_t kNetGrannyInScene = 0x53d; // u8, see above
|
||||||
|
constexpr size_t kNetGrannyWhiteLight = 0x53f; // u8, before the first render
|
||||||
|
constexpr size_t kNetGrannyScale = 0x630; // float
|
||||||
|
constexpr size_t kHeroInfoSize = 0x22c;
|
||||||
|
|
||||||
|
// cUI_NetGranny::cUI_NetGranny (0x6f06f0, thiscall (rect by value), ret 0xc).
|
||||||
|
inline uint8_t* NewNetGranny(UiRect rect) {
|
||||||
|
void* memory = GameNew(kNetGrannySize);
|
||||||
|
using ConstructFn = uint8_t*(__fastcall*)(void*, void*, UiRect);
|
||||||
|
return memory ? reinterpret_cast<ConstructFn>(0x006f06f0)(memory, nullptr, rect) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The model alone, without its name and level (vf44, thiscall (device,
|
||||||
|
// 0, label), ret 0xc).
|
||||||
|
inline void RenderNetGranny(uint8_t* granny, void* device) {
|
||||||
|
using RenderFn = void(__fastcall*)(void*, void*, void*, uint32_t, uint8_t);
|
||||||
|
reinterpret_cast<RenderFn>((*reinterpret_cast<void***>(granny))[0x44 / 4])(granny, nullptr, device, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shows the hero of `info` (0x6f4b20, thiscall (info, x, y, z, turn), ret
|
||||||
|
// 0x14; the four floats place it in the perspective scene only).
|
||||||
|
inline void SetNetGrannyHero(uint8_t* granny, const void* info) {
|
||||||
|
using SetFn = void(__fastcall*)(void*, void*, const void*, float, float, float, float);
|
||||||
|
reinterpret_cast<SetFn>(0x006f4b20)(granny, nullptr, info, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No hero (0x6f4ae0, thiscall ()).
|
||||||
|
inline void ClearNetGrannyHero(uint8_t* granny) {
|
||||||
|
reinterpret_cast<void(__fastcall*)(void*, void*)>(0x006f4ae0)(granny, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The hero info of a hero file (a path as the game's file functions take it);
|
||||||
|
// false if it cannot be read. `info` holds kHeroInfoSize bytes.
|
||||||
|
inline bool ReadHeroInfo(const char* path, void* info) {
|
||||||
|
using LoadFn = uint8_t(__cdecl*)(const char*, uint32_t, void*);
|
||||||
|
return reinterpret_cast<LoadFn>(0x0061d190)(path, 0, info) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
// The game's textures by name (a .TGA in the texture paks), as cGranny's
|
||||||
|
// effects load them (0x401232): the texture manager (0x656c80, cdecl (),
|
||||||
|
// created on first use, kept at 0x13e57b4) gives the id of a name (0x657620,
|
||||||
|
// thiscall (name), ret 4); the texture cache (0x13e57b8) loads it on demand
|
||||||
|
// (0x65f010, thiscall (id, 0), ret 8), its Direct3D surface at +0x14 (as
|
||||||
|
// cUI_Button's render binds it, 0x72afb7).
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
constexpr uintptr_t kTextureCache = 0x013e57b8;
|
||||||
|
constexpr size_t kTextureSurface = 0x14;
|
||||||
|
|
||||||
|
inline uint32_t TextureId(const char* name) {
|
||||||
|
void* manager = reinterpret_cast<void*(__cdecl*)()>(0x00656c80)();
|
||||||
|
using IdFn = uint32_t(__fastcall*)(void*, void*, const char*);
|
||||||
|
return manager ? reinterpret_cast<IdFn>(0x00657620)(manager, nullptr, name) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Direct3D surface of texture `id`, loaded if needed; null if none.
|
||||||
|
inline void* TextureSurface(uint32_t id) {
|
||||||
|
void* cache = *reinterpret_cast<void**>(kTextureCache);
|
||||||
|
if (!cache || !id) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
using GetFn = uint8_t*(__fastcall*)(void*, void*, uint32_t, uint32_t);
|
||||||
|
uint8_t* texture = reinterpret_cast<GetFn>(0x0065f010)(cache, nullptr, id, 0);
|
||||||
|
return texture ? *reinterpret_cast<void**>(texture + kTextureSurface) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
@@ -138,6 +138,7 @@ POINT ToGame(POINT p) {
|
|||||||
POINT result;
|
POINT result;
|
||||||
result.x = std::clamp(MulDiv(p.x - layout.client.left, layout.gameWidth, width), 0, layout.gameWidth - 1);
|
result.x = std::clamp(MulDiv(p.x - layout.client.left, layout.gameWidth, width), 0, layout.gameWidth - 1);
|
||||||
result.y = std::clamp(MulDiv(p.y - layout.client.top, layout.gameHeight, height), 0, layout.gameHeight - 1);
|
result.y = std::clamp(MulDiv(p.y - layout.client.top, layout.gameHeight, height), 0, layout.gameHeight - 1);
|
||||||
|
result.x -= mods::hud::MenuFrameOffsetX(); // the main menu's centred frame
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+70
-3
@@ -49,6 +49,7 @@
|
|||||||
#include "game/ui/button.h"
|
#include "game/ui/button.h"
|
||||||
#include "game/ui/control.h"
|
#include "game/ui/control.h"
|
||||||
#include "game/ui/events.h"
|
#include "game/ui/events.h"
|
||||||
|
#include "game/ui/granny.h"
|
||||||
#include "game/ui/text.h"
|
#include "game/ui/text.h"
|
||||||
#include "game/ui/texts.h"
|
#include "game/ui/texts.h"
|
||||||
#include "game/ui/windows.h"
|
#include "game/ui/windows.h"
|
||||||
@@ -386,6 +387,8 @@ std::atomic<bool> g_autosavePending{false};
|
|||||||
bool g_inGame = false; // a game runs (the load window opens from the Esc menu too)
|
bool g_inGame = false; // a game runs (the load window opens from the Esc menu too)
|
||||||
std::atomic<uint64_t> g_lastSaveAt{0}; // GetTickCount64 of the last save of any kind
|
std::atomic<uint64_t> g_lastSaveAt{0}; // GetTickCount64 of the last save of any kind
|
||||||
|
|
||||||
|
void ForgetHeroModel(); // the files may have changed: read the hero anew
|
||||||
|
|
||||||
std::string Description(const json& meta) {
|
std::string Description(const json& meta) {
|
||||||
return Convert(FromUtf8(meta.value("name", std::string())) + L" - " + ClassName(meta.value("class", 0u)),
|
return Convert(FromUtf8(meta.value("name", std::string())) + L" - " + ClassName(meta.value("class", 0u)),
|
||||||
CP_ACP);
|
CP_ACP);
|
||||||
@@ -417,6 +420,7 @@ void RebuildList() {
|
|||||||
DeleteFileA((work + file).c_str());
|
DeleteFileA((work + file).c_str());
|
||||||
}
|
}
|
||||||
g_rows.clear();
|
g_rows.clear();
|
||||||
|
ForgetHeroModel();
|
||||||
for (const std::string& name : FilesIn(CharactersDir(), "*.sacredK")) {
|
for (const std::string& name : FilesIn(CharactersDir(), "*.sacredK")) {
|
||||||
if (EndsWith(name, kAutosaveExtension) || g_rows.size() >= kMaxRows) {
|
if (EndsWith(name, kAutosaveExtension) || g_rows.size() >= kMaxRows) {
|
||||||
continue;
|
continue;
|
||||||
@@ -616,8 +620,10 @@ std::atomic<int> g_loadRow{-1};
|
|||||||
|
|
||||||
// Layout on the canvas (1365x768): three panels side by side (sizes in the
|
// Layout on the canvas (1365x768): three panels side by side (sizes in the
|
||||||
// frames' 64 steps, screen::kFrameStep), centred, a row of buttons below.
|
// frames' 64 steps, screen::kFrameStep), centred, a row of buttons below.
|
||||||
constexpr int kPanelTop = 96; // the title above the frames' ornament
|
// Top: room for the heading and the frames' ornament; the buttons end
|
||||||
constexpr int kPanelHeight = 8 * screen::kFrameStep;
|
// above the screen's bottom.
|
||||||
|
constexpr int kPanelTop = 164 + screen::kFrameOrnament;
|
||||||
|
constexpr int kPanelHeight = 7 * screen::kFrameStep;
|
||||||
constexpr int kListWidth = 5 * screen::kFrameStep;
|
constexpr int kListWidth = 5 * screen::kFrameStep;
|
||||||
constexpr int kHeroWidth = 6 * screen::kFrameStep;
|
constexpr int kHeroWidth = 6 * screen::kFrameStep;
|
||||||
constexpr int kWorldsWidth = 6 * screen::kFrameStep;
|
constexpr int kWorldsWidth = 6 * screen::kFrameStep;
|
||||||
@@ -1329,6 +1335,64 @@ void DrawList(void* device) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The selected character's hero in 3D (game/ui/granny.h): its hero file is
|
||||||
|
// unpacked to the game's save folder (_work) and read as a hero info. A
|
||||||
|
// migrated character without the shared hero has none until it is played.
|
||||||
|
constexpr float kHeroModelScale = 3.8f; // the game's 2.5 fills a third of the panel
|
||||||
|
constexpr int kHeroModelFeetAboveBottom = 24;
|
||||||
|
constexpr char kWorkPreviewHero[] = "preview.pax";
|
||||||
|
constexpr char kGamePreviewHero[] = "Save\\preview.pax";
|
||||||
|
|
||||||
|
struct HeroModel {
|
||||||
|
uint8_t* control = nullptr;
|
||||||
|
std::string file; // whose hero it shows
|
||||||
|
bool hasHero = false;
|
||||||
|
} g_heroModel;
|
||||||
|
|
||||||
|
void ForgetHeroModel() {
|
||||||
|
g_heroModel.file.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowHeroOf(const Row& row) {
|
||||||
|
if (row.file == g_heroModel.file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_heroModel.file = row.file;
|
||||||
|
g_heroModel.hasHero = false;
|
||||||
|
CharacterFile character(row.file);
|
||||||
|
std::optional<Bytes> hero = character.Open() ? character.Read("hero") : std::nullopt;
|
||||||
|
std::vector<uint8_t> info(game::kHeroInfoSize);
|
||||||
|
if (hero && WriteFileReplacing(WorkDir() + kWorkPreviewHero, *hero) &&
|
||||||
|
game::ReadHeroInfo(kGamePreviewHero, info.data())) {
|
||||||
|
game::SetNetGrannyHero(g_heroModel.control, info.data());
|
||||||
|
g_heroModel.hasHero = true;
|
||||||
|
} else {
|
||||||
|
game::ClearNetGrannyHero(g_heroModel.control);
|
||||||
|
}
|
||||||
|
DeleteFileA((WorkDir() + kWorkPreviewHero).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The model stands centred in `rect` (canvas coordinates). The control centres
|
||||||
|
// it as in a 1024 wide layout (x - 512 around the screen centre); the canvas
|
||||||
|
// is centred too, so its x is moved by the difference.
|
||||||
|
void DrawHeroModel(void* device, const Row& row, game::UiRect rect) {
|
||||||
|
rect.x -= (screen::kWidth - 1024) / 2;
|
||||||
|
if (!g_heroModel.control) {
|
||||||
|
g_heroModel.control = game::NewNetGranny(rect);
|
||||||
|
if (!g_heroModel.control) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_heroModel.control[game::kNetGrannyWhiteLight] = 1;
|
||||||
|
*reinterpret_cast<float*>(g_heroModel.control + game::kNetGrannyScale) = kHeroModelScale;
|
||||||
|
game::SetControlShown(g_heroModel.control, true);
|
||||||
|
}
|
||||||
|
*reinterpret_cast<game::UiRect*>(g_heroModel.control + game::kControlRect) = rect;
|
||||||
|
ShowHeroOf(row);
|
||||||
|
if (g_heroModel.hasHero) {
|
||||||
|
game::RenderNetGranny(g_heroModel.control, device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DrawHero(void* device) {
|
void DrawHero(void* device) {
|
||||||
const game::UiRect content = g_heroPanel.Draw(device, kHeroRect);
|
const game::UiRect content = g_heroPanel.Draw(device, kHeroRect);
|
||||||
const Row* row = SelectedCharacter();
|
const Row* row = SelectedCharacter();
|
||||||
@@ -1348,6 +1412,9 @@ void DrawHero(void* device) {
|
|||||||
line(CampaignName(last->campaign) + L" · " + DifficultyName(last->difficulty), screen::kTextColour);
|
line(CampaignName(last->campaign) + L" · " + DifficultyName(last->difficulty), screen::kTextColour);
|
||||||
line(FormatTime(last->savedAt), screen::kDimColour);
|
line(FormatTime(last->savedAt), screen::kDimColour);
|
||||||
}
|
}
|
||||||
|
// The control puts the feet at 5/6 of its rect's height (0x88f04c).
|
||||||
|
const int feet = content.y + content.h - kHeroModelFeetAboveBottom;
|
||||||
|
DrawHeroModel(device, *row, {content.x, y, content.w, static_cast<int16_t>((feet - y) * 6 / 5)});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawCell(void* device, const Row& row, int campaign, int difficulty) {
|
void DrawCell(void* device, const Row& row, int campaign, int difficulty) {
|
||||||
@@ -1375,7 +1442,7 @@ void DrawCell(void* device, const Row& row, int campaign, int difficulty) {
|
|||||||
if (cell.autosave && cell.world) {
|
if (cell.autosave && cell.world) {
|
||||||
screen::Text(device, bottom, L"+ Autosave", screen::kHeadingColour, game::kTextCenter);
|
screen::Text(device, bottom, L"+ Autosave", screen::kHeadingColour, game::kTextCenter);
|
||||||
} else if (row.storyCompleted[difficulty]) {
|
} else if (row.storyCompleted[difficulty]) {
|
||||||
screen::Text(device, bottom, L"Story abgeschlossen", screen::kDimColour, game::kTextCenter);
|
screen::Text(device, bottom, L"abgeschlossen", screen::kDimColour, game::kTextCenter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+111
-13
@@ -21,6 +21,7 @@
|
|||||||
// Enabled=1
|
// Enabled=1
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
#include "game/render/dx_driver7.h"
|
#include "game/render/dx_driver7.h"
|
||||||
#include "game/ui/events.h"
|
#include "game/ui/events.h"
|
||||||
#include "mods/hud.h"
|
#include "mods/hud.h"
|
||||||
|
#include "mods/screen.h"
|
||||||
#include "mods/widescreen.h"
|
#include "mods/widescreen.h"
|
||||||
#include "re/config.h"
|
#include "re/config.h"
|
||||||
#include "re/hook.h"
|
#include "re/hook.h"
|
||||||
@@ -268,6 +270,29 @@ private:
|
|||||||
CursorOffset cursor_;
|
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
|
// Stores the offsets of a mouse event's x fields (game/ui/events.h); returns
|
||||||
// their count (0: no mouse event).
|
// their count (0: no mouse event).
|
||||||
int MouseEventXOffsets(void* event, int (&offsets)[2]) {
|
int MouseEventXOffsets(void* event, int (&offsets)[2]) {
|
||||||
@@ -383,13 +408,47 @@ using DrawIndexedPrimitiveFn = HRESULT(__stdcall*)(void* device, DWORD type, DWO
|
|||||||
DrawPrimitiveFn g_drawPrimitive = nullptr;
|
DrawPrimitiveFn g_drawPrimitive = nullptr;
|
||||||
DrawIndexedPrimitiveFn g_drawIndexedPrimitive = 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,
|
HRESULT __stdcall DrawPrimitiveShifted(void* device, DWORD type, DWORD fvf, void* vertices, DWORD count,
|
||||||
DWORD flags) {
|
DWORD flags) {
|
||||||
|
if (BoundTextureHidden()) {
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
return g_drawPrimitive(device, type, fvf, ShiftVertices(fvf, vertices, count), count, flags);
|
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,
|
HRESULT __stdcall DrawIndexedPrimitiveShifted(void* device, DWORD type, DWORD fvf, void* vertices, DWORD count,
|
||||||
WORD* indices, DWORD indexCount, DWORD flags) {
|
WORD* indices, DWORD indexCount, DWORD flags) {
|
||||||
|
if (BoundTextureHidden()) {
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
return g_drawIndexedPrimitive(device, type, fvf, ShiftVertices(fvf, vertices, count), count, indices,
|
return g_drawIndexedPrimitive(device, type, fvf, ShiftVertices(fvf, vertices, count), count, indices,
|
||||||
indexCount, flags);
|
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);
|
using GetViewportFn = HRESULT(__stdcall*)(void* device, Viewport7* viewport);
|
||||||
constexpr int kVtableGetViewport = 15; // IDirect3DDevice7 (13 SetViewport, 14 MultiplyTransform)
|
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
|
// RAII for full-screen windows (options in a game) that fill the 1024 frame
|
||||||
// 1024 frame with a picture: the render target left and right of the frame is
|
// with a picture: the menu screens' background (mods/screen.h) goes under
|
||||||
// cleared black, and the window draws with a viewport on the frame. The
|
// it, and the window draws with a viewport on the frame. The
|
||||||
// export screen's 3D scene (heroes, background) uses a projection for a
|
// 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
|
// 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).
|
// 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 {
|
class FrameScope {
|
||||||
public:
|
public:
|
||||||
FrameScope(void* window, void* device, int dx) {
|
FrameScope(void* window, void* device, int dx) {
|
||||||
const auto* w = static_cast<const uint8_t*>(window);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
auto getViewport = reinterpret_cast<GetViewportFn>((*static_cast<void***>(device))[kVtableGetViewport]);
|
auto getViewport = reinterpret_cast<GetViewportFn>((*static_cast<void***>(device))[kVtableGetViewport]);
|
||||||
if (FAILED(getViewport(device, &previous_))) {
|
if (FAILED(getViewport(device, &previous_))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const LONG height = static_cast<LONG>(previous_.height);
|
mods::screen::DrawBackground(device);
|
||||||
Rect7 sides[] = {{0, 0, dx, height},
|
|
||||||
{dx + kLayoutWidth, 0, mods::widescreen::RenderWidth(), height}};
|
|
||||||
g_clear(device, 2, sides, kClearTarget, 0, 1.0f, 0);
|
|
||||||
Viewport7 frame = {static_cast<DWORD>(dx), 0, kLayoutWidth, 768, previous_.minZ, previous_.maxZ};
|
Viewport7 frame = {static_cast<DWORD>(dx), 0, kLayoutWidth, 768, previous_.minZ, previous_.maxZ};
|
||||||
device_ = device;
|
device_ = device;
|
||||||
g_setViewport(device, &frame);
|
g_setViewport(device, &frame);
|
||||||
@@ -512,6 +566,10 @@ void OnDevice(void* device) {
|
|||||||
g_drawIndexedPrimitive = reinterpret_cast<DrawIndexedPrimitiveFn>(vtable[26]);
|
g_drawIndexedPrimitive = reinterpret_cast<DrawIndexedPrimitiveFn>(vtable[26]);
|
||||||
re::AttachRuntimeHook("D3D7 DrawIndexedPrimitive", reinterpret_cast<void**>(&g_drawIndexedPrimitive),
|
re::AttachRuntimeHook("D3D7 DrawIndexedPrimitive", reinterpret_cast<void**>(&g_drawIndexedPrimitive),
|
||||||
reinterpret_cast<void*>(&DrawIndexedPrimitiveShifted));
|
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.
|
// Clear 10, SetViewport 13.
|
||||||
g_clear = reinterpret_cast<ClearFn>(vtable[10]);
|
g_clear = reinterpret_cast<ClearFn>(vtable[10]);
|
||||||
re::AttachRuntimeHook("D3D7 Clear", reinterpret_cast<void**>(&g_clear), reinterpret_cast<void*>(&ClearShifted));
|
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);
|
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() {
|
int CanvasOffsetX() {
|
||||||
return OffsetFor(Anchor::Canvas);
|
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);
|
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
|
// The cursor and the item it carries (0x6555e0, drawn by the render loop
|
||||||
// after the UI) is placed like UI items: relative to x = 512 of the layout.
|
// after the UI) are placed like UI items: relative to x = 512 of the layout.
|
||||||
// Items are only picked up from centred windows.
|
// 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);
|
int __fastcall MouseCursor_DrawItem(void* self, void* edx, int a, int b);
|
||||||
RE_HOOK(0x006555e0, MouseCursor_DrawItem);
|
RE_HOOK(0x006555e0, MouseCursor_DrawItem);
|
||||||
int __fastcall MouseCursor_DrawItem(void* self, void* edx, int a, int b) {
|
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));
|
mods::hud::DrawOffset offset(nullptr, mods::hud::OffsetFor(Anchor::Center));
|
||||||
return MouseCursor_DrawItem_original(mods::hud::CursorFor(self), edx, a, b);
|
return MouseCursor_DrawItem_original(mods::hud::CursorFor(self), edx, a, b);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-2
@@ -26,8 +26,8 @@ int VisibleDialogTooltips(void** out, int max);
|
|||||||
// shifted) or event handler (mouse position shifted by the window's offset).
|
// shifted) or event handler (mouse position shifted by the window's offset).
|
||||||
int RenderCentred(void* window, int (*body)(void* context), void* context);
|
int RenderCentred(void* window, int (*body)(void* context), void* context);
|
||||||
int HandleCentred(void* event, int (*body)(void* context), void* context);
|
int HandleCentred(void* event, int (*body)(void* context), void* context);
|
||||||
// RenderCentred for full-screen windows: black beside the 1024 frame, drawn
|
// RenderCentred for full-screen windows: the menu screens' background beside
|
||||||
// with a viewport on it (as the save game and export screens).
|
// the 1024 frame, drawn with a viewport on it (as the options in a game).
|
||||||
int RenderFramed(void* window, void* device, int (*body)(void* context), void* context);
|
int RenderFramed(void* window, void* device, int (*body)(void* context), void* context);
|
||||||
|
|
||||||
// 16:9 screens (mods/screen.h): laid out on a 1365x768 canvas, centred in
|
// 16:9 screens (mods/screen.h): laid out on a 1365x768 canvas, centred in
|
||||||
@@ -40,4 +40,16 @@ int CanvasOffsetX();
|
|||||||
int RenderCanvas(void* window, int (*body)(void* context), void* context);
|
int RenderCanvas(void* window, int (*body)(void* context), void* context);
|
||||||
int HandleCanvas(void* event, int (*body)(void* context), void* context);
|
int HandleCanvas(void* event, int (*body)(void* context), void* context);
|
||||||
|
|
||||||
|
// The main menu (mods/screen.cpp): with `framed`, body(context) draws the
|
||||||
|
// menu's 1024x768 frame centred in the render width (shifted, with a
|
||||||
|
// viewport on it); mouse positions are then given in frame coordinates
|
||||||
|
// (MenuFrameOffsetX, applied by the borderless mod). Without, the menu draws
|
||||||
|
// unshifted (a 16:9 screen is shown).
|
||||||
|
int RenderMenuFrame(void* device, bool framed, int (*body)(void* context), void* context);
|
||||||
|
int MenuFrameOffsetX();
|
||||||
|
|
||||||
|
// Quads drawn with one of these textures (Direct3D surfaces) are left out,
|
||||||
|
// on every thread, until the list changes (at most 64).
|
||||||
|
void SetHiddenTextures(void* const* surfaces, int count);
|
||||||
|
|
||||||
} // namespace mods::hud
|
} // namespace mods::hud
|
||||||
+156
-11
@@ -6,25 +6,38 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "game/addresses.h"
|
||||||
|
#include "game/render/dx_driver7.h"
|
||||||
#include "game/ui/button.h"
|
#include "game/ui/button.h"
|
||||||
#include "game/ui/control.h"
|
#include "game/ui/control.h"
|
||||||
#include "game/ui/draw.h"
|
#include "game/ui/draw.h"
|
||||||
#include "game/ui/events.h"
|
#include "game/ui/events.h"
|
||||||
#include "game/ui/frame.h"
|
#include "game/ui/frame.h"
|
||||||
|
#include "game/ui/texture.h"
|
||||||
#include "game/ui/texts.h"
|
#include "game/ui/texts.h"
|
||||||
#include "mods/widescreen.h"
|
#include "mods/widescreen.h"
|
||||||
|
#include "re/hook.h"
|
||||||
|
#include "re/patch.h"
|
||||||
|
|
||||||
namespace mods::screen {
|
namespace mods::screen {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Background (decided 2026-10-20: our own, no picture): dark, a little warm
|
// Background (user, 2026-10-20: "textured dark", then "doesn't need to be that
|
||||||
// at the top, darker towards the bottom and the sides.
|
// dark, but apply it to all screens"): the options window's rock wall,
|
||||||
constexpr uint32_t kBackgroundTop = 0xff1c1712;
|
// GUI_OPTION_BACKGROUND<row><column>.TGA (4 x 3 tiles of 256, a 1024x768
|
||||||
constexpr uint32_t kBackgroundBottom = 0xff070605;
|
// picture), scaled to cover the render target, a little darkened, darker
|
||||||
constexpr uint32_t kVignette = 0xa0000000;
|
// towards the bottom and the sides.
|
||||||
|
constexpr int kBackgroundColumns = 4;
|
||||||
|
constexpr int kBackgroundRows = 3;
|
||||||
|
constexpr float kBackgroundTile = 256.0f;
|
||||||
|
constexpr uint32_t kBackgroundTint = 0xffa0a0a0;
|
||||||
|
constexpr uint32_t kShadeTop = 0x10000000;
|
||||||
|
constexpr uint32_t kShadeBottom = 0x70000000;
|
||||||
|
constexpr uint32_t kVignette = 0x70000000;
|
||||||
constexpr int kVignetteWidth = 240;
|
constexpr int kVignetteWidth = 240;
|
||||||
constexpr uint32_t kPanelBackdrop = 0xb4000000;
|
constexpr uint32_t kPanelBackdrop = 0xb4000000;
|
||||||
constexpr int kHeadingHeight = 30;
|
constexpr int kHeadingHeight = 30;
|
||||||
@@ -33,8 +46,8 @@ constexpr int kHeadingHeight = 30;
|
|||||||
// frames).
|
// frames).
|
||||||
std::atomic<unsigned> g_frame{1};
|
std::atomic<unsigned> g_frame{1};
|
||||||
|
|
||||||
// The windows whose screen is shown: the menu thread renders the full width
|
// The windows whose screen is shown: the main menu's frame is not drawn
|
||||||
// while any is.
|
// centred while any is (the screen draws itself across the width).
|
||||||
std::mutex g_shownLock;
|
std::mutex g_shownLock;
|
||||||
std::set<const void*> g_shown;
|
std::set<const void*> g_shown;
|
||||||
|
|
||||||
@@ -45,19 +58,98 @@ void NoteShown(const void* window, bool shown) {
|
|||||||
} else {
|
} else {
|
||||||
g_shown.erase(window);
|
g_shown.erase(window);
|
||||||
}
|
}
|
||||||
widescreen::SetWideMenuScreen(!g_shown.empty());
|
}
|
||||||
|
|
||||||
|
// The windows' own pictures, replaced by the background (user, 2026-10-20:
|
||||||
|
// "the previous backgrounds from settings and mainMenu can be removed"), 12
|
||||||
|
// tiles each (logged: the textures the menu thread asks for, by name): the
|
||||||
|
// main menu GUI_UW_OPTION<1..12>.TGA with the sky above it UI_MAIN_SKY.TGA,
|
||||||
|
// the options window UW_OPTION_1_<01..12>.TGA. The main menu's 3D scene
|
||||||
|
// behind its picture is not drawn either (game::RemoveMenuScene).
|
||||||
|
std::vector<uint32_t> PictureTextureIds() {
|
||||||
|
std::vector<uint32_t> ids = {game::TextureId("UI_MAIN_SKY.TGA")};
|
||||||
|
char name[40];
|
||||||
|
for (int i = 1; i <= 12; ++i) {
|
||||||
|
wsprintfA(name, "GUI_UW_OPTION%d.TGA", i);
|
||||||
|
ids.push_back(game::TextureId(name));
|
||||||
|
wsprintfA(name, "UW_OPTION_1_%02d.TGA", i);
|
||||||
|
ids.push_back(game::TextureId(name));
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Their surfaces (loaded now, as the window is about to draw them; the cache
|
||||||
|
// may have made them anew) go to the HUD layer's hidden list.
|
||||||
|
void HidePictures() {
|
||||||
|
static const std::vector<uint32_t> ids = PictureTextureIds();
|
||||||
|
std::vector<void*> surfaces;
|
||||||
|
for (uint32_t id : ids) {
|
||||||
|
if (void* surface = game::TextureSurface(id)) {
|
||||||
|
surfaces.push_back(surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hud::SetHiddenTextures(surfaces.data(), static_cast<int>(surfaces.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShownAny() {
|
||||||
|
std::lock_guard guard(g_shownLock);
|
||||||
|
return !g_shown.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The game's logo top left (user, 2026-10-21: "the bright blueish one"),
|
||||||
|
// kLogoMargin from the edges: FX_SACREDLOGO.TGA, the emblem (horns around a
|
||||||
|
// ring of runes) glowing blue on black, no alpha, added onto the background as
|
||||||
|
// the game's effects draw it; the glow within u kLogoU0..U1 of the square
|
||||||
|
// texture, the full height (measured on a screenshot of it drawn whole).
|
||||||
|
constexpr char kLogoTexture[] = "FX_SACREDLOGO.TGA";
|
||||||
|
constexpr float kLogoU0 = 0.13f, kLogoU1 = 0.87f;
|
||||||
|
static_assert(kLogoWidth == static_cast<int>(kLogoHeight * (kLogoU1 - kLogoU0)));
|
||||||
|
|
||||||
|
void DrawLogo(void* device) {
|
||||||
|
if (void* logo = game::TextureSurface(game::TextureId(kLogoTexture))) {
|
||||||
|
const float edge = static_cast<float>(kLogoMargin);
|
||||||
|
game::DrawTexturedAdded(device, logo, edge, edge, edge + kLogoWidth, edge + kLogoHeight, kLogoU0, 0, kLogoU1,
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawRockWall(void* device, float width, float height) {
|
||||||
|
const float scale = std::max(width / (kBackgroundColumns * kBackgroundTile),
|
||||||
|
height / (kBackgroundRows * kBackgroundTile));
|
||||||
|
const float tile = kBackgroundTile * scale;
|
||||||
|
const float left = (width - kBackgroundColumns * tile) / 2;
|
||||||
|
const float top = (height - kBackgroundRows * tile) / 2;
|
||||||
|
for (int row = 0; row < kBackgroundRows; ++row) {
|
||||||
|
for (int column = 0; column < kBackgroundColumns; ++column) {
|
||||||
|
char name[40];
|
||||||
|
wsprintfA(name, "GUI_OPTION_BACKGROUND%d%d.TGA", row, column);
|
||||||
|
if (void* surface = game::TextureSurface(game::TextureId(name))) {
|
||||||
|
const float x = left + column * tile, y = top + row * tile;
|
||||||
|
game::DrawTextured(device, surface, x, y, x + tile, y + tile, 0, 0, 1, 1, kBackgroundTint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool AnyShown() {
|
||||||
|
return ShownAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawBackground(void* device) {
|
void DrawBackground(void* device) {
|
||||||
const float width = static_cast<float>(std::max(widescreen::RenderWidth(), kWidth));
|
const float width = static_cast<float>(std::max(widescreen::RenderWidth(), kWidth));
|
||||||
const float height = static_cast<float>(kHeight);
|
const float height = static_cast<float>(kHeight);
|
||||||
game::FillVertical(device, 0, 0, width, height, kBackgroundTop, kBackgroundBottom);
|
HidePictures();
|
||||||
|
game::FillRect(device, 0, 0, width, height, 0xff000000);
|
||||||
|
DrawRockWall(device, width, height);
|
||||||
|
game::FillVertical(device, 0, 0, width, height, kShadeTop, kShadeBottom);
|
||||||
game::FillHorizontal(device, 0, 0, kVignetteWidth, height, kVignette, 0);
|
game::FillHorizontal(device, 0, 0, kVignetteWidth, height, kVignette, 0);
|
||||||
game::FillHorizontal(device, width - kVignetteWidth, 0, width, height, 0, kVignette);
|
game::FillHorizontal(device, width - kVignetteWidth, 0, width, height, 0, kVignette);
|
||||||
|
DrawLogo(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int Render(void* window, void* device, int (*body)(void* context), void* context) {
|
int Render(void* window, void* device, int (*body)(void* context), void* context) {
|
||||||
const bool shown = game::ControlShown(window);
|
const bool shown = game::ControlShown(window);
|
||||||
NoteShown(window, shown);
|
NoteShown(window, shown);
|
||||||
@@ -151,4 +243,57 @@ bool DoubleClick::Click(int target) {
|
|||||||
return twice;
|
return twice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace mods::screen
|
} // namespace mods::screen
|
||||||
|
|
||||||
|
// The main menu (cUI_Manager's UI render 0x7587b0, thiscall (1 argument), ret
|
||||||
|
// 4, from the menu thread loop 0x756aa1 and the in-game render 0x60ea74): on
|
||||||
|
// the menu thread the background goes first, then the menu's 1024x768 frame
|
||||||
|
// is drawn centred over it (hud::RenderMenuFrame); a 16:9 screen draws its own.
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
uint32_t __fastcall UiManager_Render(void* self, void* edx, uint32_t argument);
|
||||||
|
RE_HOOK(0x007587b0, UiManager_Render);
|
||||||
|
uint32_t __fastcall UiManager_Render(void* self, void* edx, uint32_t argument) {
|
||||||
|
using namespace mods;
|
||||||
|
if (!widescreen::IsMenuThread() || widescreen::IsClassicFrame() || widescreen::RenderWidth() == 0) {
|
||||||
|
return UiManager_Render_original(self, edx, argument);
|
||||||
|
}
|
||||||
|
auto* driver = *reinterpret_cast<DxDriver7**>(addr::g_dxDriver);
|
||||||
|
void* device = driver ? driver->device : nullptr;
|
||||||
|
const bool framed = !screen::AnyShown();
|
||||||
|
if (framed && device) {
|
||||||
|
screen::DrawBackground(device);
|
||||||
|
}
|
||||||
|
struct Call {
|
||||||
|
void* self;
|
||||||
|
void* edx;
|
||||||
|
uint32_t argument;
|
||||||
|
uint32_t result;
|
||||||
|
} call{self, edx, argument, 0};
|
||||||
|
hud::RenderMenuFrame(
|
||||||
|
device, framed,
|
||||||
|
[](void* context) {
|
||||||
|
auto* c = static_cast<Call*>(context);
|
||||||
|
c->result = UiManager_Render_original(c->self, c->edx, c->argument);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
&call);
|
||||||
|
return call.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// cUI_MainMenu::Render (vf14 0x7122c0) draws a 3D scene (sky UI_UW_MAIN_CENTER,
|
||||||
|
// water UI_MAIN_REF; 0x75e620, thiscall (1 argument), ret 4, on the scene
|
||||||
|
// object 0x75df10) behind its picture: the call at 0x712bd9 goes nowhere.
|
||||||
|
constexpr uint32_t kMainMenuSceneCall = 0x00712bd9;
|
||||||
|
constexpr uint32_t kRenderMenuScene = 0x0075e620;
|
||||||
|
|
||||||
|
void __fastcall SkipMenuScene(void*, void*, uint32_t) {}
|
||||||
|
|
||||||
|
void RemoveMenuScene() {
|
||||||
|
re::RedirectCall(kMainMenuSceneCall, kRenderMenuScene, reinterpret_cast<const void*>(&SkipMenuScene));
|
||||||
|
}
|
||||||
|
RE_STARTUP(MenuSceneStartup, RemoveMenuScene);
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
+13
-2
@@ -33,6 +33,12 @@ constexpr int kMargin = kFrameOrnament + 16; // canvas edge to a panel
|
|||||||
constexpr int kPanelGap = 96; // between two panels' rects, as the options window's columns
|
constexpr int kPanelGap = 96; // between two panels' rects, as the options window's columns
|
||||||
constexpr int kPanelPadding = 16; // panel rect to its content
|
constexpr int kPanelPadding = 16; // panel rect to its content
|
||||||
|
|
||||||
|
// The logo in the background's top left corner (render target coordinates;
|
||||||
|
// screens may overlap it).
|
||||||
|
constexpr int kLogoMargin = 48;
|
||||||
|
constexpr int kLogoHeight = 124;
|
||||||
|
constexpr int kLogoWidth = kLogoHeight * 74 / 100; // the emblem's aspect (screen.cpp)
|
||||||
|
|
||||||
constexpr uint16_t kFont = 1;
|
constexpr uint16_t kFont = 1;
|
||||||
constexpr uint32_t kTextColour = 0xffe6d6aa; // as the options' texts
|
constexpr uint32_t kTextColour = 0xffe6d6aa; // as the options' texts
|
||||||
constexpr uint32_t kHeadingColour = 0xfff0c060; // as the options' headings
|
constexpr uint32_t kHeadingColour = 0xfff0c060; // as the options' headings
|
||||||
@@ -41,9 +47,14 @@ constexpr uint32_t kRowColour = 0x40302418;
|
|||||||
constexpr uint32_t kRowHoverColour = 0x80403020;
|
constexpr uint32_t kRowHoverColour = 0x80403020;
|
||||||
constexpr uint32_t kRowSelectedColour = 0xc0604818;
|
constexpr uint32_t kRowSelectedColour = 0xc0604818;
|
||||||
|
|
||||||
|
// The background of the menu screens across the whole render target (also
|
||||||
|
// beside the main menu's centred frame and full-screen windows in a game).
|
||||||
|
void DrawBackground(void* device);
|
||||||
|
// Whether a screen is shown (the main menu then does not draw centred).
|
||||||
|
bool AnyShown();
|
||||||
|
|
||||||
// Draws the screen of `window`: the background across the whole render
|
// Draws the screen of `window`: the background across the whole render
|
||||||
// target, then body(context) in canvas coordinates. While `window` is shown
|
// target, then body(context) in canvas coordinates.
|
||||||
// the main menu thread renders the full width (widescreen::SetWideMenuScreen).
|
|
||||||
int Render(void* window, void* device, int (*body)(void* context), void* context);
|
int Render(void* window, void* device, int (*body)(void* context), void* context);
|
||||||
// Runs body(context) with the event's mouse position in canvas coordinates.
|
// Runs body(context) with the event's mouse position in canvas coordinates.
|
||||||
int Handle(void* event, int (*body)(void* context), void* context);
|
int Handle(void* event, int (*body)(void* context), void* context);
|
||||||
|
|||||||
+14
-17
@@ -63,14 +63,14 @@ bool g_pushesPatched = false;
|
|||||||
int g_renderWidth = 0;
|
int g_renderWidth = 0;
|
||||||
int g_renderHeight = 0;
|
int g_renderHeight = 0;
|
||||||
|
|
||||||
// Frames from the main menu thread and the loading screen are laid out for
|
// The loading screen is laid out for 1024x768 ("classic"); the main menu
|
||||||
// 1024x768 ("classic"); everything else is the widened in-game view.
|
// thread renders the full width too (its windows drawn centred by
|
||||||
|
// mods/screen.cpp), the in-game view is widened.
|
||||||
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
|
// Whether the most recent scene (on any thread) was the main menu's or the
|
||||||
// menu is showing. UI input is processed outside the menu thread.
|
// loading screen's. UI input is processed outside the menu thread.
|
||||||
std::atomic<bool> g_lastSceneClassic = false;
|
std::atomic<bool> g_lastSceneMenu = false;
|
||||||
thread_local int g_loadingScreenDepth = 0;
|
thread_local int g_loadingScreenDepth = 0;
|
||||||
std::atomic<bool> g_wideMenuScreen = false;
|
|
||||||
|
|
||||||
// D3DVIEWPORT7 (d3d.h is not in current Windows SDKs).
|
// D3DVIEWPORT7 (d3d.h is not in current Windows SDKs).
|
||||||
struct Viewport7 {
|
struct Viewport7 {
|
||||||
@@ -340,18 +340,15 @@ int RenderWidth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MenuIsShowing() {
|
bool MenuIsShowing() {
|
||||||
return g_lastSceneClassic;
|
return g_lastSceneMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsClassicFrame() {
|
bool IsClassicFrame() {
|
||||||
if (g_renderWidth == 0) {
|
return g_renderWidth != 0 && g_loadingScreenDepth > 0;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return g_loadingScreenDepth > 0 || (GetCurrentThreadId() == g_menuThread.load() && !g_wideMenuScreen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetWideMenuScreen(bool shown) {
|
bool IsMenuThread() {
|
||||||
g_wideMenuScreen = shown;
|
return GetCurrentThreadId() == g_menuThread.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mods::widescreen
|
} // namespace mods::widescreen
|
||||||
@@ -376,13 +373,13 @@ void __fastcall DxDriver7_LoadingScreen(DxDriver7* self, void* edx, uint32_t pro
|
|||||||
--g_loadingScreenDepth;
|
--g_loadingScreenDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Menus draw their 3D parts for a 1024x768 screen: give them a 1024x768
|
// The loading screen draws for a 1024x768 screen: give it a 1024x768
|
||||||
// viewport (the borderless mod then presents just that area, scaled), and the
|
// viewport (the borderless mod then presents just that area, scaled), and
|
||||||
// world the full render size.
|
// everything else 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) {
|
if (self->sceneDepth == 0) {
|
||||||
g_lastSceneClassic = IsClassicFrame();
|
g_lastSceneMenu = IsClassicFrame() || IsMenuThread();
|
||||||
}
|
}
|
||||||
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();
|
||||||
|
|||||||
@@ -11,20 +11,18 @@ namespace mods::widescreen {
|
|||||||
// the world view to match. No-op when disabled.
|
// the world view to match. No-op when disabled.
|
||||||
void ApplyRenderSize(game::DxDisplayMode& mode, HWND window);
|
void ApplyRenderSize(game::DxDisplayMode& mode, HWND window);
|
||||||
|
|
||||||
// True while the current thread renders a frame laid out for 1024x768 (main
|
// True while the current thread renders a frame laid out for 1024x768 (the
|
||||||
// menu, loading screen); the borderless mod then presents only that area.
|
// loading screen); the borderless mod then presents only that area.
|
||||||
bool IsClassicFrame();
|
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,
|
// True while the main menu or the loading screen is on screen, regardless of
|
||||||
// regardless of the calling thread.
|
// the calling thread.
|
||||||
bool MenuIsShowing();
|
bool MenuIsShowing();
|
||||||
|
|
||||||
// While a 16:9 menu screen (mods/screen.h) is shown from the main menu, the
|
// True on the main menu's render thread (cUI_Manager's thread loop).
|
||||||
// menu thread renders and presents the full render size instead of the
|
bool IsMenuThread();
|
||||||
// classic 1024x768.
|
|
||||||
void SetWideMenuScreen(bool shown);
|
|
||||||
|
|
||||||
} // namespace mods::widescreen
|
} // namespace mods::widescreen
|
||||||
@@ -351,3 +351,23 @@
|
|||||||
0x00711fa0 cUI_MainMenu::HandleEvent # vf10: sets g_campaign 0x182cdc8 by entry name (NEWGAME 0, NEWADDON 1)
|
0x00711fa0 cUI_MainMenu::HandleEvent # vf10: sets g_campaign 0x182cdc8 by entry name (NEWGAME 0, NEWADDON 1)
|
||||||
0x00753d70 cUI_StaticText64FX::cUI_StaticText64FX # (name, rect, parent, ?, text id, command, flags)
|
0x00753d70 cUI_StaticText64FX::cUI_StaticText64FX # (name, rect, parent, ?, text id, command, flags)
|
||||||
0x0070e2a0 cUI_Character::Show # vf24 (shown), ret 4: single player + g_campaign 1 -> mode 7 (Underworld import, level 25+)
|
0x0070e2a0 cUI_Character::Show # vf24 (shown), ret 4: single player + g_campaign 1 -> mode 7 (Underworld import, level 25+)
|
||||||
|
|
||||||
|
# cUI_NetGranny: 3D hero view (game/ui/granny.h)
|
||||||
|
0x006f06f0 cUI_NetGranny::cUI_NetGranny # (rect by value), ret 0xc
|
||||||
|
0x006f17f0 cUI_NetGranny::Render # vf44 (device, 0, label), ret 0xc
|
||||||
|
0x006f4b20 cUI_NetGranny::SetHero # (hero info, x, y, z, turn), ret 0x14
|
||||||
|
0x006f4ae0 cUI_NetGranny::ClearHero
|
||||||
|
0x006f0f10 cUI_NetGranny::SetLights # (on): +0x53f white set
|
||||||
|
0x006f4d70 cUI_NetGranny::InitCamera
|
||||||
|
0x0070beb0 cUI_Character::LoadExportedHeroes # Hero00..07.pax -> slot grannies +0x1b4
|
||||||
|
|
||||||
|
# Textures by name (game/ui/texture.h)
|
||||||
|
0x00656c80 TextureManager_Instance # cdecl (), kept at 0x13e57b4
|
||||||
|
0x00657620 TextureManager::IdOf # (name), ret 4
|
||||||
|
0x0065f010 TextureCache::Get # (id, 0), ret 8; cache at 0x13e57b8, D3D surface +0x14
|
||||||
|
|
||||||
|
# Main menu (mods/screen.cpp)
|
||||||
|
0x007122c0 cUI_MainMenu::Render # vf14
|
||||||
|
0x0075df10 MenuScene_Instance # 0x1a84 bytes, kept at 0x17ecb40
|
||||||
|
0x0075e620 MenuScene::Render # (1 argument), ret 4: sky + water (UI_UW_MAIN_CENTER, UI_MAIN_REF)
|
||||||
|
0x0075e820 MenuScene::Update # (seconds)
|
||||||
Reference in new issue
Block a user