зум при создании персонажа

This commit is contained in:
2026-07-18 16:24:02 +04:00
parent cddaf2c515
commit 698de84762
2 changed files with 163 additions and 3 deletions
+117
View File
@@ -9,6 +9,7 @@
#include "core/Mem.hpp" #include "core/Mem.hpp"
#include "runtime/LuaBindings.hpp" #include "runtime/LuaBindings.hpp"
#include "runtime/ModuleInstall.hpp" #include "runtime/ModuleInstall.hpp"
#include "offsets/engine/Gx.hpp"
#include <windows.h> #include <windows.h>
@@ -24,6 +25,118 @@ namespace moonwell
constexpr uint32_t kTraitorFlag = 0x40000000u; constexpr uint32_t kTraitorFlag = 0x40000000u;
bool g_loginCharacterIsTraitor = false; bool g_loginCharacterIsTraitor = false;
using GxSetProjectionFn = wxl::offsets::engine::gx::GxSetProjectionFn;
GxSetProjectionFn g_nextSetProjection = nullptr;
struct CameraTransition
{
float zoom = 1.0f;
float verticalOffset = 0.0f;
float startZoom = 1.0f;
float startVerticalOffset = 0.0f;
float targetZoom = 1.0f;
float targetVerticalOffset = 0.0f;
DWORD startedAt = 0;
DWORD duration = 0;
} g_characterCreateCamera;
void UpdateCharacterCreateCamera(DWORD now)
{
auto& camera = g_characterCreateCamera;
if (!camera.duration)
{
camera.zoom = camera.targetZoom;
camera.verticalOffset = camera.targetVerticalOffset;
return;
}
const DWORD elapsed = now - camera.startedAt;
float t = static_cast<float>(elapsed) / static_cast<float>(camera.duration);
if (t >= 1.0f)
{
camera.zoom = camera.targetZoom;
camera.verticalOffset = camera.targetVerticalOffset;
camera.duration = 0;
return;
}
// Smoothstep avoids a visible jerk at both ends of the move.
t = t * t * (3.0f - 2.0f * t);
camera.zoom = camera.startZoom + (camera.targetZoom - camera.startZoom) * t;
camera.verticalOffset = camera.startVerticalOffset
+ (camera.targetVerticalOffset - camera.startVerticalOffset) * t;
}
int __cdecl SetCharacterCreateCamera(void* state)
{
const bool enabled = state && wxl::runtime::lua::IsNumber(state, 1)
&& wxl::runtime::lua::ToNumber(state, 1) != 0.0;
const float faceZoom = state && wxl::runtime::lua::IsNumber(state, 2)
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 2)) : 2.0f;
const float faceVerticalOffset = state && wxl::runtime::lua::IsNumber(state, 3)
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 3)) : -0.65f;
double requestedDuration = state && wxl::runtime::lua::IsNumber(state, 4)
? wxl::runtime::lua::ToNumber(state, 4) : 500.0;
if (requestedDuration < 0.0) requestedDuration = 0.0;
if (requestedDuration > 2000.0) requestedDuration = 2000.0;
const DWORD now = GetTickCount();
UpdateCharacterCreateCamera(now);
auto& camera = g_characterCreateCamera;
camera.startZoom = camera.zoom;
camera.startVerticalOffset = camera.verticalOffset;
camera.targetZoom = enabled && faceZoom > 1.0f ? faceZoom : 1.0f;
camera.targetVerticalOffset = enabled ? faceVerticalOffset : 0.0f;
camera.startedAt = now;
camera.duration = static_cast<DWORD>(requestedDuration);
UpdateCharacterCreateCamera(now);
return 0;
}
void __fastcall CharacterCreateProjectionHook(void* self, void* edx, const void* projection)
{
if (!g_nextSetProjection)
return;
UpdateCharacterCreateCamera(GetTickCount());
const auto& camera = g_characterCreateCamera;
if (projection && (camera.zoom != 1.0f || camera.verticalOffset != 0.0f))
{
const float* source = static_cast<const float*>(projection);
if (source[11] > 0.5f)
{
float adjusted[16];
std::memcpy(adjusted, source, sizeof(adjusted));
adjusted[0] *= camera.zoom;
adjusted[5] *= camera.zoom;
adjusted[9] += camera.verticalOffset;
g_nextSetProjection(self, edx, adjusted);
return;
}
}
g_nextSetProjection(self, edx, projection);
}
void InstallCharacterCreateCamera()
{
namespace gx = wxl::offsets::engine::gx;
void** vtable = reinterpret_cast<void**>(gx::kGxDeviceVTable);
void** slot = &vtable[gx::kGxSetProjectionSlot];
if (*slot == reinterpret_cast<void*>(&CharacterCreateProjectionHook))
return;
g_nextSetProjection = reinterpret_cast<GxSetProjectionFn>(*slot);
void* replacement = reinterpret_cast<void*>(&CharacterCreateProjectionHook);
if (!g_nextSetProjection || !wxl::core::mem::Patch(slot, &replacement, sizeof(replacement)))
{
g_nextSetProjection = nullptr;
WLOG_ERROR("moonwell: character-create projection hook installation failed");
return;
}
WLOG_INFO("moonwell: smooth character-create camera installed");
}
int __cdecl SetLoginCharacterFlags(void* state) int __cdecl SetLoginCharacterFlags(void* state)
{ {
uint32_t flags = 0; uint32_t flags = 0;
@@ -220,7 +333,11 @@ namespace moonwell
wxl::runtime::lua::RegisterFunction( wxl::runtime::lua::RegisterFunction(
"MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags); "MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags);
wxl::runtime::lua::RegisterFunction("MoonWellIsTraitor", &IsTraitor); wxl::runtime::lua::RegisterFunction("MoonWellIsTraitor", &IsTraitor);
wxl::runtime::lua::RegisterFunction(
"MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
wxl::runtime::modules::RegisterBoot("moonwell", &InstallBoot); wxl::runtime::modules::RegisterBoot("moonwell", &InstallBoot);
wxl::runtime::modules::Register(
"moonwell-character-create-camera", &InstallCharacterCreateCamera);
wxl::runtime::modules::Register("moonwell-log-flush", &InstallRuntimeLogFlush); wxl::runtime::modules::Register("moonwell-log-flush", &InstallRuntimeLogFlush);
} }
} g_registration; } g_registration;
@@ -676,6 +676,44 @@ function CharacterCreate_UpdateGameModeVisibility()
end end
end end
-- The stock creation scene frames each race at a different height. Projection
-- zoom therefore needs a per-race vertical target to keep the face in the same
-- part of the screen. Values are indexed by GetSelectedSex(): 2 male, 3 female.
local CHARACTER_CREATE_CAMERA_PROFILES = {
HUMAN = { [2] = -0.28, [3] = -0.20 },
ORC = { [2] = -0.50, [3] = -0.30 },
DWARF = { [2] = 0.30, [3] = 0.38 },
NIGHTELF = { [2] = -0.65, [3] = -0.65 },
SCOURGE = { [2] = 0.18, [3] = 0.27 },
TAUREN = { [2] = -0.78, [3] = -0.62 },
GNOME = { [2] = 0.64, [3] = 0.72 },
TROLL = { [2] = -0.70, [3] = -0.55 },
BLOODELF = { [2] = -0.30, [3] = -0.22 },
DRAENEI = { [2] = -0.62, [3] = -0.55 },
};
local function CharacterCreate_GetCameraProfile()
local _, raceFile = GetNameForRace();
local raceProfile = raceFile and CHARACTER_CREATE_CAMERA_PROFILES[strupper(raceFile)];
local sex = GetSelectedSex();
return 2.0, raceProfile and raceProfile[sex] or -0.20;
end
local function CharacterCreate_UpdateCamera(immediate)
local showFace = CharacterCreate.personalizationMode and not CharacterCreate.gameModeSelectionMode;
local mode = showFace and 1 or 0;
if CharacterCreate.cameraMode == mode and not immediate then
return;
end
CharacterCreate.cameraMode = mode;
CharacterCreate:SetCamera(0);
if MoonWellSetCharacterCreateCamera then
local zoom, verticalOffset = CharacterCreate_GetCameraProfile();
MoonWellSetCharacterCreateCamera(mode, zoom, verticalOffset, immediate and 0 or 500);
end
end
local function CharacterCreate_GetEnteredName() local function CharacterCreate_GetEnteredName()
local text = ""; local text = "";
@@ -701,6 +739,8 @@ local function CharacterCreate_UpdatePersonalizationStep()
CharacterCreate.gameModeSelectionMode = false; CharacterCreate.gameModeSelectionMode = false;
end end
CharacterCreate_UpdateCamera();
local showGameModes = CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE; local showGameModes = CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE;
if showGameModes then if showGameModes then
@@ -943,6 +983,7 @@ function CharacterCreate_TogglePersonalization()
CharCreatePersonalizeButton:Show(); CharCreatePersonalizeButton:Show();
CharCreateOkayButton:Hide(); CharCreateOkayButton:Hide();
CharacterCreate_UpdateGameModeVisibility(); CharacterCreate_UpdateGameModeVisibility();
CharacterCreate_UpdateCamera();
for i=1, NUM_CHAR_CUSTOMIZATIONS do for i=1, NUM_CHAR_CUSTOMIZATIONS do
_G["CharacterCustomizationButtonFrame"..i]:Hide(); _G["CharacterCustomizationButtonFrame"..i]:Hide();
@@ -1001,7 +1042,7 @@ function CharacterCreate_OnShow()
CharacterChangeFixup(); CharacterChangeFixup();
CharacterCreate_CreateGenderButtonTextures(); CharacterCreate_CreateGenderButtonTextures();
CharacterCreate_UpdateButtonCheckedStates(); CharacterCreate_UpdateButtonCheckedStates();
CharacterCreate_ResetState(); CharacterCreate_ResetState(true);
hideScheduled = true hideScheduled = true
HideNameEditFrame:Show() HideNameEditFrame:Show()
@@ -1013,7 +1054,7 @@ end
function CharacterCreate_OnHide() function CharacterCreate_OnHide()
PAID_SERVICE_CHARACTER_ID = nil; PAID_SERVICE_CHARACTER_ID = nil;
PAID_SERVICE_TYPE = nil; PAID_SERVICE_TYPE = nil;
CharacterCreate_ResetState(); CharacterCreate_ResetState(true);
for button, tooltip in pairs(raceTooltips) do for button, tooltip in pairs(raceTooltips) do
if tooltip then tooltip:Hide() end if tooltip then tooltip:Hide() end
@@ -1518,6 +1559,7 @@ end
function CharacterCreate_UpdateModel(self) function CharacterCreate_UpdateModel(self)
UpdateCustomizationScene(); UpdateCustomizationScene();
CharacterCreate_UpdateCamera();
self:AdvanceTime(); self:AdvanceTime();
end end
@@ -1644,9 +1686,10 @@ function SetCharacterGender(sex)
CharacterCreate_UpdateButtonCheckedStates(); CharacterCreate_UpdateButtonCheckedStates();
end end
function CharacterCreate_ResetState() function CharacterCreate_ResetState(immediate)
CharacterCreate.personalizationMode = false; CharacterCreate.personalizationMode = false;
CharacterCreate.gameModeSelectionMode = false; CharacterCreate.gameModeSelectionMode = false;
CharacterCreate_UpdateCamera(immediate);
CharacterCreateRaceButtonsContainer:Show(); CharacterCreateRaceButtonsContainer:Show();
CharacterCreateClassButtonsContainer:Show(); CharacterCreateClassButtonsContainer:Show();
CharacterCreateGenderButtonsContainer:Show(); CharacterCreateGenderButtonsContainer:Show();