diff --git a/premake5.lua b/premake5.lua index 87ab7053..a90e6837 100644 --- a/premake5.lua +++ b/premake5.lua @@ -11,6 +11,7 @@ workspace "re3" files { "src/weapons/*.*" } files { "src/render/*.*" } files { "src/control/*.*" } + files { "src/animation/*.*" } files { "src/audio/*.*" } includedirs { "src" } @@ -22,6 +23,7 @@ workspace "re3" includedirs { "src/render" } includedirs { "src/control" } includedirs { "src/audio" } + includedirs { "src/animation" } includedirs { "dxsdk/include" } includedirs { "rwsdk/include/d3d8" } diff --git a/src/Directory.cpp b/src/Directory.cpp new file mode 100644 index 00000000..553dd539 --- /dev/null +++ b/src/Directory.cpp @@ -0,0 +1,65 @@ +#include "common.h" +#include "patcher.h" +#include "FileMgr.h" +#include "Directory.h" + +CDirectory::CDirectory(int32 maxEntries) + : numEntries(0), maxEntries(maxEntries) +{ + entries = new DirectoryInfo[maxEntries]; +} + +CDirectory::~CDirectory(void) +{ + delete[] entries; +} + +void +CDirectory::ReadDirFile(const char *filename) +{ + int fd; + DirectoryInfo dirinfo; + + fd = CFileMgr::OpenFile(filename, "rb"); + while(CFileMgr::Read(fd, (char*)&dirinfo, sizeof(dirinfo))) + AddItem(dirinfo); + return CFileMgr::CloseFile(fd); +} + +bool +CDirectory::WriteDirFile(const char *filename) +{ + int fd, n; + fd = CFileMgr::OpenFileForWriting(filename); + n = CFileMgr::Write(fd, (char*)entries, numEntries*sizeof(DirectoryInfo)); + CFileMgr::CloseFile(fd); + return n == numEntries*sizeof(DirectoryInfo); +} + +void +CDirectory::AddItem(const DirectoryInfo &dirinfo) +{ + assert(numEntries < maxEntries); + entries[numEntries++] = dirinfo; +} + +bool +CDirectory::FindItem(const char *name, uint32 &offset, uint32 &size) +{ + int i; + + for(i = 0; i < numEntries; i++) + if(strcmpi(entries[i].name, name) == 0){ + offset = entries[i].offset; + size = entries[i].size; + return true; + } + return false; +} + +STARTPATCHES + InjectHook(0x473630, &CDirectory::ReadDirFile, PATCH_JUMP); + InjectHook(0x473690, &CDirectory::WriteDirFile, PATCH_JUMP); + InjectHook(0x473600, &CDirectory::AddItem, PATCH_JUMP); + InjectHook(0x4736E0, &CDirectory::FindItem, PATCH_JUMP); +ENDPATCHES diff --git a/src/Directory.h b/src/Directory.h new file mode 100644 index 00000000..06e6bba4 --- /dev/null +++ b/src/Directory.h @@ -0,0 +1,22 @@ +#pragma once + +class CDirectory +{ +public: + struct DirectoryInfo { + uint32 offset; + uint32 size; + char name[24]; + }; + DirectoryInfo *entries; + int32 maxEntries; + int32 numEntries; + + CDirectory(int32 maxEntries); + ~CDirectory(void); + + void ReadDirFile(const char *filename); + bool WriteDirFile(const char *filename); + void AddItem(const DirectoryInfo &dirinfo); + bool FindItem(const char *name, uint32 &offset, uint32 &size); +}; diff --git a/src/animation/AnimBlendAssocGroup.cpp b/src/animation/AnimBlendAssocGroup.cpp new file mode 100644 index 00000000..16749504 --- /dev/null +++ b/src/animation/AnimBlendAssocGroup.cpp @@ -0,0 +1,161 @@ +#include "common.h" +#include "patcher.h" +#include "ModelInfo.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" + +CAnimBlendAssocGroup::CAnimBlendAssocGroup(void) +{ + assocList = nil; + numAssociations = 0; +} + +CAnimBlendAssocGroup::~CAnimBlendAssocGroup(void) +{ + DestroyAssociations(); +} + +void +CAnimBlendAssocGroup::DestroyAssociations(void) +{ + if(assocList){ + delete[] assocList; + assocList = nil; + numAssociations = 0; + } +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::GetAnimation(uint32 id) +{ + return &assocList[id]; +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::GetAnimation(const char *name) +{ + int i; + for(i = 0; i < numAssociations; i++) + if(strcmpi(assocList[i].hierarchy->name, name) == 0) + return &assocList[i]; + return nil; +} + + +CAnimBlendAssociation* +CAnimBlendAssocGroup::CopyAnimation(uint32 id) +{ + CAnimBlendAssociation *anim = GetAnimation(id); + if(anim == nil) + return nil; + CAnimManager::UncompressAnimation(anim->hierarchy); + return new CAnimBlendAssociation(*anim); +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::CopyAnimation(const char *name) +{ + CAnimBlendAssociation *anim = GetAnimation(name); + if(anim == nil) + return nil; + CAnimManager::UncompressAnimation(anim->hierarchy); + return new CAnimBlendAssociation(*anim); +} + +int +strcmpIgnoringDigits(const char *s1, const char *s2) +{ + char c1, c2; + + for(;;){ + c1 = *s1; + c2 = *s2; + if(c1) s1++; + if(c2) s2++; + if(c1 == '\0' && c2 == '\0') + return 1; + if(islower(c1)) c1 = toupper(c1); + if(islower(c2)) c2 = toupper(c2); + if(isdigit(c1) && isdigit(c2)) + continue; + if(c1 != c2) + return 0; + } +} + +CBaseModelInfo* +GetModelFromName(const char *name) +{ + int i; + CBaseModelInfo *mi; + + for(i = 0; i < MODELINFOSIZE; i++){ + mi = CModelInfo::GetModelInfo(i); + if(mi->GetRwObject() && RwObjectGetType(mi->GetRwObject()) == rpCLUMP && + strcmpIgnoringDigits(mi->GetName(), name)) + return mi; + } + return nil; +} + +void +CAnimBlendAssocGroup::CreateAssociations(const char *name) +{ + int i; + CAnimBlock *animBlock; + + if(assocList) + DestroyAssociations(); + + animBlock = CAnimManager::GetAnimationBlock(name); + assocList = new CAnimBlendAssociation[animBlock->numAnims]; + numAssociations = 0; + + for(i = 0; i < animBlock->numAnims; i++){ + CAnimBlendHierarchy *anim = CAnimManager::GetAnimation(animBlock->firstIndex + i); + CBaseModelInfo *model = GetModelFromName(anim->name); + printf("Associated anim %s with model %s\n", anim->name, model->GetName()); + if(model){ + RpClump *clump = (RpClump*)model->CreateInstance(); + RpAnimBlendClumpInit(clump); + assocList[i].Init(clump, anim); + RpClumpDestroy(clump); + assocList[i].animId = i; + } + } + numAssociations = animBlock->numAnims; +} + +// Create associations from hierarchies for a given clump +void +CAnimBlendAssocGroup::CreateAssociations(const char *blockName, RpClump *clump, char **animNames, int numAssocs) +{ + int i; + CAnimBlock *animBlock; + + if(assocList) + DestroyAssociations(); + + animBlock = CAnimManager::GetAnimationBlock(blockName); + assocList = new CAnimBlendAssociation[animBlock->numAnims]; + + numAssociations = 0; + for(i = 0; i < numAssocs; i++){ + assocList[i].Init(clump, CAnimManager::GetAnimation(animNames[i], animBlock)); + assocList[i].animId = i; + } + numAssociations = numAssocs; +} + + + +STARTPATCHES + InjectHook(0x4013D0, (CAnimBlendAssociation *(CAnimBlendAssocGroup::*)(uint32))&CAnimBlendAssocGroup::GetAnimation, PATCH_JUMP); + InjectHook(0x401300, (CAnimBlendAssociation *(CAnimBlendAssocGroup::*)(const char*))&CAnimBlendAssocGroup::GetAnimation, PATCH_JUMP); + InjectHook(0x401420, (CAnimBlendAssociation *(CAnimBlendAssocGroup::*)(uint32))&CAnimBlendAssocGroup::CopyAnimation, PATCH_JUMP); + InjectHook(0x4013E0, (CAnimBlendAssociation *(CAnimBlendAssocGroup::*)(const char*))&CAnimBlendAssocGroup::CopyAnimation, PATCH_JUMP); + InjectHook(0x401130, (void (CAnimBlendAssocGroup::*)(const char*))&CAnimBlendAssocGroup::CreateAssociations, PATCH_JUMP); + InjectHook(0x401220, (void (CAnimBlendAssocGroup::*)(const char*, RpClump*, char**, int))&CAnimBlendAssocGroup::CreateAssociations, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/AnimBlendAssocGroup.h b/src/animation/AnimBlendAssocGroup.h new file mode 100644 index 00000000..708a3cdd --- /dev/null +++ b/src/animation/AnimBlendAssocGroup.h @@ -0,0 +1,20 @@ +#pragma once + +class CAnimBlendAssociation; + +class CAnimBlendAssocGroup +{ +public: + CAnimBlendAssociation *assocList; + int32 numAssociations; + + CAnimBlendAssocGroup(void); + ~CAnimBlendAssocGroup(void); + void DestroyAssociations(void); + CAnimBlendAssociation *GetAnimation(uint32 id); + CAnimBlendAssociation *GetAnimation(const char *name); + CAnimBlendAssociation *CopyAnimation(uint32 id); + CAnimBlendAssociation *CopyAnimation(const char *name); + void CreateAssociations(const char *name); + void CreateAssociations(const char *blockName, RpClump *clump, char **animNames, int numAssocs); +}; diff --git a/src/animation/AnimBlendAssociation.cpp b/src/animation/AnimBlendAssociation.cpp new file mode 100644 index 00000000..eb7019ab --- /dev/null +++ b/src/animation/AnimBlendAssociation.cpp @@ -0,0 +1,227 @@ +#include "common.h" +#include "patcher.h" +#include "AnimBlendHierarchy.h" +#include "AnimBlendClumpData.h" +#include "RpAnimBlend.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" + +// TODO: implement those +#define RwFreeAlign RwFree +#define RwMallocAlign(sz, algn) RwMalloc(sz) + +CAnimBlendAssociation::CAnimBlendAssociation(void) +{ + nodes = nil; + blendAmount = 1.0f; + blendDelta = 0.0f; + currentTime = 0.0f; + speed = 1.0f; + timeStep = 0.0f; + animId = -1; + flags = 0; + callbackType = CB_NONE; + link.Init(); +} + +CAnimBlendAssociation::CAnimBlendAssociation(CAnimBlendAssociation &other) +{ + nodes = nil; + blendAmount = 1.0f; + blendDelta = 0.0f; + currentTime = 0.0f; + speed = 1.0f; + timeStep = 0.0f; + callbackType = CB_NONE; + link.Init(); + Init(other); +} + +CAnimBlendAssociation::~CAnimBlendAssociation(void) +{ + FreeAnimBlendNodeArray(); + link.Remove(); +} + + +void +CAnimBlendAssociation::AllocateAnimBlendNodeArray(int n) +{ + int i; + + nodes = (CAnimBlendNode*)RwMallocAlign(n*sizeof(CAnimBlendNode), 64); + for(i = 0; i < n; i++) + nodes[i].Init(); +} + +void +CAnimBlendAssociation::FreeAnimBlendNodeArray(void) +{ + RwFreeAlign(nodes); +} + +void +CAnimBlendAssociation::Init(RpClump *clump, CAnimBlendHierarchy *hier) +{ + int i; + AnimBlendFrameData *frame; + + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + numNodes = clumpData->numFrames; + AllocateAnimBlendNodeArray(numNodes); + for(i = 0; i < numNodes; i++) + nodes[i].association = this; + hierarchy = hier; + + // Init every node from a sequence and a Clump frame + // NB: This is where the order of nodes is defined + for(i = 0; i < hier->numSequences; i++){ + CAnimBlendSequence *seq = &hier->sequences[i]; + frame = RpAnimBlendClumpFindFrame(clump, seq->name); + if(frame && seq->numFrames > 0) + nodes[frame - clumpData->frames].sequence = seq; + } +} + +void +CAnimBlendAssociation::Init(CAnimBlendAssociation &assoc) +{ + int i; + + hierarchy = assoc.hierarchy; + numNodes = assoc.numNodes; + flags = assoc.flags; + animId = assoc.animId; + AllocateAnimBlendNodeArray(numNodes); + for(i = 0; i < numNodes; i++){ + nodes[i] = assoc.nodes[i]; + nodes[i].association = this; + } +} + +void +CAnimBlendAssociation::SetBlend(float amount, float delta) +{ + blendAmount = amount; + blendDelta = delta; +} + +void +CAnimBlendAssociation::SetFinishCallback(void (*cb)(CAnimBlendAssociation*, void*), void *arg) +{ + callbackType = CB_FINISH; + callback = cb; + callbackArg = arg; +} + +void +CAnimBlendAssociation::SetDeleteCallback(void (*cb)(CAnimBlendAssociation*, void*), void *arg) +{ + callbackType = CB_DELETE; + callback = cb; + callbackArg = arg; +} + +void +CAnimBlendAssociation::SetCurrentTime(float time) +{ + int i; + + for(currentTime = time; currentTime >= hierarchy->totalLength; currentTime -= hierarchy->totalLength) + if(!IsRepeating()) + return; + CAnimManager::UncompressAnimation(hierarchy); + for(i = 0; i < numNodes; i++) + if(nodes[i].sequence) + nodes[i].FindKeyFrame(currentTime); +} + +void +CAnimBlendAssociation::SyncAnimation(CAnimBlendAssociation *other) +{ + SetCurrentTime(other->currentTime/other->hierarchy->totalLength * hierarchy->totalLength); +} + +void +CAnimBlendAssociation::Start(float time) +{ + flags |= ASSOC_RUNNING; + SetCurrentTime(time); +} + +void +CAnimBlendAssociation::UpdateTime(float timeDelta, float relSpeed) +{ + if(!IsRunning()) + return; + + timeStep = (flags & ASSOC_MOVEMENT ? relSpeed*hierarchy->totalLength : speed) * timeDelta; + currentTime += timeStep; + + if(currentTime >= hierarchy->totalLength){ + // Ran past end + + if(IsRepeating()) + currentTime -= hierarchy->totalLength; + else{ + currentTime = hierarchy->totalLength; + flags &= ~ASSOC_RUNNING; + if(flags & ASSOC_FADEOUTWHENDONE){ + flags |= ASSOC_DELETEFADEDOUT; + blendDelta = -4.0f; + } + if(callbackType == CB_FINISH){ + callbackType = CB_NONE; + callback(this, callbackArg); + } + } + } +} + +// return whether we still exist after this function +bool +CAnimBlendAssociation::UpdateBlend(float timeDelta) +{ + blendAmount += blendDelta * timeDelta; + + if(blendAmount <= 0.0f && blendDelta < 0.0f){ + // We're faded out and are not fading in + blendAmount = 0.0f; + blendDelta = max(0.0, blendDelta); + if(flags & ASSOC_DELETEFADEDOUT){ + if(callbackType == CB_FINISH || callbackType == CB_DELETE) + callback(this, callbackArg); + delete this; + return false; + } + } + + if(blendAmount > 1.0f){ + // Maximally faded in, clamp values + blendAmount = 1.0f; + blendDelta = min(0.0, blendDelta); + } + + return true; +} + + +STARTPATCHES + InjectHook(0x4016A0, &CAnimBlendAssociation::AllocateAnimBlendNodeArray, PATCH_JUMP); + InjectHook(0x4016F0, &CAnimBlendAssociation::FreeAnimBlendNodeArray, PATCH_JUMP); + InjectHook(0x4017B0, &CAnimBlendAssociation::GetNode, PATCH_JUMP); + InjectHook(0x401560, (void (CAnimBlendAssociation::*)(RpClump*, CAnimBlendHierarchy*))&CAnimBlendAssociation::Init, PATCH_JUMP); + InjectHook(0x401620, (void (CAnimBlendAssociation::*)(CAnimBlendAssociation&))&CAnimBlendAssociation::Init, PATCH_JUMP); + InjectHook(0x4017E0, &CAnimBlendAssociation::SetBlend, PATCH_JUMP); + InjectHook(0x401820, &CAnimBlendAssociation::SetFinishCallback, PATCH_JUMP); + InjectHook(0x401800, &CAnimBlendAssociation::SetDeleteCallback, PATCH_JUMP); + InjectHook(0x401700, &CAnimBlendAssociation::SetCurrentTime, PATCH_JUMP); + InjectHook(0x401780, &CAnimBlendAssociation::SyncAnimation, PATCH_JUMP); + InjectHook(0x4017D0, &CAnimBlendAssociation::Start, PATCH_JUMP); + InjectHook(0x4031F0, &CAnimBlendAssociation::UpdateTime, PATCH_JUMP); + InjectHook(0x4032B0, &CAnimBlendAssociation::UpdateBlend, PATCH_JUMP); + + InjectHook(0x401460, &CAnimBlendAssociation::ctor1, PATCH_JUMP); + InjectHook(0x4014C0, &CAnimBlendAssociation::ctor2, PATCH_JUMP); + InjectHook(0x401520, &CAnimBlendAssociation::dtor, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/AnimBlendAssociation.h b/src/animation/AnimBlendAssociation.h new file mode 100644 index 00000000..7eec69a0 --- /dev/null +++ b/src/animation/AnimBlendAssociation.h @@ -0,0 +1,89 @@ +#pragma once + +#include "AnimBlendList.h" +#include "AnimBlendNode.h" + +class CAnimBlendHierarchy; + +enum { + // TODO + ASSOC_RUNNING = 1, + ASSOC_REPEAT = 2, + ASSOC_DELETEFADEDOUT = 4, + ASSOC_FADEOUTWHENDONE = 8, + ASSOC_PARTIAL = 0x10, + ASSOC_MOVEMENT = 0x20, // ??? + ASSOC_HAS_TRANSLATION = 0x40, + ASSOC_FLAG80 = 0x80, + ASSOC_FLAG100 = 0x100, + ASSOC_FLAG200 = 0x200, + ASSOC_FLAG400 = 0x400, // not seen yet + ASSOC_FLAG800 = 0x800, + ASSOC_HAS_X_TRANSLATION = 0x1000, +}; + +// Anim hierarchy associated with a clump +// Holds the interpolated state of all nodes. +// Also used as template for other clumps. +class CAnimBlendAssociation +{ +public: + enum { + // callbackType + CB_NONE, + CB_FINISH, + CB_DELETE + }; + + CAnimBlendLink link; + + int numNodes; // taken from CAnimBlendClumpData::numFrames + // NB: Order of these depends on order of nodes in Clump this was built from + CAnimBlendNode *nodes; + CAnimBlendHierarchy *hierarchy; + float blendAmount; + float blendDelta; // how much blendAmount changes over time + float currentTime; + float speed; + float timeStep; + int32 animId; + int32 flags; + int32 callbackType; + void (*callback)(CAnimBlendAssociation*, void*); + void *callbackArg; + + bool IsRunning(void) { return !!(flags & ASSOC_RUNNING); } + bool IsRepeating(void) { return !!(flags & ASSOC_REPEAT); } + bool IsPartial(void) { return !!(flags & ASSOC_PARTIAL); } + bool IsMovement(void) { return !!(flags & ASSOC_MOVEMENT); } + bool HasTranslation(void) { return !!(flags & ASSOC_HAS_TRANSLATION); } + bool HasXTranslation(void) { return !!(flags & ASSOC_HAS_X_TRANSLATION); } + + float GetBlendAmount(float weight) { return IsPartial() ? blendAmount : blendAmount*weight; } + CAnimBlendNode *GetNode(int i) { return &nodes[i]; } + + CAnimBlendAssociation(void); + CAnimBlendAssociation(CAnimBlendAssociation &other); + virtual ~CAnimBlendAssociation(void); + void AllocateAnimBlendNodeArray(int n); + void FreeAnimBlendNodeArray(void); + void Init(RpClump *clump, CAnimBlendHierarchy *hier); + void Init(CAnimBlendAssociation &assoc); + void SetBlend(float amount, float delta); + void SetFinishCallback(void (*callback)(CAnimBlendAssociation*, void*), void *arg); + void SetDeleteCallback(void (*callback)(CAnimBlendAssociation*, void*), void *arg); + void SetCurrentTime(float time); + void SyncAnimation(CAnimBlendAssociation *other); + void Start(float time); + void UpdateTime(float timeDelta, float relSpeed); + bool UpdateBlend(float timeDelta); + + static CAnimBlendAssociation *FromLink(CAnimBlendLink *l) { + return (CAnimBlendAssociation*)((uint8*)l - offsetof(CAnimBlendAssociation, link)); + } + + CAnimBlendAssociation *ctor1(void) { return ::new (this) CAnimBlendAssociation(); } + CAnimBlendAssociation *ctor2(CAnimBlendAssociation &other) { return ::new (this) CAnimBlendAssociation(other); } + void dtor(void) { this->CAnimBlendAssociation::~CAnimBlendAssociation(); } +}; +static_assert(sizeof(CAnimBlendAssociation) == 0x40, "CAnimBlendAssociation: error"); diff --git a/src/animation/AnimBlendClumpData.cpp b/src/animation/AnimBlendClumpData.cpp new file mode 100644 index 00000000..57985533 --- /dev/null +++ b/src/animation/AnimBlendClumpData.cpp @@ -0,0 +1,46 @@ +#include "common.h" +#include "patcher.h" +#include "AnimBlendClumpData.h" + +// TODO: implement those +#define RwFreeAlign RwFree +#define RwMallocAlign(sz, algn) RwMalloc(sz) + +CAnimBlendClumpData::CAnimBlendClumpData(void) +{ + numFrames = 0; + pedPosition = nil; + frames = nil; + link.Init(); +} + +CAnimBlendClumpData::~CAnimBlendClumpData(void) +{ + link.Remove(); + if(frames) + RwFreeAlign(frames); +} + +void +CAnimBlendClumpData::SetNumberOfFrames(int n) +{ + if(frames) + RwFreeAlign(frames); + numFrames = n; + frames = (AnimBlendFrameData*)RwMallocAlign(numFrames * sizeof(AnimBlendFrameData), 64); +} + +void +CAnimBlendClumpData::ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg) +{ + int i; + for(i = 0; i < numFrames; i++) + cb(&frames[i], arg); +} + +STARTPATCHES + InjectHook(0x401880, &CAnimBlendClumpData::ctor, PATCH_JUMP); + InjectHook(0x4018B0, &CAnimBlendClumpData::dtor, PATCH_JUMP); + InjectHook(0x4018F0, &CAnimBlendClumpData::SetNumberOfFrames, PATCH_JUMP); + InjectHook(0x401930, &CAnimBlendClumpData::ForAllFrames, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/AnimBlendClumpData.h b/src/animation/AnimBlendClumpData.h new file mode 100644 index 00000000..955578f0 --- /dev/null +++ b/src/animation/AnimBlendClumpData.h @@ -0,0 +1,57 @@ +#pragma once + +#include "AnimBlendList.h" + + +// TODO: put somewhere else +struct AnimBlendFrameData +{ + enum { + IGNORE_ROTATION = 2, + IGNORE_TRANSLATION = 4, + VELOCITY_EXTRACTION = 8, + VELOCITY_EXTRACTION_3D = 0x10, + }; + + uint8 flag; + RwV3d resetPos; +#ifdef PED_SKIN + union { + RwFrame *frame; + RpHAnimStdKeyFrame *hanimframe; + }; + int32 nodeID; +#else + RwFrame *frame; +#endif +}; +#ifndef PED_SKIN +static_assert(sizeof(AnimBlendFrameData) == 0x14, "AnimBlendFrameData: error"); +#endif + + +class CAnimBlendClumpData +{ +public: + CAnimBlendLink link; + int32 numFrames; +#ifdef PED_SKIN + int32 modelNumber; // doesn't seem to be used +#endif + CVector *pedPosition; + // order of frames is determined by RW hierarchy + AnimBlendFrameData *frames; + + CAnimBlendClumpData(void); + ~CAnimBlendClumpData(void); + void SetNumberOfFrames(int n); +#ifdef PED_SKIN + void SetNumberOfBones(int n) { SetNumberOfFrames(n); } +#endif + void ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg); + + + CAnimBlendClumpData *ctor(void) { return ::new (this) CAnimBlendClumpData(); } + void dtor(void) { this->CAnimBlendClumpData::~CAnimBlendClumpData(); } +}; +static_assert(sizeof(CAnimBlendClumpData) == 0x14, "CAnimBlendClumpData: error"); diff --git a/src/animation/AnimBlendHierarchy.cpp b/src/animation/AnimBlendHierarchy.cpp new file mode 100644 index 00000000..c7d7f198 --- /dev/null +++ b/src/animation/AnimBlendHierarchy.cpp @@ -0,0 +1,84 @@ +#include "common.h" +#include "patcher.h" +#include "AnimBlendSequence.h" +#include "AnimBlendHierarchy.h" + +CAnimBlendHierarchy::CAnimBlendHierarchy(void) +{ + sequences = nil; + numSequences = 0; + compressed = 0; + totalLength = 0.0f; + linkPtr = 0; +} + +void +CAnimBlendHierarchy::Shutdown(void) +{ + RemoveAnimSequences(); + compressed = 0; + linkPtr = nil; +} + +void +CAnimBlendHierarchy::SetName(char *name) +{ + strncpy(this->name, name, 24); +} + +void +CAnimBlendHierarchy::CalcTotalTime(void) +{ + int i, j; + float totalTime = 0.0f; + + for(i = 0; i < numSequences; i++){ + float seqTime = 0.0f; + for(j = 0; j < sequences[i].numFrames; j++) + seqTime += sequences[i].GetKeyFrame(j)->deltaTime; + totalTime = max(totalTime, seqTime); + } + totalLength = totalTime; +} + +void +CAnimBlendHierarchy::RemoveQuaternionFlips(void) +{ + int i; + + for(i = 0; i < numSequences; i++) + sequences[i].RemoveQuaternionFlips(); +} + +void +CAnimBlendHierarchy::RemoveAnimSequences(void) +{ + if(sequences) + delete[] sequences; + numSequences = 0; +} + +void +CAnimBlendHierarchy::Uncompress(void) +{ + if(totalLength == 0.0f) + CalcTotalTime(); + compressed = 0; +} + +void +CAnimBlendHierarchy::RemoveUncompressedData(void) +{ + // useless + compressed = 1; +} + +STARTPATCHES + InjectHook(0x4019A0, &CAnimBlendHierarchy::Shutdown, PATCH_JUMP); + InjectHook(0x4019C0, &CAnimBlendHierarchy::SetName, PATCH_JUMP); + InjectHook(0x4019E0, &CAnimBlendHierarchy::CalcTotalTime, PATCH_JUMP); + InjectHook(0x401A80, &CAnimBlendHierarchy::RemoveQuaternionFlips, PATCH_JUMP); + InjectHook(0x401AB0, &CAnimBlendHierarchy::RemoveAnimSequences, PATCH_JUMP); + InjectHook(0x401AD0, &CAnimBlendHierarchy::Uncompress, PATCH_JUMP); + InjectHook(0x401B00, &CAnimBlendHierarchy::RemoveUncompressedData, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/AnimBlendHierarchy.h b/src/animation/AnimBlendHierarchy.h new file mode 100644 index 00000000..917e1585 --- /dev/null +++ b/src/animation/AnimBlendHierarchy.h @@ -0,0 +1,27 @@ +#pragma once + +#include "templates.h" + +class CAnimBlendSequence; + +// A collection of sequences +class CAnimBlendHierarchy +{ +public: + char name[24]; + CAnimBlendSequence *sequences; + int16 numSequences; + int16 compressed; // not really used + float totalLength; + CLink *linkPtr; + + CAnimBlendHierarchy(void); + void Shutdown(void); + void SetName(char *name); + void CalcTotalTime(void); + void RemoveQuaternionFlips(void); + void RemoveAnimSequences(void); + void Uncompress(void); + void RemoveUncompressedData(void); +}; +static_assert(sizeof(CAnimBlendHierarchy) == 0x28, "CAnimBlendHierarchy: error"); diff --git a/src/animation/AnimBlendList.h b/src/animation/AnimBlendList.h new file mode 100644 index 00000000..d4b9a64a --- /dev/null +++ b/src/animation/AnimBlendList.h @@ -0,0 +1,27 @@ +#pragma once + +// name made up +class CAnimBlendLink +{ +public: + CAnimBlendLink *next; + CAnimBlendLink *prev; + + void Init(void){ + next = nil; + prev = nil; + } + void Prepend(CAnimBlendLink *link){ + if(next) + next->prev = link; + link->next = next; + link->prev = this; + next = link; + } + void Remove(void){ + if(prev) + prev->next = next; + if(next) + next->prev = prev; + } +}; diff --git a/src/animation/AnimBlendNode.cpp b/src/animation/AnimBlendNode.cpp new file mode 100644 index 00000000..3d3d80ec --- /dev/null +++ b/src/animation/AnimBlendNode.cpp @@ -0,0 +1,170 @@ +#include "common.h" +#include "patcher.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendNode.h" + +void +CAnimBlendNode::Init(void) +{ + frameA = 0; + frameB = 0; + remainingTime = 0.0f; + sequence = nil; + association = nil; +} + +bool +CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight) +{ + bool looped = false; + + trans = CVector(0.0f, 0.0f, 0.0f); + rot = CQuaternion(0.0f, 0.0f, 0.0f, 0.0f); + + if(association->IsRunning()){ + remainingTime -= association->timeStep; + if(remainingTime <= 0.0f) + looped = NextKeyFrame(); + } + + float blend = association->GetBlendAmount(weight); + if(blend > 0.0f){ + KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA); + KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB); + float t = kfA->deltaTime == 0.0f ? 0.0f : (kfA->deltaTime - remainingTime)/kfA->deltaTime; + if(sequence->type & CAnimBlendSequence::KF_TRANS){ + trans = kfB->translation + t*(kfA->translation - kfB->translation); + trans *= blend; + } + if(sequence->type & CAnimBlendSequence::KF_ROT){ + rot.Slerp(kfB->rotation, kfA->rotation, theta, invSin, t); + rot *= blend; + } + } + + return looped; +} + +bool +CAnimBlendNode::NextKeyFrame(void) +{ + bool looped; + + if(sequence->numFrames <= 1) + return false; + + looped = false; + frameB = frameA; + + // Advance as long as we have to + while(remainingTime <= 0.0f){ + frameA++; + + if(frameA >= sequence->numFrames){ + // reached end of animation + if(!association->IsRepeating()){ + frameA--; + remainingTime = 0.0f; + return false; + } + looped = true; + frameA = 0; + } + + remainingTime += sequence->GetKeyFrame(frameA)->deltaTime; + } + + frameB = frameA - 1; + if(frameB < 0) + frameB += sequence->numFrames; + + CalcDeltas(); + return looped; +} + +// Set animation to time t +bool +CAnimBlendNode::FindKeyFrame(float t) +{ + if(sequence->numFrames < 1) + return false; + + frameA = 0; + frameB = frameA; + + if(sequence->numFrames >= 2){ + frameA++; + + // advance until t is between frameB and frameA + while(t > sequence->GetKeyFrame(frameA)->deltaTime){ + t -= sequence->GetKeyFrame(frameA)->deltaTime; + frameB = frameA++; + if(frameA >= sequence->numFrames){ + // reached end of animation + if(!association->IsRepeating()) + return false; + frameA = 0; + frameB = 0; + } + } + + remainingTime = sequence->GetKeyFrame(frameA)->deltaTime - t; + } + + CalcDeltas(); + return true; +} + +void +CAnimBlendNode::CalcDeltas(void) +{ + if((sequence->type & CAnimBlendSequence::KF_ROT) == 0) + return; + KeyFrame *kfA = sequence->GetKeyFrame(frameA); + KeyFrame *kfB = sequence->GetKeyFrame(frameB); + float cos = DotProduct(kfA->rotation, kfB->rotation); + if(cos > 1.0f) + cos = 1.0f; + theta = acos(cos); + invSin = theta == 0.0f ? 0.0f : 1.0f/sin(theta); +} + +void +CAnimBlendNode::GetCurrentTranslation(CVector &trans, float weight) +{ + trans = CVector(0.0f, 0.0f, 0.0f); + + float blend = association->GetBlendAmount(weight); + if(blend > 0.0f){ + KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA); + KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB); + float t = (kfA->deltaTime - remainingTime)/kfA->deltaTime; + if(sequence->type & CAnimBlendSequence::KF_TRANS){ + trans = kfB->translation + t*(kfA->translation - kfB->translation); + trans *= blend; + } + } +} + +void +CAnimBlendNode::GetEndTranslation(CVector &trans, float weight) +{ + trans = CVector(0.0f, 0.0f, 0.0f); + + float blend = association->GetBlendAmount(weight); + if(blend > 0.0f){ + KeyFrameTrans *kf = (KeyFrameTrans*)sequence->GetKeyFrame(sequence->numFrames-1); + if(sequence->type & CAnimBlendSequence::KF_TRANS) + trans = kf->translation * blend; + } +} + +STARTPATCHES + InjectHook(0x401B10, &CAnimBlendNode::Init, PATCH_JUMP); + InjectHook(0x401B30, &CAnimBlendNode::Update, PATCH_JUMP); + InjectHook(0x401DC0, &CAnimBlendNode::NextKeyFrame, PATCH_JUMP); + InjectHook(0x4021B0, &CAnimBlendNode::FindKeyFrame, PATCH_JUMP); + InjectHook(0x401E70, &CAnimBlendNode::CalcDeltas, PATCH_JUMP); + InjectHook(0x401FE0, &CAnimBlendNode::GetCurrentTranslation, PATCH_JUMP); + InjectHook(0x402110, &CAnimBlendNode::GetEndTranslation, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/AnimBlendNode.h b/src/animation/AnimBlendNode.h new file mode 100644 index 00000000..ea75fbfa --- /dev/null +++ b/src/animation/AnimBlendNode.h @@ -0,0 +1,29 @@ +#pragma once + +#include "AnimBlendSequence.h" + +class CAnimBlendAssociation; + +// The interpolated state between two key frames in a sequence +class CAnimBlendNode +{ +public: + // for slerp + float theta; // angle between quaternions + float invSin; // 1/sin(theta) + // indices into array in sequence + int32 frameA; // next key frame + int32 frameB; // previous key frame + float remainingTime; // time until frames have to advance + CAnimBlendSequence *sequence; + CAnimBlendAssociation *association; + + void Init(void); + bool Update(CVector &trans, CQuaternion &rot, float weight); + bool NextKeyFrame(void); + bool FindKeyFrame(float t); + void CalcDeltas(void); + void GetCurrentTranslation(CVector &trans, float weight); + void GetEndTranslation(CVector &trans, float weight); +}; +static_assert(sizeof(CAnimBlendNode) == 0x1C, "CAnimBlendNode: error"); diff --git a/src/animation/AnimBlendSequence.cpp b/src/animation/AnimBlendSequence.cpp new file mode 100644 index 00000000..4b675774 --- /dev/null +++ b/src/animation/AnimBlendSequence.cpp @@ -0,0 +1,68 @@ +#include "common.h" +#include "patcher.h" +#include "AnimBlendSequence.h" + +CAnimBlendSequence::CAnimBlendSequence(void) +{ + type = 0; + numFrames = 0; + keyFrames = nil; + keyFramesCompressed = nil; +#ifdef PED_SKIN + boneTag = -1; +#endif +} + +CAnimBlendSequence::~CAnimBlendSequence(void) +{ + if(keyFrames) + RwFree(keyFrames); +} + +void +CAnimBlendSequence::SetName(char *name) +{ + strncpy(this->name, name, 24); +} + +void +CAnimBlendSequence::SetNumFrames(int numFrames, bool translation) +{ + int sz; + + if(translation){ + sz = sizeof(KeyFrameTrans); + type |= KF_ROT | KF_TRANS; + }else{ + sz = sizeof(KeyFrame); + type |= KF_ROT; + } + keyFrames = RwMalloc(sz * numFrames); + this->numFrames = numFrames; +} + +void +CAnimBlendSequence::RemoveQuaternionFlips(void) +{ + int i; + CQuaternion last; + KeyFrame *frame; + + if(numFrames < 2) + return; + + frame = GetKeyFrame(0); + last = frame->rotation; + for(i = 1; i < numFrames; i++){ + frame = GetKeyFrame(i); + if(DotProduct(last, frame->rotation) < 0.0f) + frame->rotation = -frame->rotation; + last = frame->rotation; + } +} + +STARTPATCHES + InjectHook(0x402330, &CAnimBlendSequence::SetName, PATCH_JUMP); + InjectHook(0x402350, &CAnimBlendSequence::SetNumFrames, PATCH_JUMP); + InjectHook(0x4023A0, &CAnimBlendSequence::RemoveQuaternionFlips, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/AnimBlendSequence.h b/src/animation/AnimBlendSequence.h new file mode 100644 index 00000000..7538cf56 --- /dev/null +++ b/src/animation/AnimBlendSequence.h @@ -0,0 +1,55 @@ +#pragma once + +#include "math/Quaternion.h" + +// TODO: put them somewhere else? +struct KeyFrame { + CQuaternion rotation; + float deltaTime; // relative to previous key frame +}; + +struct KeyFrameTrans : KeyFrame { + CVector translation; +}; + + +// The sequence of key frames of one animated node +class CAnimBlendSequence +{ +public: + enum { + KF_ROT = 1, + KF_TRANS = 2 + }; + int32 type; + char name[24]; + int32 numFrames; +#ifdef PED_SKIN + int16 boneTag; +#endif + void *keyFrames; + void *keyFramesCompressed; + + CAnimBlendSequence(void); + virtual ~CAnimBlendSequence(void); + void SetName(char *name); + void SetNumFrames(int numFrames, bool translation); + void RemoveQuaternionFlips(void); + KeyFrame *GetKeyFrame(int n) { + return type & KF_TRANS ? + &((KeyFrameTrans*)keyFrames)[n] : + &((KeyFrame*)keyFrames)[n]; + } + bool HasTranslation(void) { return !!(type & KF_TRANS); } + // TODO? these are unused +// void Uncompress(void); +// void CompressKeyframes(void); +// void RemoveUncompressedData(void); + +#ifdef PED_SKIN + void SetBoneTag(int tag) { boneTag = tag; } +#endif +}; +#ifndef PED_SKIN +static_assert(sizeof(CAnimBlendSequence) == 0x2C, "CAnimBlendSequence: error"); +#endif diff --git a/src/animation/AnimManager.cpp b/src/animation/AnimManager.cpp new file mode 100644 index 00000000..73fc4d94 --- /dev/null +++ b/src/animation/AnimManager.cpp @@ -0,0 +1,930 @@ +#include "common.h" +#include "patcher.h" +#include "ModelInfo.h" +#include "ModelIndices.h" +#include "FileMgr.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" +#include "AnimManager.h" + +CAnimBlock *CAnimManager::ms_aAnimBlocks = (CAnimBlock*)0x6F01A0; +CAnimBlendHierarchy *CAnimManager::ms_aAnimations = (CAnimBlendHierarchy*)0x70F430; +int32 &CAnimManager::ms_numAnimBlocks = *(int32*)0x885AF8; +int32 &CAnimManager::ms_numAnimations = *(int32*)0x8E2DD4; +CAnimBlendAssocGroup *&CAnimManager::ms_aAnimAssocGroups = *(CAnimBlendAssocGroup**)0x8F583C; +CLinkList &CAnimManager::ms_animCache = *(CLinkList*)0x9414DC; + +AnimAssocDesc aStdAnimDescs[] = { + { ANIM_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_FLAG80 }, + { ANIM_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_FLAG80 }, + { ANIM_SPRINT, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_FLAG80 }, + { ANIM_IDLE_STANCE, ASSOC_REPEAT }, + { ANIM_WALK_START, ASSOC_HAS_TRANSLATION }, + { ANIM_RUN_STOP, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION }, + { ANIM_RUN_STOP_R, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION }, + { ANIM_IDLE_CAM, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_IDLE_HBHB, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_IDLE_TIRED, ASSOC_REPEAT }, + { ANIM_IDLE_ARMED, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_IDLE_CHAT, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_IDLE_TAXI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_KO_SHOT_FRONT1, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FLAG800 }, + { ANIM_KO_SHOT_FRONT2, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FLAG800 }, + { ANIM_KO_SHOT_FRONT3, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FLAG800 }, + { ANIM_KO_SHOT_FRONT4, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FLAG800 }, + { ANIM_KO_SHOT_FACE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FLAG800 }, + { ANIM_KO_SHOT_STOM, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_KO_SHOT_ARML, ASSOC_PARTIAL | ASSOC_FLAG800 }, + { ANIM_KO_SHOT_ARMR, ASSOC_PARTIAL | ASSOC_FLAG800 }, + { ANIM_KO_SHOT_LEGL, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_KO_SHOT_LEGR, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_KD_LEFT, ASSOC_PARTIAL | ASSOC_FLAG800 }, + { ANIM_KD_RIGHT, ASSOC_PARTIAL | ASSOC_FLAG800 }, + { ANIM_KO_SKID_FRONT, ASSOC_PARTIAL }, + { ANIM_KO_SPIN_R, ASSOC_PARTIAL }, + { ANIM_KO_SKID_BACK, ASSOC_PARTIAL | ASSOC_FLAG800 }, + { ANIM_KO_SPIN_L, ASSOC_PARTIAL }, + { ANIM_SHOT_FRONT_PARTIAL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_SHOT_LEFT_PARTIAL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_SHOT_BACK_PARTIAL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_SHOT_RIGHT_PARTIAL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_HIT_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_HIT_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_HIT_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_HIT_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FLOOR_HIT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_HIT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_HIT_CHEST, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_HIT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_HIT_WALK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_HIT_WALL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_FLOOR_HIT_F, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_FLAG800 }, + { ANIM_HIT_BEHIND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_PUNCH_R, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_KICK_FLOOR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_BAT_H, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_BAT_V, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_HGUN_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_WEAPON_AK_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_SNIPER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_THROWU, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_START_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_BOMBER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_HGUN_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_AK_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_FPS_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_BAT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_UZI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_AK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_M16, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_ROCKET, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_IDLE, ASSOC_REPEAT }, + { ANIM_FIGHT2_IDLE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_SH_F, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_FIGHT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_KICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_KNEE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_LHOOK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FIGHT_ROUNDHOUSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_FIGHT_LONGKICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_FIGHT_PPUNCH, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_CAR_JACKED_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_CAR_LJACKED_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_CAR_JACKED_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_CAR_LJACKED_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_CAR_QJACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_QJACKED, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_CAR_ALIGN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_ALIGNHI_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_OPEN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_DOORLOCKED_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_PULLOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_PULLOUT_LOW_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_GETIN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_GETIN_LOW_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CLOSEDOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CLOSEDOOR_LOW_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_ROLLDOOR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_ROLLDOOR_LOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_GETOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_GETOUT_LOW_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CLOSE_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_ALIGN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_ALIGNHI_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_OPEN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_DOORLOCKED_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_PULLOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_PULLOUT_LOW_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_GETIN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_GETIN_LOW_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CLOSEDOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CLOSEDOOR_LOW_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_SHUFFLE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_LSHUFFLE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_SIT, ASSOC_DELETEFADEDOUT }, + { ANIM_CAR_LSIT, ASSOC_DELETEFADEDOUT }, + { ANIM_CAR_SITP, ASSOC_DELETEFADEDOUT }, + { ANIM_CAR_SITPLO, ASSOC_DELETEFADEDOUT }, + { ANIM_DRIVE_L, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_DRIVE_R, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_DRIVE_LOW_L, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_DRIVE_LOW_R, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_DRIVEBY_L, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_DRIVEBY_R, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_CAR_LB, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_DRIVE_BOAT, ASSOC_DELETEFADEDOUT }, + { ANIM_CAR_GETOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_GETOUT_LOW_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CLOSE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_HOOKERTALK, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_COACH_OPEN_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_COACH_OPEN_R, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_COACH_IN_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_COACH_IN_R, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_COACH_OUT_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_TRAIN_GETIN, ASSOC_PARTIAL }, + { ANIM_TRAIN_GETOUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CRAWLOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_CAR_CRAWLOUT_RHS2, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_OPEN_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_GETIN_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_CLOSE_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_GETOUT_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_OPEN, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_GETIN, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_CLOSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_VAN_GETOUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_GETUP1, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_GETUP2, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_GETUP3, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_GETUP_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_JUMP_LAUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_JUMP_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_JUMP_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_FALL_FALL, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_FALL_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_FALL_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_FALL_COLLAPSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_EV_STEP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_EV_DIVE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FLAG800 }, + { ANIM_XPRESS_SCRATCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG100 }, + { ANIM_ROAD_CROSS, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_TURN_180, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_ARREST_GUN, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_DROWN, ASSOC_PARTIAL }, + { ANIM_CPR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_DUCK_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_DUCK_LOW, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_RBLOCK_CSHOOT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_WEAPON_THROWU, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_HANDSUP, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_HANDSCOWER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_FUCKU, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_FLAG200 }, + { ANIM_PHONE_IN, ASSOC_PARTIAL }, + { ANIM_PHONE_OUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_PHONE_TALK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, +}; +AnimAssocDesc aStdAnimDescsSide[] = { + { ANIM_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_FLAG80 | ASSOC_HAS_X_TRANSLATION }, + { ANIM_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_FLAG80 | ASSOC_HAS_X_TRANSLATION }, + { ANIM_SPRINT, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_FLAG80 | ASSOC_HAS_X_TRANSLATION }, + { ANIM_IDLE_STANCE, ASSOC_REPEAT }, + { ANIM_WALK_START, ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, +}; +char *aStdAnimations[] = { + "walk_civi", + "run_civi", + "sprint_panic", + "idle_stance", + "walk_start", + "run_stop", + "run_stopR", + "idle_cam", + "idle_hbhb", + "idle_tired", + "idle_armed", + "idle_chat", + "idle_taxi", + "KO_shot_front", + "KO_shot_front", + "KO_shot_front", + "KO_shot_front", + "KO_shot_face", + "KO_shot_stom", + "KO_shot_arml", + "KO_shot_armR", + "KO_shot_legl", + "KO_shot_legR", + "KD_left", + "KD_right", + "KO_skid_front", + "KO_spin_R", + "KO_skid_back", + "KO_spin_L", + "SHOT_partial", + "SHOT_leftP", + "SHOT_partial", + "SHOT_rightP", + "HIT_front", + "HIT_L", + "HIT_back", + "HIT_R", + "FLOOR_hit", + "HIT_bodyblow", + "HIT_chest", + "HIT_head", + "HIT_walk", + "HIT_wall", + "FLOOR_hit_f", + "HIT_behind", + "punchR", + "KICK_floor", + "WEAPON_bat_h", + "WEAPON_bat_v", + "WEAPON_hgun_body", + "WEAPON_AK_body", + "WEAPON_pump", + "WEAPON_sniper", + "WEAPON_throw", + "WEAPON_throwu", + "WEAPON_start_throw", + "bomber", + "WEAPON_hgun_rload", + "WEAPON_AK_rload", + "FPS_PUNCH", + "FPS_BAT", + "FPS_UZI", + "FPS_PUMP", + "FPS_AK", + "FPS_M16", + "FPS_ROCKET", + "FIGHTIDLE", + "FIGHT2IDLE", + "FIGHTsh_F", + "FIGHTbodyblow", + "FIGHThead", + "FIGHTkick", + "FIGHTknee", + "FIGHTLhook", + "FIGHTpunch", + "FIGHTrndhse", + "FIGHTlngkck", + "FIGHTppunch", + "car_jackedRHS", + "car_LjackedRHS", + "car_jackedLHS", + "car_LjackedLHS", + "CAR_Qjack", + "CAR_Qjacked", + "CAR_align_LHS", + "CAR_alignHI_LHS", + "CAR_open_LHS", + "CAR_doorlocked_LHS", + "CAR_pullout_LHS", + "CAR_pulloutL_LHS", + "CAR_getin_LHS", + "CAR_getinL_LHS", + "CAR_closedoor_LHS", + "CAR_closedoorL_LHS", + "CAR_rolldoor", + "CAR_rolldoorLO", + "CAR_getout_LHS", + "CAR_getoutL_LHS", + "CAR_close_LHS", + "CAR_align_RHS", + "CAR_alignHI_RHS", + "CAR_open_RHS", + "CAR_doorlocked_RHS", + "CAR_pullout_RHS", + "CAR_pulloutL_RHS", + "CAR_getin_RHS", + "CAR_getinL_RHS", + "CAR_closedoor_RHS", + "CAR_closedoorL_RHS", + "CAR_shuffle_RHS", + "CAR_Lshuffle_RHS", + "CAR_sit", + "CAR_Lsit", + "CAR_sitp", + "CAR_sitpLO", + "DRIVE_L", + "Drive_R", + "Drive_LO_l", + "Drive_LO_R", + "Driveby_L", + "Driveby_R", + "CAR_LB", + "DRIVE_BOAT", + "CAR_getout_RHS", + "CAR_getoutL_RHS", + "CAR_close_RHS", + "car_hookertalk", + "COACH_opnL", + "COACH_opnR", + "COACH_inL", + "COACH_inR", + "COACH_outL", + "TRAIN_getin", + "TRAIN_getout", + "CAR_crawloutRHS", + "CAR_crawloutRHS", + "VAN_openL", + "VAN_getinL", + "VAN_closeL", + "VAN_getoutL", + "VAN_open", + "VAN_getin", + "VAN_close", + "VAN_getout", + "Getup", + "Getup", + "Getup", + "Getup_front", + "JUMP_launch", + "JUMP_glide", + "JUMP_land", + "FALL_fall", + "FALL_glide", + "FALL_land", + "FALL_collapse", + "EV_step", + "EV_dive", + "XPRESSscratch", + "roadcross", + "TURN_180", + "ARRESTgun", + "DROWN", + "CPR", + "DUCK_down", + "DUCK_low", + "RBLOCK_Cshoot", + "WEAPON_throwu", + "handsup", + "handsCOWER", + "FUCKU", + "PHONE_in", + "PHONE_out", + "PHONE_talk", +}; +char *aPlayerAnimations[] = { + "walk_player", + "run_player", + "SPRINT_civi", + "IDLE_STANCE", + "walk_start", +}; +char *aPlayerWithRocketAnimations[] = { + "walk_rocket", + "run_rocket", + "run_rocket", + "idle_rocket", + "walk_start_rocket", +}; +char *aPlayer1ArmedAnimations[] = { + "walk_player", + "run_1armed", + "SPRINT_civi", + "IDLE_STANCE", + "walk_start", +}; +char *aPlayer2ArmedAnimations[] = { + "walk_player", + "run_armed", + "run_armed", + "idle_stance", + "walk_start", +}; +char *aPlayerBBBatAnimations[] = { + "walk_player", + "run_player", + "run_player", + "IDLE_STANCE", + "walk_start", +}; +char *aShuffleAnimations[] = { + "WALK_shuffle", + "RUN_civi", + "SPRINT_civi", + "IDLE_STANCE", +}; +char *aOldAnimations[] = { + "walk_old", + "run_civi", + "sprint_civi", + "idle_stance", +}; +char *aGang1Animations[] = { + "walk_gang1", + "run_gang1", + "sprint_civi", + "idle_stance", +}; +char *aGang2Animations[] = { + "walk_gang2", + "run_gang1", + "sprint_civi", + "idle_stance", +}; +char *aFatAnimations[] = { + "walk_fat", + "run_civi", + "woman_runpanic", + "idle_stance", +}; +char *aOldFatAnimations[] = { + "walk_fatold", + "run_fatold", + "woman_runpanic", + "idle_stance", +}; +char *aStdWomanAnimations[] = { + "woman_walknorm", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char *aWomanShopAnimations[] = { + "woman_walkshop", + "woman_run", + "woman_run", + "woman_idlestance", +}; +char *aBusyWomanAnimations[] = { + "woman_walkbusy", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char *aSexyWomanAnimations[] = { + "woman_walksexy", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char *aOldWomanAnimations[] = { + "woman_walkold", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char *aFatWomanAnimations[] = { + "walk_fat", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char *aPanicChunkyAnimations[] = { + "run_fatold", + "woman_runpanic", + "woman_runpanic", + "idle_stance", +}; +char *aPlayerStrafeBackAnimations[] = { + "walk_player_back", + "run_player_back", + "run_player_back", + "IDLE_STANCE", + "walk_start_back", +}; +char *aPlayerStrafeLeftAnimations[] = { + "walk_player_left", + "run_left", + "run_left", + "IDLE_STANCE", + "walk_start_left", +}; +char *aPlayerStrafeRightAnimations[] = { + "walk_player_right", + "run_right", + "run_right", + "IDLE_STANCE", + "walk_start_right", +}; +char *aRocketStrafeBackAnimations[] = { + "walk_rocket_back", + "run_rocket_back", + "run_rocket_back", + "idle_rocket", + "walkst_rocket_back", +}; +char *aRocketStrafeLeftAnimations[] = { + "walk_rocket_left", + "run_rocket_left", + "run_rocket_left", + "idle_rocket", + "walkst_rocket_left", +}; +char *aRocketStrafeRightAnimations[] = { + "walk_rocket_right", + "run_rocket_right", + "run_rocket_right", + "idle_rocket", + "walkst_rocket_right", +}; +AnimAssocDefinition CAnimManager::ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS] = { + { "man", "ped", MI_COP, 173, aStdAnimations, aStdAnimDescs }, + { "player", "ped", MI_COP, 5, aPlayerAnimations, aStdAnimDescs }, + { "playerrocket", "ped", MI_COP, 5, aPlayerWithRocketAnimations, aStdAnimDescs }, + { "player1armed", "ped", MI_COP, 5, aPlayer1ArmedAnimations, aStdAnimDescs }, + { "player2armed", "ped", MI_COP, 5, aPlayer2ArmedAnimations, aStdAnimDescs }, + { "playerBBBat", "ped", MI_COP, 5, aPlayerBBBatAnimations, aStdAnimDescs }, + { "shuffle", "ped", MI_COP, 4, aShuffleAnimations, aStdAnimDescs }, + { "oldman", "ped", MI_COP, 4, aOldAnimations, aStdAnimDescs }, + { "gang1", "ped", MI_COP, 4, aGang1Animations, aStdAnimDescs }, + { "gang2", "ped", MI_COP, 4, aGang2Animations, aStdAnimDescs }, + { "fatman", "ped", MI_COP, 4, aFatAnimations, aStdAnimDescs }, + { "oldfatman", "ped", MI_COP, 4, aOldFatAnimations, aStdAnimDescs }, + { "woman", "ped", MI_COP, 4, aStdWomanAnimations, aStdAnimDescs }, + { "shopping", "ped", MI_COP, 4, aWomanShopAnimations, aStdAnimDescs }, + { "busywoman", "ped", MI_COP, 4, aBusyWomanAnimations, aStdAnimDescs }, + { "sexywoman", "ped", MI_COP, 4, aSexyWomanAnimations, aStdAnimDescs }, + { "oldwoman", "ped", MI_COP, 4, aOldWomanAnimations, aStdAnimDescs }, + { "fatwoman", "ped", MI_COP, 4, aFatWomanAnimations, aStdAnimDescs }, + { "panicchunky", "ped", MI_COP, 4, aPanicChunkyAnimations, aStdAnimDescs }, + { "playerback", "ped", MI_COP, 5, aPlayerStrafeBackAnimations, aStdAnimDescs }, + { "playerleft", "ped", MI_COP, 5, aPlayerStrafeLeftAnimations, aStdAnimDescsSide }, + { "playerright", "ped", MI_COP, 5, aPlayerStrafeRightAnimations, aStdAnimDescsSide }, + { "rocketback", "ped", MI_COP, 5, aRocketStrafeBackAnimations, aStdAnimDescs }, + { "rocketleft", "ped", MI_COP, 5, aRocketStrafeLeftAnimations, aStdAnimDescsSide }, + { "rocketright", "ped", MI_COP, 5, aRocketStrafeRightAnimations, aStdAnimDescsSide }, +}; + +void +CAnimManager::Initialise(void) +{ + ms_numAnimations = 0; + ms_numAnimBlocks = 0; + ms_animCache.Init(25); + +// dumpanimdata(); +} + +void +CAnimManager::Shutdown(void) +{ + int i; + + ms_animCache.Shutdown(); + + for(i = 0; i < ms_numAnimations; i++) + ms_aAnimations[i].Shutdown(); + + delete[] ms_aAnimAssocGroups; +} + +void +CAnimManager::UncompressAnimation(CAnimBlendHierarchy *hier) +{ + if(!hier->compressed){ + if(hier->linkPtr){ + hier->linkPtr->Remove(); + ms_animCache.head.Insert(hier->linkPtr); + } + }else{ + CLink *link = ms_animCache.Insert(hier); + if(link == nil){ + ms_animCache.tail.prev->item->RemoveUncompressedData(); + ms_animCache.Remove(ms_animCache.tail.prev); + link = ms_animCache.Insert(hier); + } + hier->linkPtr = link; + hier->Uncompress(); + } +} + +CAnimBlock* +CAnimManager::GetAnimationBlock(const char *name) +{ + int i; + + for(i = 0; i < ms_numAnimBlocks; i++) + if(strcmpi(ms_aAnimBlocks[i].name, name) == 0) + return &ms_aAnimBlocks[i]; + return nil; +} + +CAnimBlendHierarchy* +CAnimManager::GetAnimation(const char *name, CAnimBlock *animBlock) +{ + int i; + CAnimBlendHierarchy *hier = &ms_aAnimations[animBlock->firstIndex]; + + for(i = 0; i < animBlock->numAnims; i++){ + if(strcmpi(hier->name, name) == 0) + return hier; + hier++; + } + return nil; +} + +const char* +CAnimManager::GetAnimGroupName(AssocGroupId groupId) +{ + return ms_aAnimAssocDefinitions[groupId].name; +} + +CAnimBlendAssociation* +CAnimManager::CreateAnimAssociation(AssocGroupId groupId, AnimationId animId) +{ + return ms_aAnimAssocGroups[groupId].CopyAnimation(animId); +} + +CAnimBlendAssociation* +CAnimManager::GetAnimAssociation(AssocGroupId groupId, AnimationId animId) +{ + return ms_aAnimAssocGroups[groupId].GetAnimation(animId); +} + +CAnimBlendAssociation* +CAnimManager::GetAnimAssociation(AssocGroupId groupId, const char *name) +{ + return ms_aAnimAssocGroups[groupId].GetAnimation(name); +} + +CAnimBlendAssociation* +CAnimManager::AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId) +{ + CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId); + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if(anim->IsMovement()){ + CAnimBlendAssociation *syncanim = nil; + CAnimBlendLink *link; + for(link = clumpData->link.next; link; link = link->next){ + syncanim = CAnimBlendAssociation::FromLink(link); + if(syncanim->IsMovement()) + break; + } + if(link){ + anim->SyncAnimation(syncanim); + anim->flags |= ASSOC_RUNNING; + }else + anim->Start(0.0f); + }else + anim->Start(0.0f); + + clumpData->link.Prepend(&anim->link); + return anim; +} + +CAnimBlendAssociation* +CAnimManager::AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId) +{ + CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId); + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if (anim->IsMovement() && syncanim){ + anim->SyncAnimation(syncanim); + anim->flags |= ASSOC_RUNNING; + }else + anim->Start(0.0f); + + clumpData->link.Prepend(&anim->link); + return anim; +} + +CAnimBlendAssociation* +CAnimManager::BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta) +{ + int removePrevAnim = 0; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + CAnimBlendAssociation *anim = GetAnimAssociation(groupId, animId); + bool isMovement = anim->IsMovement(); + bool isPartial = anim->IsPartial(); + CAnimBlendLink *link; + CAnimBlendAssociation *found = nil, *movementAnim = nil; + for(link = clumpData->link.next; link; link = link->next){ + anim = CAnimBlendAssociation::FromLink(link); + if(isMovement && anim->IsMovement()) + movementAnim = anim; + if(anim->animId == animId) + found = anim; + else{ + if(isPartial == anim->IsPartial()){ + if(anim->blendAmount > 0.0f){ + float blendDelta = -delta*anim->blendAmount; + if(blendDelta < anim->blendDelta || !isPartial) + anim->blendDelta = blendDelta; + }else{ + anim->blendDelta = -1.0f; + } + anim->flags |= ASSOC_DELETEFADEDOUT; + removePrevAnim = 1; + } + } + } + if(found){ + found->blendDelta = (1.0f - found->blendAmount)*delta; + if(!found->IsRunning() && found->currentTime == found->hierarchy->totalLength) + found->Start(0.0f); + }else{ + found = AddAnimationAndSync(clump, movementAnim, groupId, animId); + if(!removePrevAnim && !isPartial){ + found->blendAmount = 1.0f; + return found; + } + found->blendAmount = 0.0f; + found->blendDelta = delta; + } + UncompressAnimation(found->hierarchy); + return found; +} + +void +CAnimManager::LoadAnimFiles(void) +{ + int i, j; + + LoadAnimFile("ANIM\\PED.IFP"); + + // Create all assoc groups + ms_aAnimAssocGroups = new CAnimBlendAssocGroup[NUM_ANIM_ASSOC_GROUPS]; + for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(ms_aAnimAssocDefinitions[i].modelIndex); + RpClump *clump = (RpClump*)mi->CreateInstance(); + RpAnimBlendClumpInit(clump); + CAnimBlendAssocGroup *group = &CAnimManager::ms_aAnimAssocGroups[i]; + AnimAssocDefinition *def = &CAnimManager::ms_aAnimAssocDefinitions[i]; + group->CreateAssociations(def->blockName, clump, def->animNames, def->numAnims); + for(j = 0; j < group->numAssociations; j++) + group->GetAnimation(def->animDescs[j].animId)->flags |= def->animDescs[j].flags; + RpClumpDestroy(clump); + } +} + +void +CAnimManager::LoadAnimFile(const char *filename) +{ + int fd; + fd = CFileMgr::OpenFile(filename, "rb"); + assert(fd > 0); + LoadAnimFile(fd, true); + CFileMgr::CloseFile(fd); +} + +void +CAnimManager::LoadAnimFile(int fd, bool compress) +{ + #define ROUNDSIZE(x) if((x) & 3) (x) += 4 - ((x)&3) + struct IfpHeader { + char ident[4]; + uint32 size; + }; + IfpHeader anpk, info, name, dgan, cpan, anim; + int numANPK; + char buf[256]; + int i, j, k, l; + float *fbuf = (float*)buf; + + CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader)); + if(strncmp(anpk.ident, "ANLF", 4) == 0){ + ROUNDSIZE(anpk.size); + CFileMgr::Read(fd, buf, anpk.size); + numANPK = *(int*)buf; + }else if(strncmp(anpk.ident, "ANPK", 4) == 0){ + CFileMgr::Seek(fd, -8, 1); + numANPK = 1; + } + + for(i = 0; i < numANPK; i++){ + // block name + CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader)); + ROUNDSIZE(anpk.size); + CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + CFileMgr::Read(fd, buf, info.size); + CAnimBlock *animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; + strncpy(animBlock->name, buf+4, 24); + animBlock->numAnims = *(int*)buf; + + animBlock->firstIndex = ms_numAnimations; + + for(j = 0; j < animBlock->numAnims; j++){ + CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++]; + + // animation name + CFileMgr::Read(fd, (char*)&name, sizeof(IfpHeader)); + ROUNDSIZE(name.size); + CFileMgr::Read(fd, buf, name.size); + hier->SetName(buf); + + // DG info has number of nodes/sequences + CFileMgr::Read(fd, (char*)&dgan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + CFileMgr::Read(fd, buf, info.size); + hier->numSequences = *(int*)buf; + hier->sequences = new CAnimBlendSequence[hier->numSequences]; + + CAnimBlendSequence *seq = hier->sequences; + for(k = 0; k < hier->numSequences; k++, seq++){ + // Each node has a name and key frames + CFileMgr::Read(fd, (char*)&cpan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + CFileMgr::Read(fd, (char*)&anim, sizeof(IfpHeader)); + ROUNDSIZE(anim.size); + CFileMgr::Read(fd, buf, anim.size); + int numFrames = *(int*)(buf+28); +#ifdef PED_SKIN + if(anim.size == 44) + seq->SetBoneTag(*(int*)(buf+40)); +#endif + seq->SetName(buf); + if(numFrames == 0) + continue; + + CFileMgr::Read(fd, (char*)&info, sizeof(info)); + if(strncmp(info.ident, "KR00", 4) == 0){ + seq->SetNumFrames(numFrames, false); + KeyFrame *kf = seq->GetKeyFrame(0); + for(l = 0; l < numFrames; l++, kf++){ + CFileMgr::Read(fd, buf, 0x14); + kf->rotation.x = -fbuf[0]; + kf->rotation.y = -fbuf[1]; + kf->rotation.z = -fbuf[2]; + kf->rotation.w = fbuf[3]; + kf->deltaTime = fbuf[4]; // absolute time here + } + }else if(strncmp(info.ident, "KRT0", 4) == 0){ + seq->SetNumFrames(numFrames, true); + KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(0); + for(l = 0; l < numFrames; l++, kf++){ + CFileMgr::Read(fd, buf, 0x20); + kf->rotation.x = -fbuf[0]; + kf->rotation.y = -fbuf[1]; + kf->rotation.z = -fbuf[2]; + kf->rotation.w = fbuf[3]; + kf->translation.x = fbuf[4]; + kf->translation.y = fbuf[5]; + kf->translation.z = fbuf[6]; + kf->deltaTime = fbuf[7]; // absolute time here + } + }else if(strncmp(info.ident, "KRTS", 4) == 0){ + seq->SetNumFrames(numFrames, true); + KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(0); + for(l = 0; l < numFrames; l++, kf++){ + CFileMgr::Read(fd, buf, 0x2C); + kf->rotation.x = -fbuf[0]; + kf->rotation.y = -fbuf[1]; + kf->rotation.z = -fbuf[2]; + kf->rotation.w = fbuf[3]; + kf->translation.x = fbuf[4]; + kf->translation.y = fbuf[5]; + kf->translation.z = fbuf[6]; + // scaling ignored + kf->deltaTime = fbuf[10]; // absolute time here + } + } + + // convert absolute time to deltas + for(l = seq->numFrames-1; l > 0; l--){ + KeyFrame *kf1 = seq->GetKeyFrame(l); + KeyFrame *kf2 = seq->GetKeyFrame(l-1); + kf1->deltaTime -= kf2->deltaTime; + } + } + + hier->RemoveQuaternionFlips(); + if(compress) + hier->RemoveUncompressedData(); + else + hier->CalcTotalTime(); + } + } +} + +void +CAnimManager::RemoveLastAnimFile(void) +{ + int i; + ms_numAnimBlocks--; + ms_numAnimations = ms_aAnimBlocks[ms_numAnimBlocks].firstIndex; + for(i = 0; i < ms_aAnimBlocks[ms_numAnimBlocks].numAnims; i++) + ms_aAnimations[ms_aAnimBlocks[ms_numAnimBlocks].firstIndex + i].RemoveAnimSequences(); +} + + +STARTPATCHES + InjectHook(0x403380, CAnimManager::Initialise, PATCH_JUMP); + InjectHook(0x4033B0, CAnimManager::Shutdown, PATCH_JUMP); + InjectHook(0x403410, CAnimManager::UncompressAnimation, PATCH_JUMP); + InjectHook(0x4034A0, CAnimManager::GetAnimationBlock, PATCH_JUMP); + InjectHook(0x4034F0, (CAnimBlendHierarchy *(*)(const char*, CAnimBlock*))CAnimManager::GetAnimation, PATCH_JUMP); + InjectHook(0x4035B0, CAnimManager::GetAnimGroupName, PATCH_JUMP); + InjectHook(0x4035C0, CAnimManager::CreateAnimAssociation, PATCH_JUMP); + InjectHook(0x4035E0, (CAnimBlendAssociation *(*)(AssocGroupId, AnimationId))CAnimManager::GetAnimAssociation, PATCH_JUMP); + InjectHook(0x403600, (CAnimBlendAssociation *(*)(AssocGroupId, const char*))CAnimManager::GetAnimAssociation, PATCH_JUMP); + InjectHook(0x403620, CAnimManager::AddAnimation, PATCH_JUMP); + InjectHook(0x4036A0, CAnimManager::AddAnimationAndSync, PATCH_JUMP); + InjectHook(0x403710, CAnimManager::BlendAnimation, PATCH_JUMP); + InjectHook(0x4038F0, CAnimManager::LoadAnimFiles, PATCH_JUMP); + InjectHook(0x403A10, (void (*)(const char *))CAnimManager::LoadAnimFile, PATCH_JUMP); + InjectHook(0x403A40, (void (*)(int, bool))CAnimManager::LoadAnimFile, PATCH_JUMP); + InjectHook(0x404320, CAnimManager::RemoveLastAnimFile, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/AnimManager.h b/src/animation/AnimManager.h new file mode 100644 index 00000000..1b881f34 --- /dev/null +++ b/src/animation/AnimManager.h @@ -0,0 +1,268 @@ +#pragma once + +#include "AnimBlendHierarchy.h" + +enum AssocGroupId +{ + ASSOCGRP_STD, + ASSOCGRP_PLAYER, + ASSOCGRP_PLAYERROCKET, + ASSOCGRP_PLAYER1ARMED, + ASSOCGRP_PLAYER2ARMED, + ASSOCGRP_PLAYERBBBAT, + ASSOCGRP_SHUFFLE, + ASSOCGRP_OLD, + ASSOCGRP_GANG1, + ASSOCGRP_GANG2, + ASSOCGRP_FAT, + ASSOCGRP_OLDFAT, + ASSOCGRP_WOMAN, + ASSOCGRP_WOMANSHOP, + ASSOCGRP_BUSYWOMAN, + ASSOCGRP_SEXYWOMAN, + ASSOCGRP_OLDWOMAN, + ASSOCGRP_FARWOMAN, + ASSOCGRP_PANICCHUNKY, + ASSOCGRP_PLAYERBACK, + ASSOCGRP_PLAYERLEFT, + ASSOCGRP_PLAYERRIGHT, + ASSOCGRP_ROCKETBACK, + ASSOCGRP_ROCKETLEFT, + ASSOCGRP_ROCKETRIGHT, + + NUM_ANIM_ASSOC_GROUPS +}; + +enum AnimationId +{ + ANIM_WALK, + ANIM_RUN, + ANIM_SPRINT, + ANIM_IDLE_STANCE, + ANIM_WALK_START, + ANIM_RUN_STOP, + ANIM_RUN_STOP_R, + ANIM_IDLE_CAM, + ANIM_IDLE_HBHB, + ANIM_IDLE_TIRED, + ANIM_IDLE_ARMED, + ANIM_IDLE_CHAT, + ANIM_IDLE_TAXI, + ANIM_KO_SHOT_FRONT1, + ANIM_KO_SHOT_FRONT2, + ANIM_KO_SHOT_FRONT3, + ANIM_KO_SHOT_FRONT4, + ANIM_KO_SHOT_FACE, + ANIM_KO_SHOT_STOM, + ANIM_KO_SHOT_ARML, + ANIM_KO_SHOT_ARMR, + ANIM_KO_SHOT_LEGL, + ANIM_KO_SHOT_LEGR, + ANIM_KD_LEFT, + ANIM_KD_RIGHT, + ANIM_KO_SKID_FRONT, + ANIM_KO_SPIN_R, + ANIM_KO_SKID_BACK, + ANIM_KO_SPIN_L, + ANIM_SHOT_FRONT_PARTIAL, + ANIM_SHOT_LEFT_PARTIAL, + ANIM_SHOT_BACK_PARTIAL, + ANIM_SHOT_RIGHT_PARTIAL, + ANIM_HIT_FRONT, + ANIM_HIT_LEFT, + ANIM_HIT_BACK, + ANIM_HIT_RIGHT, + ANIM_FLOOR_HIT, + ANIM_HIT_BODYBLOW, + ANIM_HIT_CHEST, + ANIM_HIT_HEAD, + ANIM_HIT_WALK, + ANIM_HIT_WALL, + ANIM_FLOOR_HIT_F, + ANIM_HIT_BEHIND, + ANIM_PUNCH_R, + ANIM_KICK_FLOOR, + ANIM_WEAPON_BAT_H, + ANIM_WEAPON_BAT_V, + ANIM_WEAPON_HGUN_BODY, + ANIM_WEAPON_AK_BODY, + ANIM_WEAPON_PUMP, + ANIM_WEAPON_SNIPER, + ANIM_WEAPON_THROW, + ANIM_WEAPON_THROWU, + ANIM_WEAPON_START_THROW, + ANIM_BOMBER, + ANIM_HGUN_RELOAD, + ANIM_AK_RELOAD, + ANIM_FPS_PUNCH, + ANIM_FPS_BAT, + ANIM_FPS_UZI, + ANIM_FPS_PUMP, + ANIM_FPS_AK, + ANIM_FPS_M16, + ANIM_FPS_ROCKET, + ANIM_FIGHT_IDLE, + ANIM_FIGHT2_IDLE, + ANIM_FIGHT_SH_F, + ANIM_FIGHT_BODYBLOW, + ANIM_FIGHT_HEAD, + ANIM_FIGHT_KICK, + ANIM_FIGHT_KNEE, + ANIM_FIGHT_LHOOK, + ANIM_FIGHT_PUNCH, + ANIM_FIGHT_ROUNDHOUSE, + ANIM_FIGHT_LONGKICK, + ANIM_FIGHT_PPUNCH, + ANIM_CAR_JACKED_RHS, + ANIM_CAR_LJACKED_RHS, + ANIM_CAR_JACKED_LHS, + ANIM_CAR_LJACKED_LHS, + ANIM_CAR_QJACK, + ANIM_CAR_QJACKED, + ANIM_CAR_ALIGN_LHS, + ANIM_CAR_ALIGNHI_LHS, + ANIM_CAR_OPEN_LHS, + ANIM_CAR_DOORLOCKED_LHS, + ANIM_CAR_PULLOUT_LHS, + ANIM_CAR_PULLOUT_LOW_LHS, + ANIM_CAR_GETIN_LHS, + ANIM_CAR_GETIN_LOW_LHS, + ANIM_CAR_CLOSEDOOR_LHS, + ANIM_CAR_CLOSEDOOR_LOW_LHS, + ANIM_CAR_ROLLDOOR, + ANIM_CAR_ROLLDOOR_LOW, + ANIM_CAR_GETOUT_LHS, + ANIM_CAR_GETOUT_LOW_LHS, + ANIM_CAR_CLOSE_LHS, + ANIM_CAR_ALIGN_RHS, + ANIM_CAR_ALIGNHI_RHS, + ANIM_CAR_OPEN_RHS, + ANIM_CAR_DOORLOCKED_RHS, + ANIM_CAR_PULLOUT_RHS, + ANIM_CAR_PULLOUT_LOW_RHS, + ANIM_CAR_GETIN_RHS, + ANIM_CAR_GETIN_LOW_RHS, + ANIM_CAR_CLOSEDOOR_RHS, + ANIM_CAR_CLOSEDOOR_LOW_RHS, + ANIM_CAR_SHUFFLE_RHS, + ANIM_CAR_LSHUFFLE_RHS, + ANIM_CAR_SIT, + ANIM_CAR_LSIT, + ANIM_CAR_SITP, + ANIM_CAR_SITPLO, + ANIM_DRIVE_L, + ANIM_DRIVE_R, + ANIM_DRIVE_LOW_L, + ANIM_DRIVE_LOW_R, + ANIM_DRIVEBY_L, + ANIM_DRIVEBY_R, + ANIM_CAR_LB, + ANIM_DRIVE_BOAT, + ANIM_CAR_GETOUT_RHS, + ANIM_CAR_GETOUT_LOW_RHS, + ANIM_CAR_CLOSE_RHS, + ANIM_CAR_HOOKERTALK, + ANIM_COACH_OPEN_L, + ANIM_COACH_OPEN_R, + ANIM_COACH_IN_L, + ANIM_COACH_IN_R, + ANIM_COACH_OUT_L, + ANIM_TRAIN_GETIN, + ANIM_TRAIN_GETOUT, + ANIM_CAR_CRAWLOUT_RHS, + ANIM_CAR_CRAWLOUT_RHS2, + ANIM_VAN_OPEN_L, + ANIM_VAN_GETIN_L, + ANIM_VAN_CLOSE_L, + ANIM_VAN_GETOUT_L, + ANIM_VAN_OPEN, + ANIM_VAN_GETIN, + ANIM_VAN_CLOSE, + ANIM_VAN_GETOUT, + ANIM_GETUP1, + ANIM_GETUP2, + ANIM_GETUP3, + ANIM_GETUP_FRONT, + ANIM_JUMP_LAUNCH, + ANIM_JUMP_GLIDE, + ANIM_JUMP_LAND, + ANIM_FALL_FALL, + ANIM_FALL_GLIDE, + ANIM_FALL_LAND, + ANIM_FALL_COLLAPSE, + ANIM_EV_STEP, + ANIM_EV_DIVE, + ANIM_XPRESS_SCRATCH, + ANIM_ROAD_CROSS, + ANIM_TURN_180, + ANIM_ARREST_GUN, + ANIM_DROWN, + ANIM_CPR, + ANIM_DUCK_DOWN, + ANIM_DUCK_LOW, + ANIM_RBLOCK_CSHOOT, + ANIM_WEAPON_THROWU2, + ANIM_HANDSUP, + ANIM_HANDSCOWER, + ANIM_FUCKU, + ANIM_PHONE_IN, + ANIM_PHONE_OUT, + ANIM_PHONE_TALK, +}; + +class CAnimBlendAssociation; +class CAnimBlendAssocGroup; + +// A block of hierarchies +struct CAnimBlock +{ + char name[24]; + int32 firstIndex; + int32 numAnims; +}; + +struct AnimAssocDesc +{ + int32 animId; + int32 flags; +}; + +struct AnimAssocDefinition +{ + char *name; + char *blockName; + int32 modelIndex; + int32 numAnims; + char **animNames; + AnimAssocDesc *animDescs; +}; + +class CAnimManager +{ + static AnimAssocDefinition ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS]; + static CAnimBlock *ms_aAnimBlocks; //[2] + static CAnimBlendHierarchy *ms_aAnimations; //[250] + static int32 &ms_numAnimBlocks; + static int32 &ms_numAnimations; + static CAnimBlendAssocGroup *&ms_aAnimAssocGroups; + static CLinkList &ms_animCache; +public: + + static void Initialise(void); + static void Shutdown(void); + static void UncompressAnimation(CAnimBlendHierarchy *anim); + static CAnimBlock *GetAnimationBlock(const char *name); + static CAnimBlendHierarchy *GetAnimation(const char *name, CAnimBlock *animBlock); + static CAnimBlendHierarchy *GetAnimation(int32 n) { return &ms_aAnimations[n]; } + static const char *GetAnimGroupName(AssocGroupId groupId); + static CAnimBlendAssociation *CreateAnimAssociation(AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, const char *name); + static CAnimBlendAssociation *AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta); + static void LoadAnimFiles(void); + static void LoadAnimFile(const char *filename); + static void LoadAnimFile(int fd, bool compress); + static void RemoveLastAnimFile(void); +}; diff --git a/src/animation/FrameUpdate.cpp b/src/animation/FrameUpdate.cpp new file mode 100644 index 00000000..1533897e --- /dev/null +++ b/src/animation/FrameUpdate.cpp @@ -0,0 +1,228 @@ +#include "common.h" +#include "patcher.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "RpAnimBlend.h" + +CAnimBlendClumpData *&gpAnimBlendClump = *(CAnimBlendClumpData**)0x621000; + +void FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWithVelocityExtraction(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg); + +void +FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION && + gpAnimBlendClump->pedPosition){ + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D) + FrameUpdateCallBackWith3dVelocityExtraction(frame, arg); + else + FrameUpdateCallBackWithVelocityExtraction(frame, arg); + return; + } + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + (*node)->Update(vec, q, 1.0f-totalBlendAmount); + if((*node)->sequence->HasTranslation()) + pos += vec; + rot += q; + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + + float norm = rot.MagnitudeSqr(); + if(norm == 0.0f) + rot.w = 1.0f; + else + rot *= 1.0f/sqrt(norm); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + mat->pos.x = pos.x; + mat->pos.y = pos.y; + mat->pos.z = pos.z; + mat->pos.x += frame->resetPos.x; + mat->pos.y += frame->resetPos.y; + mat->pos.z += frame->resetPos.z; + } + RwMatrixUpdate(mat); +} + +void +FrameUpdateCallBackWithVelocityExtraction(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + float transx = 0.0f, transy = 0.0f; + float curx = 0.0f, cury = 0.0f; + float endx = 0.0f, endy = 0.0f; + bool looped = false; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cury += vec.y; + if((*node)->association->HasXTranslation()) + curx += vec.x; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + transy += vec.y; + if((*node)->association->HasXTranslation()) + transx += vec.x; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + endy += vec.y; + if((*node)->association->HasXTranslation()) + endx += vec.x; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + + float norm = rot.MagnitudeSqr(); + if(norm == 0.0f) + rot.w = 1.0f; + else + rot *= 1.0f/sqrt(norm); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + gpAnimBlendClump->pedPosition->x = transx - curx; + gpAnimBlendClump->pedPosition->y = transy - cury; + if(looped){ + gpAnimBlendClump->pedPosition->x += endx; + gpAnimBlendClump->pedPosition->y += endy; + } + mat->pos.x = pos.x - transx; + mat->pos.y = pos.y - transy; + mat->pos.z = pos.z; + if(mat->pos.z >= -0.8f) + if(mat->pos.z < -0.4f) + mat->pos.z += (2.5f * mat->pos.z + 2.0f) * frame->resetPos.z; + else + mat->pos.z += frame->resetPos.z; + mat->pos.x += frame->resetPos.x; + mat->pos.y += frame->resetPos.y; + } + RwMatrixUpdate(mat); +} + +// original code uses do loops? +void +FrameUpdateCallBackWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + CVector trans(0.0f, 0.0f, 0.0f); + CVector cur(0.0f, 0.0f, 0.0f); + CVector end(0.0f, 0.0f, 0.0f); + bool looped = false; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cur += vec; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + trans += vec; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + end += vec; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + + float norm = rot.MagnitudeSqr(); + if(norm == 0.0f) + rot.w = 1.0f; + else + rot *= 1.0f/sqrt(norm); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + *gpAnimBlendClump->pedPosition = trans - cur; + if(looped) + *gpAnimBlendClump->pedPosition += end; + mat->pos.x = (pos - trans).x + frame->resetPos.x; + mat->pos.y = (pos - trans).y + frame->resetPos.y; + mat->pos.z = (pos - trans).z + frame->resetPos.z; + } + RwMatrixUpdate(mat); +} + +STARTPATCHES + InjectHook(0x4025F0, FrameUpdateCallBack, PATCH_JUMP); + InjectHook(0x4028B0, FrameUpdateCallBackWithVelocityExtraction, PATCH_JUMP); + InjectHook(0x402D40, FrameUpdateCallBackWith3dVelocityExtraction, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/RpAnimBlend.cpp b/src/animation/RpAnimBlend.cpp new file mode 100644 index 00000000..9d5e2162 --- /dev/null +++ b/src/animation/RpAnimBlend.cpp @@ -0,0 +1,402 @@ +#include "common.h" +#include "patcher.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendHierarchy.h" +#include "AnimBlendAssociation.h" +#include "RpAnimBlend.h" + +RwInt32 &ClumpOffset = *(RwInt32*)0x8F1B84; + +enum +{ + ID_RPANIMBLEND = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0xFD), +}; + +void* +AnimBlendClumpCreate(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + *RWPLUGINOFFSET(CAnimBlendClumpData*, object, offsetInObject) = nil; + return object; +} + +void* +AnimBlendClumpDestroy(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + CAnimBlendClumpData *data; + data = *RPANIMBLENDCLUMPDATA(object); + if(data){ + RpAnimBlendClumpRemoveAllAssociations((RpClump*)object); + delete data; + *RPANIMBLENDCLUMPDATA(object) = nil; + } + return object; +} + +void *AnimBlendClumpCopy(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject) { return nil; } + +bool +RpAnimBlendPluginAttach(void) +{ + ClumpOffset = RpClumpRegisterPlugin(sizeof(CAnimBlendClumpData*), ID_RPANIMBLEND, + AnimBlendClumpCreate, AnimBlendClumpDestroy, AnimBlendClumpCopy); + return ClumpOffset >= 0; +} + +CAnimBlendAssociation* +RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc) +{ + if(assoc->link.next) + CAnimBlendAssociation::FromLink(assoc->link.next); + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc, uint32 mask) +{ + CAnimBlendLink *link; + for(link = assoc->link.next; link; link = link->next){ + assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->flags & mask) + return assoc; + } + return nil; +} + +void +RpAnimBlendAllocateData(RpClump *clump) +{ + *RPANIMBLENDCLUMPDATA(clump) = new CAnimBlendClumpData; +} + + +void +RpAnimBlendClumpSetBlendDeltas(RpClump *clump, uint32 mask, float delta) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(mask == 0 || (assoc->flags & mask)) + assoc->blendDelta = delta; + } +} + +void +RpAnimBlendClumpRemoveAllAssociations(RpClump *clump) +{ + RpAnimBlendClumpRemoveAssociations(clump, 0); +} + +void +RpAnimBlendClumpRemoveAssociations(RpClump *clump, uint32 mask) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + CAnimBlendLink *next; + for(CAnimBlendLink *link = clumpData->link.next; link; link = next){ + next = link->next; + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(mask == 0 || (assoc->flags & mask)) + if(assoc) + delete assoc; + } +} + +RwFrame* +FrameForAllChildrenCountCallBack(RwFrame *frame, void *data) +{ + int *numFrames = (int*)data; + (*numFrames)++; + RwFrameForAllChildren(frame, FrameForAllChildrenCountCallBack, data); + return frame; +} + +RwFrame* +FrameForAllChildrenFillFrameArrayCallBack(RwFrame *frame, void *data) +{ + AnimBlendFrameData **frames = (AnimBlendFrameData**)data; + (*frames)->frame = frame; + (*frames)++; + RwFrameForAllChildren(frame, FrameForAllChildrenFillFrameArrayCallBack, frames); + return frame; +} + +void +FrameInitCallBack(AnimBlendFrameData *frameData, void*) +{ + frameData->flag = 0; + frameData->resetPos = *RwMatrixGetPos(RwFrameGetMatrix(frameData->frame)); +} + +void +RpAnimBlendClumpInit(RpClump *clump) +{ +#ifdef PED_SKIN + TODO +#else + int numFrames = 0; + CAnimBlendClumpData *clumpData; + RwFrame *root; + AnimBlendFrameData *frames; + + RpAnimBlendAllocateData(clump); + clumpData = *RPANIMBLENDCLUMPDATA(clump); + root = RpClumpGetFrame(clump); + RwFrameForAllChildren(root, FrameForAllChildrenCountCallBack, &numFrames); + clumpData->SetNumberOfFrames(numFrames); + frames = clumpData->frames; + RwFrameForAllChildren(root, FrameForAllChildrenFillFrameArrayCallBack, &frames); + clumpData->ForAllFrames(FrameInitCallBack, nil); + clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION; +#endif +} + +bool +RpAnimBlendClumpIsInitialized(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + return clumpData && clumpData->numFrames != 0; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetAssociation(RpClump *clump, uint32 id) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->animId == id) + return assoc; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainAssociation(RpClump *clump, CAnimBlendAssociation **assocRet, float *blendRet) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + CAnimBlendAssociation *mainAssoc = nil; + CAnimBlendAssociation *secondAssoc = nil; + float mainBlend = 0.0; + float secondBlend = 0.0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(assoc->IsPartial()) + continue; + + if(assoc->blendAmount > mainBlend){ + secondBlend = mainBlend; + mainBlend = assoc->blendAmount; + + secondAssoc = mainAssoc; + mainAssoc = assoc; + }else if(assoc->blendAmount > secondBlend){ + secondBlend = assoc->blendAmount; + secondAssoc = assoc; + } + } + if(assocRet) *assocRet = secondAssoc; + if(blendRet) *blendRet = secondBlend; + return mainAssoc; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainPartialAssociation(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + CAnimBlendAssociation *mainAssoc = nil; + float mainBlend = 0.0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(!assoc->IsPartial()) + continue; + + if(assoc->blendAmount > mainBlend){ + mainBlend = assoc->blendAmount; + mainAssoc = assoc; + } + } + return mainAssoc; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainAssociation_N(RpClump *clump, int n) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + i = 0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(assoc->IsPartial()) + continue; + + if(i == n) + return assoc; + i++; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainPartialAssociation_N(RpClump *clump, int n) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + i = 0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(!assoc->IsPartial()) + continue; + + if(i == n) + return assoc; + i++; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetFirstAssociation(RpClump *clump, uint32 mask) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->flags & mask) + return assoc; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetFirstAssociation(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if(clumpData == nil) return nil; + if(clumpData->link.next == nil) return nil; + return CAnimBlendAssociation::FromLink(clumpData->link.next); +} + +void +FillFrameArrayCallBack(AnimBlendFrameData *frame, void *arg) +{ + AnimBlendFrameData **frames = (AnimBlendFrameData**)arg; + frames[CVisibilityPlugins::GetFrameHierarchyId(frame->frame)] = frame; +} + +void +RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames) +{ +#ifdef PED_SKIN + TODO +#else + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCallBack, frames); +#endif +} + +AnimBlendFrameData *pFrameDataFound; + +void +FrameFindCallBack(AnimBlendFrameData *frame, void *arg) +{ + char *nodename = GetFrameNodeName(frame->frame); + if(strcmpi(nodename, (char*)arg) == 0) + pFrameDataFound = frame; +} + +AnimBlendFrameData* +RpAnimBlendClumpFindFrame(RpClump *clump, const char *name) +{ + pFrameDataFound = nil; +#ifdef PED_SKIN + TODO +#else + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindCallBack, (void*)name); +#endif + return pFrameDataFound; +} + +void +RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta) +{ + int i; + AnimBlendFrameUpdateData updateData; + float totalLength = 0.0f; + float totalBlend = 0.0f; + CAnimBlendLink *link, *next; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + gpAnimBlendClump = clumpData; + + if(clumpData->link.next == nil) + return; + + // Update blend and get node array + i = 0; + updateData.foobar = 0; + for(link = clumpData->link.next; link; link = next){ + next = link->next; + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->UpdateBlend(timeDelta)){ + // CAnimManager::UncompressAnimation(v6->hierarchy) + updateData.nodes[i++] = assoc->GetNode(0); + if(assoc->flags & ASSOC_MOVEMENT){ + totalLength += assoc->hierarchy->totalLength/assoc->speed * assoc->blendAmount; + totalBlend += assoc->blendAmount; + }else + updateData.foobar = 1; + } + } + updateData.nodes[i] = 0; + + clumpData->ForAllFrames(FrameUpdateCallBack, &updateData); + + for(link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + float relSpeed = totalLength == 0.0f ? 1.0f : totalBlend/totalLength; + assoc->UpdateTime(timeDelta, relSpeed); + } + RwFrameUpdateObjects(RpClumpGetFrame(clump)); +} + + +STARTPATCHES + InjectHook(0x4052D0, RpAnimBlendPluginAttach, PATCH_JUMP); + InjectHook(0x4052A0, RpAnimBlendAllocateData, PATCH_JUMP); + InjectHook(0x405780, (CAnimBlendAssociation *(*)(CAnimBlendAssociation*))RpAnimBlendGetNextAssociation, PATCH_JUMP); + InjectHook(0x4057A0, (CAnimBlendAssociation *(*)(CAnimBlendAssociation*,uint32))RpAnimBlendGetNextAssociation, PATCH_JUMP); + + InjectHook(0x405520, RpAnimBlendClumpSetBlendDeltas, PATCH_JUMP); + InjectHook(0x405560, RpAnimBlendClumpRemoveAllAssociations, PATCH_JUMP); + InjectHook(0x405570, RpAnimBlendClumpRemoveAssociations, PATCH_JUMP); + InjectHook(0x405480, RpAnimBlendClumpInit, PATCH_JUMP); + InjectHook(0x405500, RpAnimBlendClumpIsInitialized, PATCH_JUMP); + InjectHook(0x4055C0, RpAnimBlendClumpGetAssociation, PATCH_JUMP); + InjectHook(0x4055F0, RpAnimBlendClumpGetMainAssociation, PATCH_JUMP); + InjectHook(0x405680, RpAnimBlendClumpGetMainPartialAssociation, PATCH_JUMP); + InjectHook(0x4056D0, RpAnimBlendClumpGetMainAssociation_N, PATCH_JUMP); + InjectHook(0x405710, RpAnimBlendClumpGetMainPartialAssociation_N, PATCH_JUMP); + InjectHook(0x405750, (CAnimBlendAssociation *(*)(RpClump*, uint32))RpAnimBlendClumpGetFirstAssociation, PATCH_JUMP); + InjectHook(0x4031B0, (CAnimBlendAssociation *(*)(RpClump*))RpAnimBlendClumpGetFirstAssociation, PATCH_JUMP); + InjectHook(0x405460, RpAnimBlendClumpFillFrameArray, PATCH_JUMP); + InjectHook(0x4024B0, RpAnimBlendClumpUpdateAnimations, PATCH_JUMP); +ENDPATCHES diff --git a/src/animation/RpAnimBlend.h b/src/animation/RpAnimBlend.h new file mode 100644 index 00000000..24c3d273 --- /dev/null +++ b/src/animation/RpAnimBlend.h @@ -0,0 +1,39 @@ +#pragma once + +class CAnimBlendNode; +class CAnimBlendAssociation; +class CAnimBlendClumpData; +struct AnimBlendFrameData; + +struct AnimBlendFrameUpdateData +{ + int foobar; + CAnimBlendNode *nodes[16]; +}; + +extern RwInt32 &ClumpOffset; +#define RPANIMBLENDCLUMPDATA(o) (RWPLUGINOFFSET(CAnimBlendClumpData*, o, ClumpOffset)) + +bool RpAnimBlendPluginAttach(void); +CAnimBlendAssociation *RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc); +CAnimBlendAssociation *RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc, uint32 mask); +void RpAnimBlendAllocateData(RpClump *clump); + +void RpAnimBlendClumpSetBlendDeltas(RpClump *clump, uint32 mask, float delta); +void RpAnimBlendClumpRemoveAllAssociations(RpClump *clump); +void RpAnimBlendClumpRemoveAssociations(RpClump *clump, uint32 mask); +void RpAnimBlendClumpInit(RpClump *clump); +bool RpAnimBlendClumpIsInitialized(RpClump *clump); +AnimBlendFrameData *RpAnimBlendClumpFindFrame(RpClump *clump, const char *name); +void FillFrameArrayCallBack(AnimBlendFrameData *frame, void *arg); +CAnimBlendAssociation *RpAnimBlendClumpGetAssociation(RpClump *clump, uint32 id); +CAnimBlendAssociation *RpAnimBlendClumpGetMainAssociation(RpClump *clump, CAnimBlendAssociation **assocRet, float *blendRet); +CAnimBlendAssociation *RpAnimBlendClumpGetMainPartialAssociation(RpClump *clump); +CAnimBlendAssociation *RpAnimBlendClumpGetMainAssociation_N(RpClump *clump, int n); +CAnimBlendAssociation *RpAnimBlendClumpGetMainPartialAssociation_N(RpClump *clump, int n); +CAnimBlendAssociation *RpAnimBlendClumpGetFirstAssociation(RpClump *clump, uint32 mask); +CAnimBlendAssociation *RpAnimBlendClumpGetFirstAssociation(RpClump *clump); + + +extern CAnimBlendClumpData *&gpAnimBlendClump; +void FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg); diff --git a/src/main.cpp b/src/main.cpp index dd5804d0..f8f9561d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #include "TxdStore.h" #include "FileMgr.h" #include "Text.h" +#include "RpAnimBlend.h" #include "Frontend.h" #define DEFAULT_VIEWWINDOW (tan(CDraw::GetFOV() * (360.0f / PI))) @@ -54,7 +55,7 @@ WRAPPER void CameraSize(RwCamera *camera, void *rect, float viewWindow, float aspectRatio) { EAXJMP(0x527170); } -WRAPPER RwBool RpAnimBlendPluginAttach() { EAXJMP(0x4052D0); } +//WRAPPER RwBool RpAnimBlendPluginAttach() { EAXJMP(0x4052D0); } WRAPPER RwBool NodeNamePluginAttach() { EAXJMP(0x527100); } diff --git a/src/math/Quaternion.h b/src/math/Quaternion.h new file mode 100644 index 00000000..702fc72f --- /dev/null +++ b/src/math/Quaternion.h @@ -0,0 +1,83 @@ +#pragma once + +// TODO: actually implement this +class CQuaternion +{ +public: + float x, y, z, w; + CQuaternion(void) {} + CQuaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + + float Magnitude(void) const { return sqrt(x*x + y*y + z*z + w*w); } + float MagnitudeSqr(void) const { return x*x + y*y + z*z + w*w; } + + const CQuaternion &operator+=(CQuaternion const &right) { + x += right.x; + y += right.y; + z += right.z; + w += right.w; + return *this; + } + + const CQuaternion &operator-=(CQuaternion const &right) { + x -= right.x; + y -= right.y; + z -= right.z; + w -= right.w; + return *this; + } + + const CQuaternion &operator*=(float right) { + x *= right; + y *= right; + z *= right; + w *= right; + return *this; + } + + const CQuaternion &operator/=(float right) { + x /= right; + y /= right; + z /= right; + w /= right; + return *this; + } + + CQuaternion operator-() const { + return CQuaternion(-x, -y, -z, -w); + } + + void Slerp(const CQuaternion &q1, const CQuaternion &q2, float theta, float invSin, float t); + void Get(RwMatrix *matrix); +}; + +inline float +DotProduct(const CQuaternion &q1, const CQuaternion &q2) +{ + return q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; +} + +inline CQuaternion operator+(const CQuaternion &left, const CQuaternion &right) +{ + return CQuaternion(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w); +} + +inline CQuaternion operator-(const CQuaternion &left, const CQuaternion &right) +{ + return CQuaternion(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w); +} + +inline CQuaternion operator*(const CQuaternion &left, float right) +{ + return CQuaternion(left.x * right, left.y * right, left.z * right, left.w * right); +} + +inline CQuaternion operator*(float left, const CQuaternion &right) +{ + return CQuaternion(left * right.x, left * right.y, left * right.z, left * right.w); +} + +inline CQuaternion operator/(const CQuaternion &left, float right) +{ + return CQuaternion(left.x / right, left.y / right, left.z / right, left.w / right); +} diff --git a/src/math/math.cpp b/src/math/math.cpp new file mode 100644 index 00000000..b76db4ae --- /dev/null +++ b/src/math/math.cpp @@ -0,0 +1,57 @@ +#include "common.h" +#include "patcher.h" +#include "Quaternion.h" + +// TODO: move more stuff into here + +void +CQuaternion::Slerp(const CQuaternion &q1, const CQuaternion &q2, float theta, float invSin, float t) +{ + if(theta == 0.0f) + *this = q2; + else{ + float w1, w2; + if(theta > PI/2){ + theta = PI - theta; + w1 = sin((1.0f - t) * theta) * invSin; + w2 = -sin(t * theta) * invSin; + }else{ + w1 = sin((1.0f - t) * theta) * invSin; + w2 = sin(t * theta) * invSin; + } + *this = w1*q1 + w2*q2; + } +} + +void +CQuaternion::Get(RwMatrix *matrix) +{ + float x2 = x+x; + float y2 = y+y; + float z2 = z+z; + + float x_2x = x * x2; + float x_2y = x * y2; + float x_2z = x * z2; + float y_2y = y * y2; + float y_2z = y * z2; + float z_2z = z * z2; + float w_2x = w * x2; + float w_2y = w * y2; + float w_2z = w * z2; + + matrix->right.x = 1.0f - (y_2y + z_2z); + matrix->up.x = x_2y - w_2z; + matrix->at.x = x_2z + w_2y; + matrix->right.y = x_2y + w_2z; + matrix->up.y = 1.0f - (x_2x + z_2z); + matrix->at.y = y_2z - w_2x; + matrix->right.z = x_2z - w_2y; + matrix->up.z = y_2z + w_2x; + matrix->at.z = 1.0f - (x_2x + y_2y); +} + +STARTPATCHES + InjectHook(0x4BA1C0, &CQuaternion::Slerp, PATCH_JUMP); + InjectHook(0x4BA0D0, &CQuaternion::Get, PATCH_JUMP); +ENDPATCHES diff --git a/src/templates.h b/src/templates.h index 75bfd26b..03e41a64 100644 --- a/src/templates.h +++ b/src/templates.h @@ -138,7 +138,10 @@ public: while(n--) freeHead.Insert(&links[n]); } - // Shutdown + void Shutdown(void){ + delete[] links; + links = nil; + } void Clear(void){ while(head.next != &tail) Remove(head.next);