summaryrefslogtreecommitdiffstats
path: root/src/animation
diff options
context:
space:
mode:
authoraap <aap@papnet.eu>2019-06-11 08:59:28 +0200
committeraap <aap@papnet.eu>2019-06-11 08:59:28 +0200
commite7ed4d009636804d5dbe05aae9e7ab23b80fdd37 (patch)
tree4c95f6e07923b5ed0a7046afeb42a1ea2b8693bf /src/animation
parentMerge branch 'master' of github.com:GTAmodding/re3 (diff)
downloadre3-e7ed4d009636804d5dbe05aae9e7ab23b80fdd37.tar
re3-e7ed4d009636804d5dbe05aae9e7ab23b80fdd37.tar.gz
re3-e7ed4d009636804d5dbe05aae9e7ab23b80fdd37.tar.bz2
re3-e7ed4d009636804d5dbe05aae9e7ab23b80fdd37.tar.lz
re3-e7ed4d009636804d5dbe05aae9e7ab23b80fdd37.tar.xz
re3-e7ed4d009636804d5dbe05aae9e7ab23b80fdd37.tar.zst
re3-e7ed4d009636804d5dbe05aae9e7ab23b80fdd37.zip
Diffstat (limited to '')
-rw-r--r--src/animation/AnimBlendAssocGroup.cpp161
-rw-r--r--src/animation/AnimBlendAssocGroup.h20
-rw-r--r--src/animation/AnimBlendAssociation.cpp227
-rw-r--r--src/animation/AnimBlendAssociation.h89
-rw-r--r--src/animation/AnimBlendClumpData.cpp46
-rw-r--r--src/animation/AnimBlendClumpData.h57
-rw-r--r--src/animation/AnimBlendHierarchy.cpp84
-rw-r--r--src/animation/AnimBlendHierarchy.h27
-rw-r--r--src/animation/AnimBlendList.h27
-rw-r--r--src/animation/AnimBlendNode.cpp170
-rw-r--r--src/animation/AnimBlendNode.h29
-rw-r--r--src/animation/AnimBlendSequence.cpp68
-rw-r--r--src/animation/AnimBlendSequence.h55
-rw-r--r--src/animation/AnimManager.cpp930
-rw-r--r--src/animation/AnimManager.h268
-rw-r--r--src/animation/FrameUpdate.cpp228
-rw-r--r--src/animation/RpAnimBlend.cpp402
-rw-r--r--src/animation/RpAnimBlend.h39
18 files changed, 2927 insertions, 0 deletions
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<CAnimBlendHierarchy*> *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<CAnimBlendHierarchy*> &CAnimManager::ms_animCache = *(CLinkList<CAnimBlendHierarchy*>*)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<CAnimBlendHierarchy*> *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<CAnimBlendHierarchy*> &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);