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
+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
|
||||
Reference in new issue
Block a user