зум при создании персонажа
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user