From d5970c262b304341fee7b6f8b65c22eacaeefb3c Mon Sep 17 00:00:00 2001 From: ragnarok-clanker Date: Thu, 29 Oct 2026 21:33:16 +0000 Subject: [PATCH] Decompile 6 functions in Source/Contents/Actors/RenderObject.cpp, Source/Resource/Models/ModelResource.cpp, ... Batch #3345 by eAthena: 0 byte-identical, 6 confirmed by peer review. - fn_21193 CGameActor::ProcessHitMessage: logic confirmed by peer review - fn_21180 C3dGrannyGameActor::Slot08: logic confirmed by peer review - fn_08025 C3dGrannyModelRes::GetMeshRenderData: logic confirmed by peer review - fn_08026 C3dGrannyModelRes::GetMeshRenderData: logic confirmed by peer review - fn_08027 C3dGrannyModelRes::FindHairPivot: logic confirmed by peer review - fn_08029 C3dGrannyModelRes::Animate: logic confirmed by peer review Peer-reviewed-by: meitogi, youko Accepted without human review: peers confirmed the logic ("same" ahead by at least 3, with 70% of the votes). --- Source/Contents/Actors/GameActor.h | 45 ++- Source/Contents/Actors/RenderObject.cpp | 356 ++++++++++++++++++- Source/Contents/Actors/RenderObjectState.h | 2 +- Source/Resource/Models/GrannyModelResource.h | 60 +++- Source/Resource/Models/ModelResource.cpp | 131 +++++++ 5 files changed, 585 insertions(+), 9 deletions(-) diff --git a/Source/Contents/Actors/GameActor.h b/Source/Contents/Actors/GameActor.h index 3ef5032a..e47a9400 100644 --- a/Source/Contents/Actors/GameActor.h +++ b/Source/Contents/Actors/GameActor.h @@ -18,14 +18,50 @@ enum { BODYSTATE_STONECURSE_ING, }; +struct ActorHitInfo { + /* 0000 */ int unknown_0000; + /* 0004 */ int damage; + /* 0008 */ int extraDamage; + /* 000C */ int type; + /* 0010 */ std::uint32_t targetAid; + /* 0014 */ int unknown_0014; + /* 0018 */ int hitCount; + /* 001C */ int unknown_001C; + /* 0020 */ int unknown_0020; + /* 0024 */ int attackSpeed; + /* 0028 */ int motionSpeed; + /* 002C */ unsigned char unknown_002C; +}; + +struct ActorDamageMessage { + /* 0000 */ std::uint32_t targetAid; + /* 0004 */ int action; + /* 0008 */ int damage; + /* 000C */ std::uint32_t time; + /* 0010 */ std::uint32_t attackerAid; + /* 0014 */ int unknown_0014; + /* 0018 */ int unknown_0018; + /* 001C */ int unknown_001C; + /* 0020 */ int effectId; + /* 0024 */ int unknown_0024; + /* 0028 */ float motionSpeed; + /* 002C */ unsigned char unknown_002C; + /* 0030 */ int unknown_0030; + /* 0034 */ int unknown_0034; +}; + class CGameActor : public EffectActor { public: ~CGameActor() override; - /* 0158 */ unsigned char unknown_0158[0xC8]; + /* 0158 */ unsigned char unknown_0158[0x20]; + /* 0178 */ int unknown_0178; + /* 017C */ unsigned char unknown_017C[0xA4]; /* 0220 */ MovementRouteInterpolation route; /* 0238 */ unsigned char unknown_0238; - /* 0239 */ unsigned char unknown_0239[0x1B]; + /* 0239 */ unsigned char unknown_0239[0x13]; + /* 024C */ std::uint32_t hitTargetAid; + /* 0250 */ unsigned char unknown_0250[0x04]; /* 0254 */ std::uint32_t routeStartTick; /* 0258 */ unsigned char unknown_0258[0x04]; /* 025C */ int unknown_025C; @@ -146,15 +182,20 @@ public: int WeaponActionBase(int weaponType); int GetUnknown0404(); int MountHeightOffset(); + void ProcessHitMessage(CGameObject* sender, const ActorHitInfo* info, int unused1, int unused2); }; int ShapeShiftJob(int form, int job); extern const int g_maxMoveSpeed; +extern int g_multiHitInterval; #if defined(_WIN32) && !defined(_WIN64) static_assert(sizeof(CGameActor) == 0x410); static_assert(offsetof(CGameActor, route) == 0x220); +static_assert(offsetof(CGameActor, unknown_0178) == 0x178); +static_assert(offsetof(CGameActor, hitTargetAid) == 0x24C); +static_assert(sizeof(ActorDamageMessage) == 0x38); static_assert(offsetof(CGameActor, unknown_0238) == 0x238); static_assert(offsetof(CGameActor, routeStartTick) == 0x254); static_assert(offsetof(CGameActor, unknown_0260) == 0x260); diff --git a/Source/Contents/Actors/RenderObject.cpp b/Source/Contents/Actors/RenderObject.cpp index 760f2d0e..a2b19a0c 100644 --- a/Source/Contents/Actors/RenderObject.cpp +++ b/Source/Contents/Actors/RenderObject.cpp @@ -12,16 +12,21 @@ #include "Contents/Actors/EffectActors.h" #include "Contents/Effects/Effect.h" #include "Contents/Effects/EffectLists.h" +#include "Contents/Effects/RagEffect.h" #include "Contents/GuildMgr.h" #include "Contents/Jobs/JobId.h" #include "Contents/Jobs/JobInheritance.h" +#include "Contents/World/World.h" +#include "Render/Primitives/LightmappedPrimitive.h" #include "Render/Primitives/RenderPrimitive.h" #include "Render/Renderer.h" #include "Render/SpriteQuad.h" +#include "Render/TextureQuad.h" #include "Resource/Models/GrannyModelResource.h" #include "Resource/ResourceMgr.h" #include "Resource/Sprites/ActionResource.h" #include "Resource/Sprites/SpriteResource.h" +#include "Resource/World/TerrainResource.h" #include "Script/LuaScript.h" #include "Utility/Math/AffineMatrix.h" #include "Utility/Cipher/XorBytes.h" @@ -35,6 +40,9 @@ #include #include +// GLOBAL: gl_24798 +int g_multiHitInterval = 200; + namespace { template @@ -71,14 +79,17 @@ private: class C3dGrannyGameActor : public CGameActor { public: void SetAction(unsigned animation, int motion, int repeat) override; + void Slot08() override; void Slot36() override; int Slot37() override; /* 0410 */ unsigned char unknown_0410[4]; /* 0414 */ C3dGrannyModelRes* model; /* 0418 */ granny_model_instance* instance; - /* 041C */ unsigned char unknown_041C[0x110]; - /* 052C */ int unknown_052C; + /* 041C */ unsigned char unknown_041C[0xF0]; + /* 050C */ granny_system_clock clock; + /* 051C */ granny_system_clock lastClock; + /* 052C */ float unknown_052C; /* 0530 */ unsigned char unknown_0530[0x14]; /* 0544 */ int motion; /* 0548 */ unsigned char unknown_0548[0x110]; @@ -90,6 +101,7 @@ public: explicit CGrannyCorpse3d(const char* name); ~CGrannyCorpse3d() override; bool OnProcess() override; + void Render(int argument) override; /* 0010 */ vector3d position; /* 001C */ C3dGrannyModelRes* model; @@ -244,6 +256,20 @@ void CRenderObject::SetColor(int alpha, int red, int green, int blue) { } // OFFSET: 0x00CAF050 +// FUNCTION: fn_21078 +void CRenderObject::ModifyArgb(int alpha, int red, int green, int blue) { + if (this->render.unknown_00A3 == 0) { + unsigned char* argb = reinterpret_cast(&this->render.color); + int newAlpha = argb[3] + alpha; + int newRed = argb[2] + red; + int newGreen = argb[1] + green; + int newBlue = argb[0] + blue; + argb[3] = static_cast(std::min(std::max(newAlpha, 0), 255)); + argb[2] = static_cast(std::min(std::max(newRed, 0), 255)); + argb[1] = static_cast(std::min(std::max(newGreen, 0), 255)); + argb[0] = static_cast(std::min(std::max(newBlue, 0), 255)); + } +} // OFFSET: 0x00CC1C20 // FUNCTION: fn_21125 @@ -1652,3 +1678,329 @@ CGrannyCorpse3d::~CGrannyCorpse3d() { GrannyFreeModelInstance(this->instance); } +// OFFSET: 0x00CC8230 +// FUNCTION: fn_21193 +void CGameActor::ProcessHitMessage(CGameObject*, const ActorHitInfo* info, int, int) { + CGameMode* mode = static_cast(g_modeMgr.GetGameMode()); + int type = info->type; + this->hitTargetAid = info->targetAid; + int damage = info->damage; + int hitCount = info->hitCount; + const int extraDamage = info->extraDamage; + const int unknown_001C = info->unknown_001C; + const int unknown_0020 = info->unknown_0020; + const int attackSpeed = info->attackSpeed; + const int motionSpeed = info->motionSpeed; + + CGameActor* target; + if (g_session.m_aid == info->targetAid) { + target = reinterpret_cast(mode->scene->player); + } else { + target = static_cast(mode->scene->GetGameActorByAID(info->targetAid)); + if (target == nullptr) { + target = static_cast(mode->scene->GetSkillByAID(this->hitTargetAid)); + } + } + + float x; + float z; + if (target == nullptr) { + const CRenderObject* player = reinterpret_cast(mode->scene->player); + x = this->render.position.x - (player->render.position.x - this->render.position.x); + z = this->render.position.z - (player->render.position.z - this->render.position.z); + } else { + x = target->render.position.x; + z = target->render.position.z; + target->GetEffectiveJob(-1); + } + + if (hitCount <= 0) { + hitCount = 1; + } else if (hitCount > 36) { + hitCount = 1; + } + + if (type != 8 && type != 9 && type != 13) { + hitCount = 1; + } + + damage /= hitCount; + switch (type) { + case 0: + case 4: + case 8: + case 9: + case 10: + case 11: + case 13: { + if (this->unknown_0178 != 0) { + CRagEffect* effect = this->LaunchEffect(0x83, vector3d{0.0f, 0.0f, 0.0f}, 0.0f); + if (effect != nullptr) { + effect->SendMsg(this, 50, 0, 0, 0, 0, 0, 0); + } + this->unknown_0178 = 0; + } + + this->Slot39(attackSpeed); + if (info->targetAid != this->render.accountId) { + this->Slot15(2, 1); + } + + this->Face(x, z); + unsigned delay = static_cast(this->unknown_0378 * this->render.speed * 24.0f); + if (this->GetEffectiveJob(-1) == 0x3F8 || this->GetEffectiveJob(-1) == 0x58C) { + delay += 0xC0; + } else if (this->GetEffectiveJob(-1) == 0x505 || this->GetEffectiveJob(-1) == 0x726) { + delay += 0x390; + } else if (this->GetEffectiveJob(-1) == 0x506 || this->GetEffectiveJob(-1) == 0x725 || this->GetEffectiveJob(-1) == 0x507) { + delay += 0x198; + } + + int flag = -1; + int action; + switch (type) { + case 4: + case 9: + action = 0x27; + break; + case 10: + action = 0x5D; + flag = 1; + break; + case 11: + action = this->unknown_0178 != 0 ? 0x1A : 0x5E; + break; + case 13: + action = 0x94; + flag = 1; + break; + default: + action = 0x1A; + break; + } + + if (target == nullptr) { + return; + } + + int effectId = 0; + switch (this->GetEffectiveJob(-1)) { + case 0xFCE: + case 0xFCF: + case 0xFD0: + case 0x1081: + case 0x1082: + case 0x108E: + case 0x108F: + case 0x1090: + case 0x1091: + case 0x1092: + case 0x1093: + case 0x10CE: + case 0x10CF: + case 0x10DC: + effectId = 0x1B2; + break; + case 0x576F: + case 0x5770: + effectId = 0x974; + break; + } + + const CRenderObject* player = reinterpret_cast(mode->scene->player); + int column; + int row; + mode->WorldCell(player->render.position.x, player->render.position.y, column, row); + if (info->targetAid == this->render.accountId) { + effectId = -2; + } + + ActorDamageMessage message; + message.motionSpeed = static_cast(motionSpeed) / 288.0f; + message.targetAid = this->hitTargetAid; + message.time = timeGetTime() + delay; + message.attackerAid = this->render.accountId; + message.unknown_0014 = unknown_001C; + message.unknown_0018 = unknown_0020; + message.unknown_001C = flag; + message.effectId = effectId; + message.unknown_002C = info->unknown_002C; + message.action = action; + message.damage = damage; + message.unknown_0024 = 1; + message.unknown_0034 = 0; + + const std::int64_t address = reinterpret_cast(&message); + if (hitCount == 1) { + target->SendMsg(this, 0x1B, 0, static_cast(address), static_cast(address >> 32), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } else { + const std::int64_t value = damage; + target->SendMsg( + this, 0x1B, 0, static_cast(address), static_cast(address >> 32), static_cast(value), static_cast(value >> 32), 0, 0, 0, + 0, 0, 0, 0, 0 + ); + } + + const int job = this->GetEffectiveJob(-1); + if (extraDamage != 0 && (g_session.IsNpcJob(job) || job > 4000 || job == 12)) { + int total = damage; + if (hitCount > 1) { + for (int hit = hitCount - 1; hit != 0; --hit) { + message.time += g_multiHitInterval / 2; + total += damage; + message.damage = damage; + const std::int64_t value = total; + target->SendMsg( + this, 0x1B, 0, static_cast(address), static_cast(address >> 32), static_cast(value), static_cast(value >> 32), 0, 0, + 0, 0, 0, 0, 0, 0 + ); + message.time -= g_multiHitInterval / 2; + } + } + + message.action = action; + message.time += g_multiHitInterval / 2 + (g_multiHitInterval / 4 + g_multiHitInterval); + message.damage = extraDamage; + if (hitCount == 1) { + target->SendMsg(this, 0x1B, 0, static_cast(address), static_cast(address >> 32), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } else { + const std::int64_t value = total + extraDamage; + target->SendMsg( + this, 0x1B, 0, static_cast(address), static_cast(address >> 32), static_cast(value), static_cast(value >> 32), 0, 0, 0, + 0, 0, 0, 0, 0 + ); + } + return; + } + + if (hitCount > 1) { + int total = damage; + for (int hit = hitCount - 1; hit != 0; --hit) { + message.time += g_multiHitInterval; + total += damage; + message.damage = damage; + const std::int64_t value = total; + target->SendMsg( + this, 0x1B, 0, static_cast(address), static_cast(address >> 32), static_cast(value), static_cast(value >> 32), 0, 0, 0, + 0, 0, 0, 0, 0 + ); + } + } + break; + } + case 1: + this->Slot15(5, 1); + return; + case 2: + this->Slot15(6, 1); + return; + case 3: + this->Slot15(0, 1); + return; + } +} + +// OFFSET: 0x00CC5C70 +// FUNCTION: fn_21180 +void C3dGrannyGameActor::Slot08() { + this->render.isMotionFinished = this->render.unknown_0070 != 2; + if (this->render.unknown_0070 != 2 || !(this->render.motionSpeed < 1.0f)) { + return; + } + + this->clock = GrannyGetSystemSeconds(); + const float elapsed = GrannyGetSecondsElapsed(&this->lastClock, &this->clock) * 2.0f; + this->unknown_052C += elapsed / this->render.motionSpeed; + this->model->Animate(this->unknown_052C, this->instance); + this->lastClock = this->clock; + + if (this->motion == 2 && this->controls[2] != nullptr && GrannyGetControlDurationLeft(this->controls[2]) <= 0.0f) { + this->motion = 0; + this->controls[this->motion] = this->model->PlayAnimation(0, this->instance, this->controls[0], 0); + this->render.motionSpeed = 1.0f; + } +} + +// OFFSET: 0x00CC5EE0 +// FUNCTION: fn_21183 +void CGrannyCorpse3d::Render(int argument) { + const matrix& view = *reinterpret_cast(argument); + CTexture* texture = nullptr; + std::uint16_t* indices = nullptr; + + this->clock = GrannyGetSystemSeconds(); + const float elapsed = GrannyGetSecondsElapsed(&this->startClock, &this->clock) * 2.0f; + this->unknown_0138 += elapsed; + this->model->Animate(this->unknown_0138, this->instance); + this->startClock = this->clock; + + CWorld* scene = static_cast(g_modeMgr.GetGameMode())->scene; + const float sine = ClientSine(static_cast(this->angle)); + const float cosine = ClientCosine(static_cast(this->angle)); + scene->terrain->Grid().Height(cosine * 0.0f - sine * -40.0f + this->position.x, sine * 0.0f - cosine * 40.0f + this->position.z); + scene->terrain->Grid().Height(cosine * 0.0f - sine * 40.0f + this->position.x, cosine * 40.0f + sine * 0.0f + this->position.z); + + matrix transform; + std::memcpy(transform.values, g_identityMatrix, sizeof(g_identityMatrix)); + matrix rotation; + rotation.MakeXRotation(std::strstr(this->name, "90") != nullptr ? 1.570796f : 3.141592f); + rotation.AppendYRotation((this->angle + 180.0f) / 180.0f * 3.141592f); + rotation.AppendTranslate(&this->position.x); + + vector3d position{0.0f, 0.0f, 0.0f}; + g_modeMgr.GetGameMode(); + matrix inverse; + inverse.MakeInverse(view); + float lightX = inverse.values[0] * 0.0f - inverse.values[3] * 300.0f + inverse.values[6] * 0.0f + inverse.values[9]; + float lightY = inverse.values[1] * 0.0f - inverse.values[4] * 300.0f + inverse.values[7] * 0.0f + inverse.values[10]; + float lightZ = inverse.values[2] * 0.0f - inverse.values[5] * 300.0f + inverse.values[8] * 0.0f + inverse.values[11]; + const float scale = 1.0f / SquareRoot(lightY * lightY + lightX * lightX + lightZ * lightZ); + lightX *= scale; + lightY *= scale; + lightZ *= scale; + + float screenX; + float screenY; + float reciprocalDepth; + g_renderer->ProjectViewport(&this->position.x, view.values, &screenX, &screenY, &reciprocalDepth); + + for (int mesh = 0; mesh < this->model->instance->meshCount; ++mesh) { + this->model->deformBuffer = this->vertices[mesh]; + int triangleCount; + const GrannyPNT332Vertex* source = static_cast( + this->model->GetMeshRenderData(mesh, &texture, &indices, &triangleCount, &transform, nullptr) + ); + if (source == nullptr || texture == nullptr) { + continue; + } + + matrix local; + local.Compose(transform, rotation); + matrix world; + world.Compose(local, view); + + RenderCommand* command = this->commands[mesh]; + int vertexCount = 0; + for (int triangle = 0; triangle < triangleCount; ++triangle) { + command = this->commands[mesh]; + for (int corner = 0; corner < 3; ++corner) { + const GrannyPNT332Vertex& vertex = source[indices[vertexCount]]; + RenderVertex& output = command->vertices[vertexCount]; + ++vertexCount; + position.x = vertex.position[0]; + position.y = vertex.position[1]; + position.z = vertex.position[2]; + g_renderer->viewport.Projection().Project(&position.x, world.values, output); + output.u = vertex.uv[0]; + output.v = vertex.uv[1]; + const float shade = lightX * vertex.normal[0] + lightY * vertex.normal[1] + lightZ * vertex.normal[2]; + this->brightness = static_cast(std::max(0.0f, std::min(1.0f, shade)) * 100.0f + 155.0f); + output.diffuse = (this->alpha << 24) + static_cast(this->brightness) * 0x10101; + } + } + + command->texture = texture; + command->vertexCount = vertexCount; + g_renderer->queues.Submit(command, 0, g_renderer->fog.supported != 0, g_depthStart, g_depthEnd); + } +} diff --git a/Source/Contents/Actors/RenderObjectState.h b/Source/Contents/Actors/RenderObjectState.h index 4fec04f0..94a7a7a9 100644 --- a/Source/Contents/Actors/RenderObjectState.h +++ b/Source/Contents/Actors/RenderObjectState.h @@ -34,7 +34,7 @@ struct RenderObjectState { /* 005C */ float unknown_005C; /* 0060 */ float unknown_0060; /* 0064 */ float unknown_0064; - /* 0068 */ std::uint32_t unknown_0068; + /* 0068 */ float motionSpeed; /* 006C */ std::uint32_t unknown_006C; /* 0070 */ std::uint32_t unknown_0070; /* 0074 */ std::uint32_t unknown_0074; diff --git a/Source/Resource/Models/GrannyModelResource.h b/Source/Resource/Models/GrannyModelResource.h index 55dfbefa..eb4d80ab 100644 --- a/Source/Resource/Models/GrannyModelResource.h +++ b/Source/Resource/Models/GrannyModelResource.h @@ -2,13 +2,13 @@ #define RAGNAROK_SOURCE_RESOURCE_MODELS_GRANNYMODELRESOURCE_H #include "Resource/Models/ModelResource.h" +#include "Utility/Math/AffineMatrix.h" #include #include #include class CTexture; -struct matrix; struct granny_animation; struct granny_control; @@ -56,7 +56,8 @@ struct granny_material_binding { }; struct granny_mesh { - /* 0000 */ unsigned char unknown_0000[0x14]; + /* 0000 */ const char* name; + /* 0004 */ unsigned char unknown_0004[0x10]; /* 0014 */ std::int32_t materialBindingCount; /* 0018 */ granny_material_binding* materialBindings; }; @@ -88,6 +89,18 @@ struct granny_file_info { /* 0054 */ granny_animation** animations; }; +struct granny_tri_material_group { + /* 0000 */ std::int32_t materialIndex; + /* 0004 */ std::int32_t triFirst; + /* 0008 */ std::int32_t triCount; +}; + +struct GrannyPNT332Vertex { + /* 0000 */ float position[3]; + /* 000C */ float normal[3]; + /* 0018 */ float uv[2]; +}; + struct granny_system_clock { /* 0000 */ std::uint32_t data[4]; }; @@ -126,7 +139,27 @@ __declspec(dllimport) granny_local_pose* WINAPI GrannyNewLocalPose(std::int32_t __declspec(dllimport) void WINAPI GrannyFreeMeshDeformer(granny_mesh_deformer* deformer); __declspec(dllimport) void WINAPI GrannyFreeModelInstance(granny_model_instance* instance); __declspec(dllimport) granny_system_clock WINAPI GrannyGetSystemSeconds(); +__declspec(dllimport) float WINAPI GrannyGetSecondsElapsed(const granny_system_clock* start, const granny_system_clock* end); +__declspec(dllimport) float WINAPI GrannyGetControlDurationLeft(granny_control* control); __declspec(dllimport) void WINAPI GrannyFreeWorldPose(granny_world_pose* pose); +__declspec(dllimport) const float* WINAPI GrannyControlModelsNext(const granny_world_pose* pose); +__declspec(dllimport) const std::int32_t* WINAPI GrannyGetMeshBindingToBoneIndices(const granny_mesh_binding* binding); +__declspec(dllimport) const granny_tri_material_group* WINAPI GrannyGetMeshTriangleGroups(const granny_mesh* mesh); +__declspec(dllimport) const float* WINAPI GrannyGetWorldPoseComposite4x4(const granny_world_pose* pose, std::int32_t bone); +__declspec(dllimport) const void* WINAPI GrannyGetMeshVertices(const granny_mesh* mesh); +__declspec(dllimport) void WINAPI GrannyDeformVertices( + const granny_mesh_deformer* deformer, const std::int32_t* boneIndices, const float* matrices, std::int32_t vertexCount, const void* source, + void* destination +); +__declspec(dllimport) std::int32_t WINAPI GrannyGetMeshBytesPerIndex(const granny_mesh* mesh); +__declspec(dllimport) const void* WINAPI GrannyGetMeshIndices(const granny_mesh* mesh); +__declspec(dllimport) void WINAPI GrannySetModelClock(granny_model_instance* instance, float seconds); +__declspec(dllimport) void WINAPI GrannySampleModelAnimations(granny_model_instance* instance, std::int32_t firstBone, std::int32_t boneCount, granny_local_pose* pose); +__declspec(dllimport) void WINAPI GrannyBuildWorldPose( + const granny_skeleton* skeleton, std::int32_t firstBone, std::int32_t boneCount, const granny_local_pose* localPose, const float* offset, + granny_world_pose* worldPose +); +__declspec(dllimport) void WINAPI GrannyFreeCompletedModelControls(granny_model_instance* instance); __declspec(dllimport) granny_control* WINAPI GrannyPlayControlledAnimation(float startTime, const granny_animation* animation, granny_model_instance* instance); __declspec(dllimport) void WINAPI GrannySetControlLoopCount(granny_control* control, std::int32_t loopCount); __declspec(dllimport) void WINAPI GrannyFreeLocalPose(granny_local_pose* pose); @@ -179,6 +212,12 @@ public: /* 0280 */ granny_animation* animations[100][20]; }; +struct GrannyHairPivot { + /* 0000 */ int unknown_0000; + /* 0004 */ int mesh; + /* 0008 */ matrix transform; +}; + class C3dGrannyModelRes : public CRes { public: explicit C3dGrannyModelRes(ModelResourceContext& context); @@ -192,6 +231,16 @@ public: bool ProjectBounds(float* rect, const matrix& view, const matrix& world, const float* eye) const; int GetTriangleCount(int mesh) const; unsigned char* GetRigidVertices(int mesh) const; + const void* GetMeshRenderData( + int mesh, CTexture** texture, std::uint16_t** indices, int* triangleCount, matrix* transform, granny_world_pose* pose + ); + const void* GetMeshRenderData( + GrannyModelInstance* model, int mesh, CTexture** texture, std::uint16_t** indices, int* triangleCount, matrix* transform, + granny_world_pose* pose + ); + bool FindHairPivot(); + void Release(); + granny_world_pose* Animate(float seconds, granny_model_instance* instance); /* 0110 */ float unknown_0110; /* 0114 */ unsigned char unknown_0114[0x14C]; @@ -204,7 +253,8 @@ public: /* 0278 */ void* localPose; /* 027C */ int deformBufferSize; /* 0280 */ int unknown_0280; - /* 0284 */ unsigned char unknown_0284[8]; + /* 0284 */ int unknown_0284; + /* 0288 */ void* deformBuffer; /* 028C */ char fileName[MAX_PATH]; /* 0390 */ const granny_model* model; /* 0394 */ float position[3]; @@ -212,7 +262,7 @@ public: /* 03A4 */ float unknown_03A4; /* 03A8 */ GrannyAnimationLibrary* animations; /* 03AC */ unsigned job; - /* 03B0 */ int unknown_03B0; + /* 03B0 */ GrannyHairPivot* unknown_03B0; /* 03B4 */ granny_skeleton* skeleton; private: @@ -233,6 +283,8 @@ static_assert(offsetof(C3dGrannyModelRes, file) == 0x260); static_assert(offsetof(C3dGrannyModelRes, fileName) == 0x28C); static_assert(offsetof(C3dGrannyModelRes, model) == 0x390); static_assert(offsetof(C3dGrannyModelRes, skeleton) == 0x3B4); +static_assert(offsetof(C3dGrannyModelRes, deformBuffer) == 0x288); +static_assert(offsetof(C3dGrannyModelRes, unknown_03B0) == 0x3B0); static_assert(offsetof(granny_file_info, models) == 0x44); static_assert(offsetof(granny_file_info, animations) == 0x54); static_assert(offsetof(GrannyAnimationLibrary, file) == 0x150); diff --git a/Source/Resource/Models/ModelResource.cpp b/Source/Resource/Models/ModelResource.cpp index 6facf67e..d8a53616 100644 --- a/Source/Resource/Models/ModelResource.cpp +++ b/Source/Resource/Models/ModelResource.cpp @@ -877,3 +877,134 @@ void AnimatedModelNode::Shade(const float* matrix, int frame, const float* light child->Shade(matrix, frame, light, diffuse, ambient, shadeType, alpha); } } + +// OFFSET: 0x0075CB30 +// FUNCTION: fn_08025 +const void* C3dGrannyModelRes::GetMeshRenderData( + int mesh, CTexture** texture, std::uint16_t** indices, int* triangleCount, matrix* transform, granny_world_pose* pose +) { + return this->GetMeshRenderData(this->instance, mesh, texture, indices, triangleCount, transform, pose); +} + +// OFFSET: 0x0075CB60 +// FUNCTION: fn_08026 +const void* C3dGrannyModelRes::GetMeshRenderData( + GrannyModelInstance* model, int mesh, CTexture** texture, std::uint16_t** indices, int* triangleCount, matrix* transform, + granny_world_pose* pose +) { + if (pose == nullptr) { + pose = model->worldPose; + } + + const float* matrices = GrannyControlModelsNext(pose); + GrannyMeshEntry* meshes = model->meshes; + const std::int32_t* boneIndices = GrannyGetMeshBindingToBoneIndices(meshes[mesh].binding); + const std::int32_t vertexCount = GrannyGetMeshVertexCount(meshes[mesh].mesh); + const granny_tri_material_group* groups = GrannyGetMeshTriangleGroups(meshes[mesh].mesh); + GrannyTextureEntry* entry = meshes[mesh].textures[groups->materialIndex]; + *texture = entry != nullptr && groups->materialIndex < meshes[mesh].materialCount ? entry->texture : nullptr; + + if (GrannyMeshIsRigid(meshes[mesh].mesh)) { + const float* composite = GrannyGetWorldPoseComposite4x4(model->worldPose, boneIndices[0]); + transform->values[0] = composite[0]; + transform->values[1] = composite[1]; + transform->values[2] = composite[2]; + transform->values[3] = composite[4]; + transform->values[4] = composite[5]; + transform->values[5] = composite[6]; + transform->values[6] = composite[8]; + transform->values[7] = composite[9]; + transform->values[8] = composite[10]; + transform->values[9] = composite[12]; + transform->values[10] = composite[13]; + transform->values[11] = composite[14]; + + const void* vertices = meshes[mesh].vertices; + *indices = meshes[mesh].indices; + *triangleCount = GrannyGetMeshIndexCount(meshes[mesh].mesh) / 3; + if (std::strstr(meshes[mesh].mesh->name, "hair_pivot") != nullptr && this->unknown_03B0 != nullptr) { + this->unknown_03B0->transform = *transform; + } + + return vertices; + } + + if (meshes[mesh].deformer == nullptr) { + return nullptr; + } + + GrannyDeformVertices(meshes[mesh].deformer, boneIndices, matrices, vertexCount, GrannyGetMeshVertices(meshes[mesh].mesh), this->deformBuffer); + GrannyGetMeshBytesPerIndex(meshes[mesh].mesh); + GrannyGetMeshIndices(meshes[mesh].mesh); + *indices = meshes[mesh].indices; + *triangleCount = GrannyGetMeshIndexCount(meshes[mesh].mesh) / 3; + std::memcpy(transform->values, g_identityMatrix, sizeof(g_identityMatrix)); + return this->deformBuffer; +} + +// OFFSET: 0x0075CD90 +// FUNCTION: fn_08027 +bool C3dGrannyModelRes::FindHairPivot() { + if (this->unknown_03B0 == nullptr) { + return false; + } + + for (int mesh = 0; mesh < this->instance->meshCount; ++mesh) { + if (std::strstr(this->instance->meshes[mesh].mesh->name, "hair_pivot") != nullptr) { + this->unknown_03B0->mesh = mesh; + return true; + } + } + + return false; +} + +// OFFSET: 0x0075CFC0 +// FUNCTION: fn_08029 +granny_world_pose* C3dGrannyModelRes::Animate(float seconds, granny_model_instance* instance) { + GrannyModelInstance* model = this->instance; + if (model == nullptr) { + return nullptr; + } + + granny_skeleton* skeleton = GrannyGetSourceSkeleton(instance); + const std::int32_t boneCount = skeleton->boneCount; + GrannySetModelClock(instance, seconds); + GrannySampleModelAnimations(instance, 0, boneCount, static_cast(this->localPose)); + GrannyBuildWorldPose(skeleton, 0, boneCount, static_cast(this->localPose), nullptr, model->worldPose); + GrannyFreeCompletedModelControls(instance); + return model->worldPose; +} + +// OFFSET: 0x0075CA20 +// FUNCTION: fn_08024 +void C3dGrannyModelRes::Release() { + if (GrannyModelInstance* instance = this->instance) { + for (int index = 0; index < instance->meshCount; ++index) { + GrannyMeshEntry& entry = instance->meshes[index]; + GrannyFreeMeshBinding(entry.binding); + GrannyFreeMeshDeformer(entry.deformer); + delete[] entry.textures; + delete entry.indices; + if (entry.vertices != nullptr) { + delete entry.vertices; + } + } + + GrannyFreeModelInstance(instance->instance); + GrannyFreeWorldPose(instance->worldPose); + delete[] instance->meshes; + } + + for (int index = 0; index < this->textureCount; ++index) { + if (this->textures[index].texture != nullptr) { + delete this->textures[index].texture; + } + } + + delete[] this->textures; + GrannyFreeLocalPose(static_cast(this->localPose)); + GrannyFreeFile(this->file); + delete this->instance; + this->instance = nullptr; +}