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:
khallaandClaude Opus 5.5 committed 2026-10-21 00:05:55 +02:00
1 parent 34a119433a
commit db87e276bf
16 files changed
+620 -69

No files matched your search

+156 -11
View File
@@ -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