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
@@ -10,6 +10,12 @@ namespace game {
|
||||
|
||||
constexpr int kDeviceDrawPrimitive = 25; // IDirect3DDevice7 vtable slots
|
||||
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 kFvfScreenColourTexture = 0x1c4; // XYZRHW | DIFFUSE | SPECULAR | TEX1
|
||||
|
||||
@@ -19,6 +25,45 @@ struct ScreenVertex {
|
||||
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,
|
||||
// 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,
|
||||
|
||||
@@ -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;
|
||||
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.x -= mods::hud::MenuFrameOffsetX(); // the main menu's centred frame
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+70
-3
@@ -49,6 +49,7 @@
|
||||
#include "game/ui/button.h"
|
||||
#include "game/ui/control.h"
|
||||
#include "game/ui/events.h"
|
||||
#include "game/ui/granny.h"
|
||||
#include "game/ui/text.h"
|
||||
#include "game/ui/texts.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)
|
||||
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) {
|
||||
return Convert(FromUtf8(meta.value("name", std::string())) + L" - " + ClassName(meta.value("class", 0u)),
|
||||
CP_ACP);
|
||||
@@ -417,6 +420,7 @@ void RebuildList() {
|
||||
DeleteFileA((work + file).c_str());
|
||||
}
|
||||
g_rows.clear();
|
||||
ForgetHeroModel();
|
||||
for (const std::string& name : FilesIn(CharactersDir(), "*.sacredK")) {
|
||||
if (EndsWith(name, kAutosaveExtension) || g_rows.size() >= kMaxRows) {
|
||||
continue;
|
||||
@@ -616,8 +620,10 @@ std::atomic<int> g_loadRow{-1};
|
||||
|
||||
// Layout on the canvas (1365x768): three panels side by side (sizes in the
|
||||
// frames' 64 steps, screen::kFrameStep), centred, a row of buttons below.
|
||||
constexpr int kPanelTop = 96; // the title above the frames' ornament
|
||||
constexpr int kPanelHeight = 8 * screen::kFrameStep;
|
||||
// Top: room for the heading and the frames' ornament; the buttons end
|
||||
// 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 kHeroWidth = 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) {
|
||||
const game::UiRect content = g_heroPanel.Draw(device, kHeroRect);
|
||||
const Row* row = SelectedCharacter();
|
||||
@@ -1348,6 +1412,9 @@ void DrawHero(void* device) {
|
||||
line(CampaignName(last->campaign) + L" · " + DifficultyName(last->difficulty), screen::kTextColour);
|
||||
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) {
|
||||
@@ -1375,7 +1442,7 @@ void DrawCell(void* device, const Row& row, int campaign, int difficulty) {
|
||||
if (cell.autosave && cell.world) {
|
||||
screen::Text(device, bottom, L"+ Autosave", screen::kHeadingColour, game::kTextCenter);
|
||||
} 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
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
+14
-2
@@ -26,8 +26,8 @@ int VisibleDialogTooltips(void** out, int max);
|
||||
// shifted) or event handler (mouse position shifted by the window's offset).
|
||||
int RenderCentred(void* window, 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
|
||||
// with a viewport on it (as the save game and export screens).
|
||||
// RenderCentred for full-screen windows: the menu screens' background beside
|
||||
// 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);
|
||||
|
||||
// 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 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
|
||||
+156
-11
@@ -6,25 +6,38 @@
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "game/addresses.h"
|
||||
#include "game/render/dx_driver7.h"
|
||||
#include "game/ui/button.h"
|
||||
#include "game/ui/control.h"
|
||||
#include "game/ui/draw.h"
|
||||
#include "game/ui/events.h"
|
||||
#include "game/ui/frame.h"
|
||||
#include "game/ui/texture.h"
|
||||
#include "game/ui/texts.h"
|
||||
#include "mods/widescreen.h"
|
||||
#include "re/hook.h"
|
||||
#include "re/patch.h"
|
||||
|
||||
namespace mods::screen {
|
||||
namespace {
|
||||
|
||||
// Background (decided 2026-10-20: our own, no picture): dark, a little warm
|
||||
// at the top, darker towards the bottom and the sides.
|
||||
constexpr uint32_t kBackgroundTop = 0xff1c1712;
|
||||
constexpr uint32_t kBackgroundBottom = 0xff070605;
|
||||
constexpr uint32_t kVignette = 0xa0000000;
|
||||
// Background (user, 2026-10-20: "textured dark", then "doesn't need to be that
|
||||
// dark, but apply it to all screens"): the options window's rock wall,
|
||||
// GUI_OPTION_BACKGROUND<row><column>.TGA (4 x 3 tiles of 256, a 1024x768
|
||||
// picture), scaled to cover the render target, a little darkened, darker
|
||||
// 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 uint32_t kPanelBackdrop = 0xb4000000;
|
||||
constexpr int kHeadingHeight = 30;
|
||||
@@ -33,8 +46,8 @@ constexpr int kHeadingHeight = 30;
|
||||
// frames).
|
||||
std::atomic<unsigned> g_frame{1};
|
||||
|
||||
// The windows whose screen is shown: the menu thread renders the full width
|
||||
// while any is.
|
||||
// The windows whose screen is shown: the main menu's frame is not drawn
|
||||
// centred while any is (the screen draws itself across the width).
|
||||
std::mutex g_shownLock;
|
||||
std::set<const void*> g_shown;
|
||||
|
||||
@@ -45,19 +58,98 @@ void NoteShown(const void* window, bool shown) {
|
||||
} else {
|
||||
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) {
|
||||
const float width = static_cast<float>(std::max(widescreen::RenderWidth(), kWidth));
|
||||
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, width - kVignetteWidth, 0, width, height, 0, kVignette);
|
||||
DrawLogo(device);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int Render(void* window, void* device, int (*body)(void* context), void* context) {
|
||||
const bool shown = game::ControlShown(window);
|
||||
NoteShown(window, shown);
|
||||
@@ -151,4 +243,57 @@ bool DoubleClick::Click(int target) {
|
||||
return twice;
|
||||
}
|
||||
|
||||
|
||||
} // 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 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 uint32_t kTextColour = 0xffe6d6aa; // as the options' texts
|
||||
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 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
|
||||
// target, then body(context) in canvas coordinates. While `window` is shown
|
||||
// the main menu thread renders the full width (widescreen::SetWideMenuScreen).
|
||||
// target, then body(context) in canvas coordinates.
|
||||
int Render(void* window, void* device, int (*body)(void* context), void* context);
|
||||
// Runs body(context) with the event's mouse position in canvas coordinates.
|
||||
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_renderHeight = 0;
|
||||
|
||||
// Frames from the main menu thread and the loading screen are laid out for
|
||||
// 1024x768 ("classic"); everything else is the widened in-game view.
|
||||
// The loading screen is laid out for 1024x768 ("classic"); the main menu
|
||||
// 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;
|
||||
// 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;
|
||||
// Whether the most recent scene (on any thread) was the main menu's or the
|
||||
// loading screen's. UI input is processed outside the menu thread.
|
||||
std::atomic<bool> g_lastSceneMenu = false;
|
||||
thread_local int g_loadingScreenDepth = 0;
|
||||
std::atomic<bool> g_wideMenuScreen = false;
|
||||
|
||||
// D3DVIEWPORT7 (d3d.h is not in current Windows SDKs).
|
||||
struct Viewport7 {
|
||||
@@ -340,18 +340,15 @@ int RenderWidth() {
|
||||
}
|
||||
|
||||
bool MenuIsShowing() {
|
||||
return g_lastSceneClassic;
|
||||
return g_lastSceneMenu;
|
||||
}
|
||||
|
||||
bool IsClassicFrame() {
|
||||
if (g_renderWidth == 0) {
|
||||
return false;
|
||||
}
|
||||
return g_loadingScreenDepth > 0 || (GetCurrentThreadId() == g_menuThread.load() && !g_wideMenuScreen);
|
||||
return g_renderWidth != 0 && g_loadingScreenDepth > 0;
|
||||
}
|
||||
|
||||
void SetWideMenuScreen(bool shown) {
|
||||
g_wideMenuScreen = shown;
|
||||
bool IsMenuThread() {
|
||||
return GetCurrentThreadId() == g_menuThread.load();
|
||||
}
|
||||
|
||||
} // namespace mods::widescreen
|
||||
@@ -376,13 +373,13 @@ void __fastcall DxDriver7_LoadingScreen(DxDriver7* self, void* edx, uint32_t pro
|
||||
--g_loadingScreenDepth;
|
||||
}
|
||||
|
||||
// Menus draw their 3D parts for a 1024x768 screen: give them a 1024x768
|
||||
// viewport (the borderless mod then presents just that area, scaled), and the
|
||||
// world the full render size.
|
||||
// The loading screen draws for a 1024x768 screen: give it a 1024x768
|
||||
// viewport (the borderless mod then presents just that area, scaled), and
|
||||
// everything else the full render size.
|
||||
int __fastcall DxDriver7_BeginScene(DxDriver7* self, void* edx) {
|
||||
mods::hud::OnDevice(self->device);
|
||||
if (self->sceneDepth == 0) {
|
||||
g_lastSceneClassic = IsClassicFrame();
|
||||
g_lastSceneMenu = IsClassicFrame() || IsMenuThread();
|
||||
}
|
||||
if (g_renderWidth != 0 && self->sceneDepth == 0 && self->device) {
|
||||
const bool classic = IsClassicFrame();
|
||||
|
||||
@@ -11,20 +11,18 @@ namespace mods::widescreen {
|
||||
// the world view to match. No-op when disabled.
|
||||
void ApplyRenderSize(game::DxDisplayMode& mode, HWND window);
|
||||
|
||||
// True while the current thread renders a frame laid out for 1024x768 (main
|
||||
// menu, loading screen); the borderless mod then presents only that area.
|
||||
// True while the current thread renders a frame laid out for 1024x768 (the
|
||||
// loading screen); the borderless mod then presents only that area.
|
||||
bool IsClassicFrame();
|
||||
|
||||
// Applied render width (0 while the original 1024x768 is in use).
|
||||
int RenderWidth();
|
||||
|
||||
// True while the main menu / loading screen (classic layout) is on screen,
|
||||
// regardless of the calling thread.
|
||||
// True while the main menu or the loading screen is on screen, regardless of
|
||||
// the calling thread.
|
||||
bool MenuIsShowing();
|
||||
|
||||
// While a 16:9 menu screen (mods/screen.h) is shown from the main menu, the
|
||||
// menu thread renders and presents the full render size instead of the
|
||||
// classic 1024x768.
|
||||
void SetWideMenuScreen(bool shown);
|
||||
// True on the main menu's render thread (cUI_Manager's thread loop).
|
||||
bool IsMenuThread();
|
||||
|
||||
} // namespace mods::widescreen
|
||||
Reference in new issue
Block a user