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