diff --git a/modules/moonwell/src/MoonWell.cpp b/modules/moonwell/src/MoonWell.cpp index cf01755..f8d2e86 100644 --- a/modules/moonwell/src/MoonWell.cpp +++ b/modules/moonwell/src/MoonWell.cpp @@ -9,6 +9,7 @@ #include "core/Mem.hpp" #include "runtime/LuaBindings.hpp" #include "runtime/ModuleInstall.hpp" +#include "offsets/engine/Gx.hpp" #include @@ -24,6 +25,118 @@ namespace moonwell constexpr uint32_t kTraitorFlag = 0x40000000u; 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(elapsed) / static_cast(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(wxl::runtime::lua::ToNumber(state, 2)) : 2.0f; + const float faceVerticalOffset = state && wxl::runtime::lua::IsNumber(state, 3) + ? static_cast(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(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(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(gx::kGxDeviceVTable); + void** slot = &vtable[gx::kGxSetProjectionSlot]; + if (*slot == reinterpret_cast(&CharacterCreateProjectionHook)) + return; + + g_nextSetProjection = reinterpret_cast(*slot); + void* replacement = reinterpret_cast(&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) { uint32_t flags = 0; @@ -220,7 +333,11 @@ namespace moonwell wxl::runtime::lua::RegisterFunction( "MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags); wxl::runtime::lua::RegisterFunction("MoonWellIsTraitor", &IsTraitor); + wxl::runtime::lua::RegisterFunction( + "MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera); wxl::runtime::modules::RegisterBoot("moonwell", &InstallBoot); + wxl::runtime::modules::Register( + "moonwell-character-create-camera", &InstallCharacterCreateCamera); wxl::runtime::modules::Register("moonwell-log-flush", &InstallRuntimeLogFlush); } } g_registration; diff --git a/src/Data/ruRU/patch-ruRU-4/Interface/GlueXML/CharacterCreate.lua b/src/Data/ruRU/patch-ruRU-4/Interface/GlueXML/CharacterCreate.lua index ee77ae1..e96a332 100644 --- a/src/Data/ruRU/patch-ruRU-4/Interface/GlueXML/CharacterCreate.lua +++ b/src/Data/ruRU/patch-ruRU-4/Interface/GlueXML/CharacterCreate.lua @@ -676,6 +676,44 @@ function CharacterCreate_UpdateGameModeVisibility() 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 text = ""; @@ -701,6 +739,8 @@ local function CharacterCreate_UpdatePersonalizationStep() CharacterCreate.gameModeSelectionMode = false; end + CharacterCreate_UpdateCamera(); + local showGameModes = CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE; if showGameModes then @@ -943,6 +983,7 @@ function CharacterCreate_TogglePersonalization() CharCreatePersonalizeButton:Show(); CharCreateOkayButton:Hide(); CharacterCreate_UpdateGameModeVisibility(); + CharacterCreate_UpdateCamera(); for i=1, NUM_CHAR_CUSTOMIZATIONS do _G["CharacterCustomizationButtonFrame"..i]:Hide(); @@ -1001,7 +1042,7 @@ function CharacterCreate_OnShow() CharacterChangeFixup(); CharacterCreate_CreateGenderButtonTextures(); CharacterCreate_UpdateButtonCheckedStates(); - CharacterCreate_ResetState(); + CharacterCreate_ResetState(true); hideScheduled = true HideNameEditFrame:Show() @@ -1013,7 +1054,7 @@ end function CharacterCreate_OnHide() PAID_SERVICE_CHARACTER_ID = nil; PAID_SERVICE_TYPE = nil; - CharacterCreate_ResetState(); + CharacterCreate_ResetState(true); for button, tooltip in pairs(raceTooltips) do if tooltip then tooltip:Hide() end @@ -1518,6 +1559,7 @@ end function CharacterCreate_UpdateModel(self) UpdateCustomizationScene(); + CharacterCreate_UpdateCamera(); self:AdvanceTime(); end @@ -1644,9 +1686,10 @@ function SetCharacterGender(sex) CharacterCreate_UpdateButtonCheckedStates(); end -function CharacterCreate_ResetState() +function CharacterCreate_ResetState(immediate) CharacterCreate.personalizationMode = false; CharacterCreate.gameModeSelectionMode = false; + CharacterCreate_UpdateCamera(immediate); CharacterCreateRaceButtonsContainer:Show(); CharacterCreateClassButtonsContainer:Show(); CharacterCreateGenderButtonsContainer:Show();