summaryrefslogtreecommitdiff
path: root/src/animation
diff options
context:
space:
mode:
Diffstat (limited to 'src/animation')
-rw-r--r--src/animation/AnimBlendAssocGroup.cpp187
-rw-r--r--src/animation/AnimBlendAssocGroup.h24
-rw-r--r--src/animation/AnimBlendAssociation.cpp232
-rw-r--r--src/animation/AnimBlendAssociation.h93
-rw-r--r--src/animation/AnimBlendClumpData.cpp36
-rw-r--r--src/animation/AnimBlendClumpData.h45
-rw-r--r--src/animation/AnimBlendHierarchy.cpp129
-rw-r--r--src/animation/AnimBlendHierarchy.h36
-rw-r--r--src/animation/AnimBlendList.h28
-rw-r--r--src/animation/AnimBlendNode.cpp314
-rw-r--r--src/animation/AnimBlendNode.h37
-rw-r--r--src/animation/AnimBlendSequence.cpp200
-rw-r--r--src/animation/AnimBlendSequence.h95
-rw-r--r--src/animation/AnimManager.cpp1453
-rw-r--r--src/animation/AnimManager.h145
-rw-r--r--src/animation/AnimationId.h287
-rw-r--r--src/animation/Bones.cpp59
-rw-r--r--src/animation/Bones.h30
-rw-r--r--src/animation/CutsceneMgr.cpp683
-rw-r--r--src/animation/CutsceneMgr.h61
-rw-r--r--src/animation/FrameUpdate.cpp668
-rw-r--r--src/animation/RpAnimBlend.cpp517
-rw-r--r--src/animation/RpAnimBlend.h48
23 files changed, 5407 insertions, 0 deletions
diff --git a/src/animation/AnimBlendAssocGroup.cpp b/src/animation/AnimBlendAssocGroup.cpp
new file mode 100644
index 0000000..935e7fd
--- /dev/null
+++ b/src/animation/AnimBlendAssocGroup.cpp
@@ -0,0 +1,187 @@
+#include "common.h"
+
+#if defined _WIN32 && !defined __MINGW32__
+#if defined __MWERKS__
+#include <wctype.h>
+#else
+#include "ctype.h"
+#endif
+#else
+#include <cwctype>
+#endif
+
+#include "General.h"
+#include "RwHelper.h"
+#include "ModelIndices.h"
+#include "ModelInfo.h"
+#include "AnimManager.h"
+#include "RpAnimBlend.h"
+#include "AnimBlendAssociation.h"
+#include "AnimBlendAssocGroup.h"
+
+CAnimBlendAssocGroup::CAnimBlendAssocGroup(void)
+{
+ animBlock = nil;
+ assocList = nil;
+ numAssociations = 0;
+ firstAnimId = 0;
+ groupId = -1;
+}
+
+CAnimBlendAssocGroup::~CAnimBlendAssocGroup(void)
+{
+ DestroyAssociations();
+}
+
+void
+CAnimBlendAssocGroup::DestroyAssociations(void)
+{
+ if(assocList){
+ delete[] assocList;
+ assocList = nil;
+ numAssociations = 0;
+ }
+}
+
+CAnimBlendAssociation*
+CAnimBlendAssocGroup::GetAnimation(uint32 id)
+{
+ return &assocList[id - firstAnimId];
+}
+
+CAnimBlendAssociation*
+CAnimBlendAssocGroup::GetAnimation(const char *name)
+{
+ int i;
+ for(i = 0; i < numAssociations; i++)
+ if(!CGeneral::faststricmp(assocList[i].hierarchy->name, name))
+ return &assocList[i];
+ debug("\n\nCan't find the fucking animation %s\n\n\n", name);
+ 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);
+}
+
+bool
+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 true;
+#ifndef ASCII_STRCMP
+ if(iswdigit(c1) && iswdigit(c2))
+#else
+ if(__ascii_iswdigit(c1) && __ascii_iswdigit(c2))
+#endif
+ continue;
+#ifndef ASCII_STRCMP
+ c1 = toupper(c1);
+ c2 = toupper(c2);
+#else
+ c1 = __ascii_toupper(c1);
+ c2 = __ascii_toupper(c2);
+#endif
+
+ if(c1 && c2 && c1 != c2)
+ return false;
+ }
+}
+
+CBaseModelInfo*
+GetModelFromName(const char *name)
+{
+ int i;
+ CBaseModelInfo *mi;
+ char playername[32];
+
+ if(strncasecmp(name, "CSplay", 6) == 0 &&
+ strncasecmp(CModelInfo::GetModelInfo(MI_PLAYER)->GetModelName(), "ig", 2) == 0){
+ strcpy(playername, CModelInfo::GetModelInfo(MI_PLAYER)->GetModelName());
+ playername[0] = 'C';
+ playername[1] = 'S';
+ name = playername;
+ }
+
+ for(i = 0; i < MODELINFOSIZE; i++){
+ mi = CModelInfo::GetModelInfo(i);
+ if(mi && mi->GetRwObject() && RwObjectGetType(mi->GetRwObject()) == rpCLUMP &&
+ strcmpIgnoringDigits(mi->GetModelName(), name))
+ return mi;
+ }
+ return nil;
+}
+
+void
+CAnimBlendAssocGroup::CreateAssociations(const char *name)
+{
+ int i;
+ CAnimBlock *animBlock;
+
+ 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);
+ if(model){
+ debug("Associated anim %s with model %s\n", anim->name, model->GetModelName());
+ RpClump *clump = (RpClump*)model->CreateInstance();
+ RpAnimBlendClumpInit(clump);
+ assocList[i].Init(clump, anim);
+ if(IsClumpSkinned(clump))
+ RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil);
+ RpClumpDestroy(clump);
+ assocList[i].animId = firstAnimId + i;
+ assocList[i].groupId = groupId;
+ }else
+ debug("\n\nCANNOT FIND MODELINFO WITH NAME %s\n\n\n", anim->name);
+ }
+ numAssociations = animBlock->numAnims;
+}
+
+// Create associations from hierarchies for a given clump
+void
+CAnimBlendAssocGroup::CreateAssociations(const char *blockName, RpClump *clump, const char **animNames, int numAssocs)
+{
+ int i;
+
+ DestroyAssociations();
+
+ animBlock = CAnimManager::GetAnimationBlock(blockName);
+ assocList = new CAnimBlendAssociation[numAssocs];
+
+ numAssociations = 0;
+ for(i = 0; i < numAssocs; i++){
+ assocList[i].Init(clump, CAnimManager::GetAnimation(animNames[i], animBlock));
+ assocList[i].animId = firstAnimId + i;
+ assocList[i].groupId = groupId;
+ }
+ numAssociations = numAssocs;
+}
diff --git a/src/animation/AnimBlendAssocGroup.h b/src/animation/AnimBlendAssocGroup.h
new file mode 100644
index 0000000..86f0ca1
--- /dev/null
+++ b/src/animation/AnimBlendAssocGroup.h
@@ -0,0 +1,24 @@
+#pragma once
+
+class CAnimBlendAssociation;
+struct CAnimBlock;
+
+class CAnimBlendAssocGroup
+{
+public:
+ CAnimBlock *animBlock;
+ CAnimBlendAssociation *assocList;
+ int32 numAssociations;
+ int32 firstAnimId;
+ int32 groupId; // id of self in ms_aAnimAssocGroups
+
+ 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, const char **animNames, int numAssocs);
+};
diff --git a/src/animation/AnimBlendAssociation.cpp b/src/animation/AnimBlendAssociation.cpp
new file mode 100644
index 0000000..bb4e7bf
--- /dev/null
+++ b/src/animation/AnimBlendAssociation.cpp
@@ -0,0 +1,232 @@
+#include "common.h"
+
+#include "AnimBlendHierarchy.h"
+#include "AnimBlendClumpData.h"
+#include "RpAnimBlend.h"
+#include "AnimManager.h"
+#include "AnimBlendAssociation.h"
+#include "MemoryMgr.h"
+
+CAnimBlendAssociation::CAnimBlendAssociation(void)
+{
+ groupId = -1;
+ 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)
+{
+ if(nodes)
+ 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];
+ if(seq->boneTag == -1)
+ frame = RpAnimBlendClumpFindFrame(clump, seq->name);
+ else
+ frame = RpAnimBlendClumpFindBone(clump, seq->boneTag);
+ 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;
+ groupId = assoc.groupId;
+ 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()) {
+ currentTime = hierarchy->totalLength;
+ break;
+ }
+
+ CAnimManager::UncompressAnimation(hierarchy);
+#ifdef ANIM_COMPRESSION
+ // strangely PC has this but android doesn't
+ if(hierarchy->keepCompressed){
+ for(i = 0; i < numNodes; i++)
+ if(nodes[i].sequence)
+ nodes[i].SetupKeyFrameCompressed();
+ }else
+#endif
+ {
+ 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::UpdateTimeStep(float timeDelta, float relSpeed)
+{
+ if(IsRunning())
+ timeStep = (flags & ASSOC_MOVEMENT ? relSpeed*hierarchy->totalLength : speed) * timeDelta;
+}
+
+bool
+CAnimBlendAssociation::UpdateTime(float timeDelta, float relSpeed)
+{
+ if(!IsRunning())
+ return true;
+ if(currentTime >= hierarchy->totalLength){
+ flags &= ~ASSOC_RUNNING;
+ return true;
+ }
+
+ currentTime += timeStep;
+
+ if(currentTime >= hierarchy->totalLength){
+ // Ran past end
+
+ if(IsRepeating())
+ currentTime -= hierarchy->totalLength;
+ else{
+ currentTime = hierarchy->totalLength;
+ if(flags & ASSOC_FADEOUTWHENDONE){
+ flags |= ASSOC_DELETEFADEDOUT;
+ blendDelta = -4.0f;
+ }
+ if(callbackType == CB_FINISH){
+ callbackType = CB_NONE;
+ callback(this, callbackArg);
+ }
+ }
+ }
+ return true;
+}
+
+// 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.0f, 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.0f, blendDelta);
+ }
+
+ return true;
+}
diff --git a/src/animation/AnimBlendAssociation.h b/src/animation/AnimBlendAssociation.h
new file mode 100644
index 0000000..dbfcb72
--- /dev/null
+++ b/src/animation/AnimBlendAssociation.h
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "AnimBlendList.h"
+#include "AnimBlendNode.h"
+#include "AnimBlendHierarchy.h"
+
+enum {
+ ASSOC_RUNNING = 1,
+ ASSOC_REPEAT = 2,
+ ASSOC_DELETEFADEDOUT = 4,
+ ASSOC_FADEOUTWHENDONE = 8,
+ ASSOC_PARTIAL = 0x10,
+ ASSOC_MOVEMENT = 0x20, // ???
+ ASSOC_HAS_TRANSLATION = 0x40,
+ ASSOC_HAS_X_TRANSLATION = 0x80, // for 2d velocity extraction
+ ASSOC_WALK = 0x100, // for CPed::PlayFootSteps(void)
+ ASSOC_IDLE = 0x200, // only xpress scratch has it by default, but game adds it to player's idle animations later
+ ASSOC_NOWALK = 0x400, // see CPed::PlayFootSteps(void)
+ ASSOC_BLOCK = 0x800, // unused in assoc description, blocks other anims from being played
+ ASSOC_FRONTAL = 0x1000, // anims that we fall to front
+ ASSOC_DRIVING = 0x2000, // new in VC
+};
+
+// 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;
+
+ int16 numNodes; // taken from CAnimBlendClumpData::numFrames
+ int16 groupId; // ID of CAnimBlendAssocGroup this is in
+ // 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;
+ int16 animId;
+ int16 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);
+#ifndef FIX_BUGS
+ virtual
+#endif
+ ~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 UpdateTimeStep(float timeDelta, float relSpeed);
+ bool UpdateTime(float timeDelta, float relSpeed);
+ bool UpdateBlend(float timeDelta);
+
+ void SetRun(void) { flags |= ASSOC_RUNNING; }
+
+ float GetTimeLeft() { return hierarchy->totalLength - currentTime; }
+ float GetProgress() { return currentTime / hierarchy->totalLength; }
+
+ static CAnimBlendAssociation *FromLink(CAnimBlendLink *l) {
+ return (CAnimBlendAssociation*)((uint8*)l - offsetof(CAnimBlendAssociation, link));
+ }
+};
diff --git a/src/animation/AnimBlendClumpData.cpp b/src/animation/AnimBlendClumpData.cpp
new file mode 100644
index 0000000..b333a44
--- /dev/null
+++ b/src/animation/AnimBlendClumpData.cpp
@@ -0,0 +1,36 @@
+#include "common.h"
+
+#include "AnimBlendClumpData.h"
+#include "MemoryMgr.h"
+
+CAnimBlendClumpData::CAnimBlendClumpData(void)
+{
+ numFrames = 0;
+ velocity2d = 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);
+}
diff --git a/src/animation/AnimBlendClumpData.h b/src/animation/AnimBlendClumpData.h
new file mode 100644
index 0000000..bb711b2
--- /dev/null
+++ b/src/animation/AnimBlendClumpData.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "AnimBlendList.h"
+
+
+struct AnimBlendFrameData
+{
+ enum {
+ IGNORE_ROTATION = 2,
+ IGNORE_TRANSLATION = 4,
+ VELOCITY_EXTRACTION = 8,
+ VELOCITY_EXTRACTION_3D = 0x10,
+ UPDATE_KEYFRAMES = 0x20,
+ COMPRESSED = 0x40
+ };
+
+ uint8 flag;
+ RwV3d resetPos;
+ union {
+ RwFrame *frame;
+ RpHAnimStdInterpFrame *hanimFrame;
+ };
+ int32 nodeID;
+};
+VALIDATE_SIZE(AnimBlendFrameData, 0x18);
+
+
+class CAnimBlendClumpData
+{
+public:
+ CAnimBlendLink link;
+ int32 numFrames;
+ union {
+ CVector2D *velocity2d;
+ CVector *velocity3d;
+ };
+ // order of frames is determined by RW hierarchy
+ AnimBlendFrameData *frames;
+
+ CAnimBlendClumpData(void);
+ ~CAnimBlendClumpData(void);
+ void SetNumberOfFrames(int n);
+ void SetNumberOfBones(int n) { SetNumberOfFrames(n); }
+ void ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg);
+};
diff --git a/src/animation/AnimBlendHierarchy.cpp b/src/animation/AnimBlendHierarchy.cpp
new file mode 100644
index 0000000..bfa39ef
--- /dev/null
+++ b/src/animation/AnimBlendHierarchy.cpp
@@ -0,0 +1,129 @@
+#include "common.h"
+
+#include "AnimBlendSequence.h"
+#include "AnimBlendHierarchy.h"
+#include "AnimManager.h"
+
+CAnimBlendHierarchy::CAnimBlendHierarchy(void)
+{
+ sequences = nil;
+ numSequences = 0;
+ compressed = 0;
+ totalLength = 0.0f;
+ linkPtr = nil;
+}
+
+void
+CAnimBlendHierarchy::Shutdown(void)
+{
+ CAnimManager::RemoveFromUncompressedCache(this);
+ RemoveAnimSequences();
+ totalLength = 0.0f;
+ compressed = 0;
+}
+
+void
+CAnimBlendHierarchy::SetName(char *name)
+{
+ strncpy(this->name, name, 24);
+}
+
+void
+CAnimBlendHierarchy::CalcTotalTime(void)
+{
+ int i, j;
+
+ totalLength = 0.0f;
+
+ for(i = 0; i < numSequences; i++){
+#ifdef FIX_BUGS
+ if(sequences[i].numFrames == 0)
+ continue;
+#endif
+
+ totalLength = Max(totalLength, sequences[i].GetKeyFrame(sequences[i].numFrames-1)->deltaTime);
+ for(j = sequences[i].numFrames-1; j >= 1; j--){
+ KeyFrame *kf1 = sequences[i].GetKeyFrame(j);
+ KeyFrame *kf2 = sequences[i].GetKeyFrame(j-1);
+ kf1->deltaTime -= kf2->deltaTime;
+ }
+ }
+}
+
+void
+CAnimBlendHierarchy::CalcTotalTimeCompressed(void)
+{
+ int i, j;
+
+ totalLength = 0.0f;
+
+ for(i = 0; i < numSequences; i++){
+#ifdef FIX_BUGS
+ if(sequences[i].numFrames == 0)
+ continue;
+#endif
+
+ totalLength = Max(totalLength, sequences[i].GetKeyFrameCompressed(sequences[i].numFrames-1)->GetDeltaTime());
+ for(j = sequences[i].numFrames-1; j >= 1; j--){
+ KeyFrameCompressed *kf1 = sequences[i].GetKeyFrameCompressed(j);
+ KeyFrameCompressed *kf2 = sequences[i].GetKeyFrameCompressed(j-1);
+ kf1->deltaTime -= kf2->deltaTime;
+ }
+ }
+}
+
+void
+CAnimBlendHierarchy::RemoveQuaternionFlips(void)
+{
+ int i;
+
+ for(i = 0; i < numSequences; i++)
+ sequences[i].RemoveQuaternionFlips();
+}
+
+void
+CAnimBlendHierarchy::RemoveAnimSequences(void)
+{
+ delete[] sequences;
+ sequences = nil;
+ numSequences = 0;
+}
+
+void
+CAnimBlendHierarchy::Uncompress(void)
+{
+#ifdef ANIM_COMPRESSION
+ int i;
+ assert(compressed);
+ for(i = 0; i < numSequences; i++)
+ sequences[i].Uncompress();
+#endif
+ compressed = 0;
+ if(totalLength == 0.0f){
+ RemoveQuaternionFlips();
+ CalcTotalTime();
+ }
+}
+
+void
+CAnimBlendHierarchy::RemoveUncompressedData(void)
+{
+#ifdef ANIM_COMPRESSION
+ int i;
+ assert(!compressed);
+ for(i = 0; i < numSequences; i++)
+ sequences[i].RemoveUncompressedData();
+#endif
+ compressed = 1;
+}
+
+#ifdef USE_CUSTOM_ALLOCATOR
+void
+CAnimBlendHierarchy::MoveMemory(bool onlyone)
+{
+ int i;
+ for(i = 0; i < numSequences; i++)
+ if(sequences[i].MoveMemory() && onlyone)
+ return;
+}
+#endif
diff --git a/src/animation/AnimBlendHierarchy.h b/src/animation/AnimBlendHierarchy.h
new file mode 100644
index 0000000..4838c4f
--- /dev/null
+++ b/src/animation/AnimBlendHierarchy.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "templates.h"
+
+#ifdef MoveMemory
+#undef MoveMemory // windows shit
+#endif
+
+class CAnimBlendSequence;
+
+// A collection of sequences
+class CAnimBlendHierarchy
+{
+public:
+ char name[24];
+ CAnimBlendSequence *sequences;
+ int16 numSequences;
+ bool compressed;
+ bool keepCompressed;
+ float totalLength;
+ CLink<CAnimBlendHierarchy*> *linkPtr;
+
+ CAnimBlendHierarchy(void);
+ void Shutdown(void);
+ void SetName(char *name);
+ void CalcTotalTime(void);
+ void CalcTotalTimeCompressed(void);
+ void RemoveQuaternionFlips(void);
+ void RemoveAnimSequences(void);
+ void Uncompress(void);
+ void RemoveUncompressedData(void);
+ void MoveMemory(bool onlyone = false);
+ bool IsCompressed() { return !!compressed; };
+};
+
+VALIDATE_SIZE(CAnimBlendHierarchy, 0x28); \ No newline at end of file
diff --git a/src/animation/AnimBlendList.h b/src/animation/AnimBlendList.h
new file mode 100644
index 0000000..018b598
--- /dev/null
+++ b/src/animation/AnimBlendList.h
@@ -0,0 +1,28 @@
+#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;
+ Init();
+ }
+};
diff --git a/src/animation/AnimBlendNode.cpp b/src/animation/AnimBlendNode.cpp
new file mode 100644
index 0000000..6352c11
--- /dev/null
+++ b/src/animation/AnimBlendNode.cpp
@@ -0,0 +1,314 @@
+#include "common.h"
+
+#include "AnimBlendAssociation.h"
+#include "AnimBlendNode.h"
+
+void
+CAnimBlendNode::Init(void)
+{
+ frameA = -1;
+ frameB = -1;
+ 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::UpdateCompressed(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 = NextKeyFrameCompressed();
+ }
+
+ float blend = association->GetBlendAmount(weight);
+ if(blend > 0.0f){
+ KeyFrameTransCompressed *kfA = (KeyFrameTransCompressed*)sequence->GetKeyFrameCompressed(frameA);
+ KeyFrameTransCompressed *kfB = (KeyFrameTransCompressed*)sequence->GetKeyFrameCompressed(frameB);
+ float t = kfA->deltaTime == 0 ? 0.0f : (kfA->GetDeltaTime() - remainingTime)/kfA->GetDeltaTime();
+ if(sequence->type & CAnimBlendSequence::KF_TRANS){
+ CVector transA, transB;
+ kfA->GetTranslation(&transA);
+ kfB->GetTranslation(&transB);
+ trans = transB + t*(transA - transB);
+ trans *= blend;
+ }
+ if(sequence->type & CAnimBlendSequence::KF_ROT){
+ CQuaternion rotA, rotB;
+ kfA->GetRotation(&rotA);
+ kfB->GetRotation(&rotB);
+ rot.Slerp(rotB, rotA, 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;
+}
+
+bool
+CAnimBlendNode::NextKeyFrameCompressed(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->GetKeyFrameCompressed(frameA)->GetDeltaTime();
+ }
+
+ frameB = frameA - 1;
+ if(frameB < 0)
+ frameB += sequence->numFrames;
+
+ CalcDeltasCompressed();
+ 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 == 1){
+ remainingTime = 0.0f;
+ }else{
+ // advance until t is between frameB and frameA
+ while (t > sequence->GetKeyFrame(++frameA)->deltaTime) {
+ t -= sequence->GetKeyFrame(frameA)->deltaTime;
+ if (frameA + 1 >= sequence->numFrames) {
+ // reached end of animation
+ if (!association->IsRepeating()) {
+ CalcDeltas();
+ remainingTime = 0.0f;
+ return false;
+ }
+ frameA = 0;
+ }
+ frameB = frameA;
+ }
+
+ remainingTime = sequence->GetKeyFrame(frameA)->deltaTime - t;
+ }
+
+ CalcDeltas();
+ return true;
+}
+
+bool
+CAnimBlendNode::SetupKeyFrameCompressed(void)
+{
+ if(sequence->numFrames < 1)
+ return false;
+
+ frameA = 1;
+ frameB = 0;
+
+ if(sequence->numFrames == 1){
+ frameA = 0;
+ remainingTime = 0.0f;
+ }else
+ remainingTime = sequence->GetKeyFrameCompressed(frameA)->GetDeltaTime();
+
+ CalcDeltasCompressed();
+ 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::CalcDeltasCompressed(void)
+{
+ if((sequence->type & CAnimBlendSequence::KF_ROT) == 0)
+ return;
+ KeyFrameCompressed *kfA = sequence->GetKeyFrameCompressed(frameA);
+ KeyFrameCompressed *kfB = sequence->GetKeyFrameCompressed(frameB);
+ CQuaternion rotA, rotB;
+ kfA->GetRotation(&rotA);
+ kfB->GetRotation(&rotB);
+ float cos = DotProduct(rotA, rotB);
+ if(cos < 0.0f){
+ rotB *= -1.0f;
+ kfB->SetRotation(rotB);
+ }
+ cos = DotProduct(rotA, rotB);
+ 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 == 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;
+ }
+ }
+}
+
+void
+CAnimBlendNode::GetCurrentTranslationCompressed(CVector &trans, float weight)
+{
+ trans = CVector(0.0f, 0.0f, 0.0f);
+
+ float blend = association->GetBlendAmount(weight);
+ if(blend > 0.0f){
+ KeyFrameTransCompressed *kfA = (KeyFrameTransCompressed*)sequence->GetKeyFrameCompressed(frameA);
+ KeyFrameTransCompressed *kfB = (KeyFrameTransCompressed*)sequence->GetKeyFrameCompressed(frameB);
+ float t = kfA->deltaTime == 0 ? 0.0f : (kfA->GetDeltaTime() - remainingTime)/kfA->GetDeltaTime();
+ if(sequence->type & CAnimBlendSequence::KF_TRANS){
+ CVector transA, transB;
+ kfA->GetTranslation(&transA);
+ kfB->GetTranslation(&transB);
+ trans = transB + t*(transA - transB);
+ 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;
+ }
+}
+
+void
+CAnimBlendNode::GetEndTranslationCompressed(CVector &trans, float weight)
+{
+ trans = CVector(0.0f, 0.0f, 0.0f);
+
+ float blend = association->GetBlendAmount(weight);
+ if(blend > 0.0f){
+ KeyFrameTransCompressed *kf = (KeyFrameTransCompressed*)sequence->GetKeyFrameCompressed(sequence->numFrames-1);
+ if(sequence->type & CAnimBlendSequence::KF_TRANS){
+ CVector pos;
+ kf->GetTranslation(&pos);
+ trans = pos * blend;
+ }
+ }
+}
diff --git a/src/animation/AnimBlendNode.h b/src/animation/AnimBlendNode.h
new file mode 100644
index 0000000..99a657a
--- /dev/null
+++ b/src/animation/AnimBlendNode.h
@@ -0,0 +1,37 @@
+#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 UpdateCompressed(CVector &trans, CQuaternion &rot, float weight);
+ bool NextKeyFrame(void);
+ bool NextKeyFrameCompressed(void);
+ bool FindKeyFrame(float t);
+ bool SetupKeyFrameCompressed(void);
+ void CalcDeltas(void);
+ void CalcDeltasCompressed(void);
+ void GetCurrentTranslation(CVector &trans, float weight);
+ void GetCurrentTranslationCompressed(CVector &trans, float weight);
+ void GetEndTranslation(CVector &trans, float weight);
+ void GetEndTranslationCompressed(CVector &trans, float weight);
+};
+
+
+VALIDATE_SIZE(CAnimBlendNode, 0x1C);
diff --git a/src/animation/AnimBlendSequence.cpp b/src/animation/AnimBlendSequence.cpp
new file mode 100644
index 0000000..36ac949
--- /dev/null
+++ b/src/animation/AnimBlendSequence.cpp
@@ -0,0 +1,200 @@
+#include "common.h"
+
+#include "AnimBlendSequence.h"
+#include "MemoryHeap.h"
+
+CAnimBlendSequence::CAnimBlendSequence(void)
+{
+ type = 0;
+ numFrames = 0;
+ keyFrames = nil;
+ keyFramesCompressed = nil;
+ boneTag = -1;
+}
+
+CAnimBlendSequence::~CAnimBlendSequence(void)
+{
+ if(keyFrames)
+ RwFree(keyFrames);
+ if(keyFramesCompressed)
+ RwFree(keyFramesCompressed);
+}
+
+void
+CAnimBlendSequence::SetName(char *name)
+{
+ strncpy(this->name, name, 24);
+}
+
+void
+CAnimBlendSequence::SetNumFrames(int numFrames, bool translation, bool compressed)
+{
+ if(translation){
+ type |= KF_ROT | KF_TRANS;
+ if(compressed)
+ keyFramesCompressed = RwMalloc(sizeof(KeyFrameTrans) * numFrames);
+ else
+ keyFrames = RwMalloc(sizeof(KeyFrameTrans) * numFrames);
+ }else{
+ type |= KF_ROT;
+ if(compressed)
+ keyFramesCompressed = RwMalloc(sizeof(KeyFrame) * numFrames);
+ else
+ keyFrames = RwMalloc(sizeof(KeyFrame) * 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;
+ }
+}
+
+void
+CAnimBlendSequence::Uncompress(void)
+{
+ int i;
+
+ if(numFrames == 0)
+ return;
+
+ PUSH_MEMID(MEMID_ANIMATION);
+
+ float rotScale = 1.0f/4096.0f;
+ float timeScale = 1.0f/60.0f;
+ float transScale = 1.0f/1024.0f;
+ if(type & KF_TRANS){
+ void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTrans));
+ KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)keyFramesCompressed;
+ KeyFrameTrans *kf = (KeyFrameTrans*)newKfs;
+ for(i = 0; i < numFrames; i++){
+ kf->rotation.x = ckf->rot[0]*rotScale;
+ kf->rotation.y = ckf->rot[1]*rotScale;
+ kf->rotation.z = ckf->rot[2]*rotScale;
+ kf->rotation.w = ckf->rot[3]*rotScale;
+ kf->deltaTime = ckf->deltaTime*timeScale;
+ kf->translation.x = ckf->trans[0]*transScale;
+ kf->translation.y = ckf->trans[1]*transScale;
+ kf->translation.z = ckf->trans[2]*transScale;
+ kf++;
+ ckf++;
+ }
+ keyFrames = newKfs;
+ }else{
+ void *newKfs = RwMalloc(numFrames * sizeof(KeyFrame));
+ KeyFrameCompressed *ckf = (KeyFrameCompressed*)keyFramesCompressed;
+ KeyFrame *kf = (KeyFrame*)newKfs;
+ for(i = 0; i < numFrames; i++){
+ kf->rotation.x = ckf->rot[0]*rotScale;
+ kf->rotation.y = ckf->rot[1]*rotScale;
+ kf->rotation.z = ckf->rot[2]*rotScale;
+ kf->rotation.w = ckf->rot[3]*rotScale;
+ kf->deltaTime = ckf->deltaTime*timeScale;
+ kf++;
+ ckf++;
+ }
+ keyFrames = newKfs;
+ }
+ REGISTER_MEMPTR(&keyFrames);
+
+ RwFree(keyFramesCompressed);
+ keyFramesCompressed = nil;
+
+ POP_MEMID();
+}
+
+void
+CAnimBlendSequence::CompressKeyframes(void)
+{
+ int i;
+
+ if(numFrames == 0)
+ return;
+
+ PUSH_MEMID(MEMID_ANIMATION);
+
+ float rotScale = 4096.0f;
+ float timeScale = 60.0f;
+ float transScale = 1024.0f;
+ if(type & KF_TRANS){
+ void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTransCompressed));
+ KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)newKfs;
+ KeyFrameTrans *kf = (KeyFrameTrans*)keyFrames;
+ for(i = 0; i < numFrames; i++){
+ ckf->rot[0] = kf->rotation.x*rotScale;
+ ckf->rot[1] = kf->rotation.y*rotScale;
+ ckf->rot[2] = kf->rotation.z*rotScale;
+ ckf->rot[3] = kf->rotation.w*rotScale;
+ ckf->deltaTime = kf->deltaTime*timeScale + 0.5f;
+ ckf->trans[0] = kf->translation.x*transScale;
+ ckf->trans[1] = kf->translation.y*transScale;
+ ckf->trans[2] = kf->translation.z*transScale;
+ kf++;
+ ckf++;
+ }
+ keyFramesCompressed = newKfs;
+ }else{
+ void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameCompressed));
+ KeyFrameCompressed *ckf = (KeyFrameCompressed*)newKfs;
+ KeyFrame *kf = (KeyFrame*)keyFrames;
+ for(i = 0; i < numFrames; i++){
+ ckf->rot[0] = kf->rotation.x*rotScale;
+ ckf->rot[1] = kf->rotation.y*rotScale;
+ ckf->rot[2] = kf->rotation.z*rotScale;
+ ckf->rot[3] = kf->rotation.w*rotScale;
+ ckf->deltaTime = kf->deltaTime*timeScale + 0.5f;
+ kf++;
+ ckf++;
+ }
+ keyFramesCompressed = newKfs;
+ }
+ REGISTER_MEMPTR(&keyFramesCompressed);
+
+ POP_MEMID();
+}
+
+void
+CAnimBlendSequence::RemoveUncompressedData(void)
+{
+ if(numFrames == 0)
+ return;
+ CompressKeyframes();
+ RwFree(keyFrames);
+ keyFrames = nil;
+}
+
+#ifdef USE_CUSTOM_ALLOCATOR
+bool
+CAnimBlendSequence::MoveMemory(void)
+{
+ if(keyFrames){
+ void *newaddr = gMainHeap.MoveMemory(keyFrames);
+ if(newaddr != keyFrames){
+ keyFrames = newaddr;
+ return true;
+ }
+ }else if(keyFramesCompressed){
+ void *newaddr = gMainHeap.MoveMemory(keyFramesCompressed);
+ if(newaddr != keyFramesCompressed){
+ keyFramesCompressed = newaddr;
+ return true;
+ }
+ }
+ return false;
+}
+#endif
diff --git a/src/animation/AnimBlendSequence.h b/src/animation/AnimBlendSequence.h
new file mode 100644
index 0000000..67118b2
--- /dev/null
+++ b/src/animation/AnimBlendSequence.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include "Quaternion.h"
+
+#ifdef MoveMemory
+#undef MoveMemory // windows shit
+#endif
+
+// TODO: put them somewhere else?
+struct KeyFrame {
+ CQuaternion rotation;
+ float deltaTime; // relative to previous key frame
+};
+
+struct KeyFrameTrans : KeyFrame {
+ CVector translation;
+};
+
+struct KeyFrameCompressed {
+ int16 rot[4]; // 4096
+ int16 deltaTime; // 60
+
+ void GetRotation(CQuaternion *quat){
+ float scale = 1.0f/4096.0f;
+ quat->x = rot[0]*scale;
+ quat->y = rot[1]*scale;
+ quat->z = rot[2]*scale;
+ quat->w = rot[3]*scale;
+ }
+ void SetRotation(const CQuaternion &quat){
+ rot[0] = quat.x * 4096.0f;
+ rot[1] = quat.y * 4096.0f;
+ rot[2] = quat.z * 4096.0f;
+ rot[3] = quat.w * 4096.0f;
+ }
+ float GetDeltaTime(void) { return deltaTime/60.0f; }
+ void SetTime(float t) { deltaTime = t*60.0f + 0.5f; }
+};
+
+struct KeyFrameTransCompressed : KeyFrameCompressed {
+ int16 trans[3]; // 1024
+
+ void GetTranslation(CVector *vec) {
+ float scale = 1.0f/1024.0f;
+ vec->x = trans[0]*scale;
+ vec->y = trans[1]*scale;
+ vec->z = trans[2]*scale;
+ }
+ void SetTranslation(const CVector &vec){
+ trans[0] = vec.x*1024.0f;
+ trans[1] = vec.y*1024.0f;
+ trans[2] = vec.z*1024.0f;
+ }
+};
+
+
+// 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;
+ int16 boneTag;
+ void *keyFrames;
+ void *keyFramesCompressed;
+
+ CAnimBlendSequence(void);
+ virtual ~CAnimBlendSequence(void);
+ void SetName(char *name);
+ void SetNumFrames(int numFrames, bool translation, bool compressed);
+ void RemoveQuaternionFlips(void);
+ KeyFrame *GetKeyFrame(int n) {
+ return type & KF_TRANS ?
+ &((KeyFrameTrans*)keyFrames)[n] :
+ &((KeyFrame*)keyFrames)[n];
+ }
+ KeyFrameCompressed *GetKeyFrameCompressed(int n) {
+ return type & KF_TRANS ?
+ &((KeyFrameTransCompressed*)keyFramesCompressed)[n] :
+ &((KeyFrameCompressed*)keyFramesCompressed)[n];
+ }
+ bool HasTranslation(void) { return !!(type & KF_TRANS); }
+ void Uncompress(void);
+ void CompressKeyframes(void);
+ void RemoveUncompressedData(void);
+ bool MoveMemory(void);
+
+ void SetBoneTag(int tag) { boneTag = tag; }
+};
+VALIDATE_SIZE(CAnimBlendSequence, 0x30);
diff --git a/src/animation/AnimManager.cpp b/src/animation/AnimManager.cpp
new file mode 100644
index 0000000..f6ac3eb
--- /dev/null
+++ b/src/animation/AnimManager.cpp
@@ -0,0 +1,1453 @@
+#include "common.h"
+
+#include "General.h"
+#include "RwHelper.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"
+#include "Streaming.h"
+
+CAnimBlock CAnimManager::ms_aAnimBlocks[NUMANIMBLOCKS];
+CAnimBlendHierarchy CAnimManager::ms_aAnimations[NUMANIMATIONS];
+int32 CAnimManager::ms_numAnimBlocks;
+int32 CAnimManager::ms_numAnimations;
+CAnimBlendAssocGroup *CAnimManager::ms_aAnimAssocGroups;
+CLinkList<CAnimBlendHierarchy*> CAnimManager::ms_animCache;
+
+AnimAssocDesc aStdAnimDescs[] = {
+ { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK },
+ { ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK },
+ { ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK },
+ { ANIM_STD_IDLE, ASSOC_REPEAT },
+ { ANIM_STD_STARTWALK, ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_RUNSTOP1, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_RUNSTOP2, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_IDLE_CAM, ASSOC_REPEAT | ASSOC_PARTIAL },
+ { ANIM_STD_IDLE_HBHB, ASSOC_REPEAT | ASSOC_PARTIAL },
+ { ANIM_STD_IDLE_TIRED, ASSOC_REPEAT },
+ { ANIM_STD_IDLE_BIGGUN, ASSOC_REPEAT | ASSOC_PARTIAL },
+ { ANIM_STD_CHAT, ASSOC_REPEAT | ASSOC_PARTIAL },
+ { ANIM_STD_HAILTAXI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_KO_FRONT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL },
+ { ANIM_STD_KO_LEFT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL },
+ { ANIM_STD_KO_BACK, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL },
+ { ANIM_STD_KO_RIGHT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL },
+ { ANIM_STD_KO_SHOT_FACE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL },
+ { ANIM_STD_KO_SHOT_STOMACH, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_KO_SHOT_ARM_L, ASSOC_PARTIAL | ASSOC_FRONTAL },
+ { ANIM_STD_KO_SHOT_ARM_R, ASSOC_PARTIAL | ASSOC_FRONTAL },
+ { ANIM_STD_KO_SHOT_LEG_L, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_KO_SHOT_LEG_R, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_SPINFORWARD_LEFT, ASSOC_PARTIAL | ASSOC_FRONTAL },
+ { ANIM_STD_SPINFORWARD_RIGHT, ASSOC_PARTIAL | ASSOC_FRONTAL },
+ { ANIM_STD_HIGHIMPACT_FRONT, ASSOC_PARTIAL },
+ { ANIM_STD_HIGHIMPACT_LEFT, ASSOC_PARTIAL },
+ { ANIM_STD_HIGHIMPACT_BACK, ASSOC_PARTIAL | ASSOC_FRONTAL },
+ { ANIM_STD_HIGHIMPACT_RIGHT, ASSOC_PARTIAL },
+ { ANIM_STD_HITBYGUN_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK },
+ { ANIM_STD_HITBYGUN_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK },
+ { ANIM_STD_HITBYGUN_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK },
+ { ANIM_STD_HITBYGUN_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK },
+ { ANIM_STD_HIT_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HIT_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_HIT_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HIT_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_HIT_FLOOR, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_HIT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HIT_CHEST, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HIT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HIT_WALK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HIT_WALL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HIT_FLOOR_FRONT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_FRONTAL },
+ { ANIM_STD_HIT_BEHIND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_IDLE, ASSOC_REPEAT },
+ { ANIM_STD_FIGHT_2IDLE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_SHUFFLE_F, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_FIGHT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_KICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_KNEE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_LHOOK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_ROUNDHOUSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_FIGHT_LONGKICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_PARTIAL_PUNCH, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_NOWALK },
+ { ANIM_STD_FIGHT_JAB, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_ELBOW_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_ELBOW_R, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_BKICK_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_BKICK_R, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_DETONATE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_PARTIALPUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_KICKGROUND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_THROW_UNDER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_FIGHT_SHUFFLE_B, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_JACKEDCAR_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_JACKEDCAR_LO_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_JACKEDCAR_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_JACKEDCAR_LO_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_QUICKJACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_QUICKJACKED, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_ALIGN_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_ALIGNHI_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_OPEN_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CARDOOR_LOCKED_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_PULL_OUT_PED_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_PULL_OUT_PED_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_GET_IN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_GET_IN_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_DOOR_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_JUMP_IN_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_GETOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_GETOUT_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_ALIGN_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_ALIGNHI_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_OPEN_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CARDOOR_LOCKED_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_PULL_OUT_PED_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_PULL_OUT_PED_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_GET_IN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_GET_IN_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_DOOR_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_SHUFFLE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_SHUFFLE_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_SIT, ASSOC_DELETEFADEDOUT},
+ { ANIM_STD_CAR_SIT_LO, ASSOC_DELETEFADEDOUT},
+ { ANIM_STD_CAR_SIT_P, ASSOC_DELETEFADEDOUT},
+ { ANIM_STD_CAR_SIT_P_LO, ASSOC_DELETEFADEDOUT},
+ { ANIM_STD_CAR_DRIVE_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_DRIVE_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_DRIVE_LEFT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_DRIVE_RIGHT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_DRIVEBY_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_DRIVEBY_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_DRIVEBY_LEFT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_DRIVEBY_RIGHT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_CAR_LOOKBEHIND, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_BOAT_DRIVE, ASSOC_DELETEFADEDOUT | ASSOC_DRIVING },
+ { ANIM_STD_BOAT_DRIVE_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_BOAT_DRIVE_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_BOAT_LOOKBEHIND, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_STD_BIKE_PICKUP_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_BIKE_PICKUP_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_BIKE_PULLUP_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_BIKE_PULLUP_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_BIKE_ELBOW_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_BIKE_ELBOW_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_BIKE_FALLOFF, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_BIKE_FALLBACK, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_GETOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_GETOUT_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_CLOSE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CAR_HOOKERTALK, ASSOC_REPEAT | ASSOC_PARTIAL },
+ { ANIM_STD_TRAIN_GETIN, ASSOC_PARTIAL },
+ { ANIM_STD_TRAIN_GETOUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CRAWLOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_CRAWLOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_ROLLOUT_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION },
+ { ANIM_STD_ROLLOUT_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION },
+ { ANIM_STD_GET_UP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_GET_UP_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_GET_UP_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_GET_UP_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_JUMP_LAUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_JUMP_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_JUMP_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_FALL, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_FALL_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_FALL_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_FALL_COLLAPSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_FALL_ONBACK, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_FALL_ONFRONT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_FRONTAL },
+ { ANIM_STD_EVADE_STEP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_EVADE_DIVE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL },
+ { ANIM_STD_XPRESS_SCRATCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_IDLE },
+ { ANIM_STD_ROADCROSS, ASSOC_REPEAT | ASSOC_PARTIAL },
+ { ANIM_STD_TURN180, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_ARREST, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_DROWN, ASSOC_PARTIAL },
+ { ANIM_STD_DUCK_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_DUCK_LOW, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_DUCK_WEAPON, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_RBLOCK_SHOOT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_HANDSUP, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_HANDSCOWER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_STD_PARTIAL_FUCKU, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK },
+ { ANIM_STD_PHONE_IN, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_PHONE_OUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_PHONE_TALK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+ { ANIM_STD_SEAT_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_SEAT_UP, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_SEAT_IDLE, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_SEAT_RVRS, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_ATM, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_ABSEIL, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
+};
+AnimAssocDesc aVanAnimDescs[] = {
+ { ANIM_STD_VAN_OPEN_DOOR_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_VAN_GET_IN_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_VAN_GET_OUT_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_VAN_OPEN_DOOR_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_VAN_GET_IN_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_VAN_GET_OUT_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+AnimAssocDesc aCoachAnimDescs[] = {
+ { ANIM_STD_COACH_OPEN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_COACH_OPEN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_COACH_GET_IN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_COACH_GET_IN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STD_COACH_GET_OUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+AnimAssocDesc aBikeAnimDescs[] = {
+ { ANIM_BIKE_RIDE, ASSOC_DELETEFADEDOUT},
+ { ANIM_BIKE_READY, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_LEFT, ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_RIGHT, ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_LEANB, ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_LEANF, ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_WALKBACK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_JUMPON_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_BIKE_JUMPON_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_BIKE_KICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_BIKE_HIT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_BIKE_GETOFF_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_BIKE_GETOFF_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_BIKE_GETOFF_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+ { ANIM_BIKE_DRIVEBY_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_DRIVEBY_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_DRIVEBY_FORWARD, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING },
+ { ANIM_BIKE_RIDE_P, ASSOC_DELETEFADEDOUT | ASSOC_DRIVING },
+};
+AnimAssocDesc aMeleeAnimDescs[] = {
+ { ANIM_MELEE_ATTACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_MELEE_ATTACK_2ND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_MELEE_ATTACK_START, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK },
+ { ANIM_MELEE_IDLE_FIGHTMODE, ASSOC_REPEAT },
+ { ANIM_MELEE_ATTACK_FINISH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION },
+};
+AnimAssocDesc aSwingAnimDescs[] = {
+ { ANIM_MELEE_ATTACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_MELEE_ATTACK_2ND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_MELEE_ATTACK_START, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_MELEE_IDLE_FIGHTMODE, ASSOC_REPEAT },
+ { ANIM_MELEE_ATTACK_FINISH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+AnimAssocDesc aWeaponAnimDescs[] = {
+ { ANIM_WEAPON_FIRE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_WEAPON_CROUCHFIRE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_WEAPON_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_WEAPON_CROUCHRELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_WEAPON_FIRE_3RD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+AnimAssocDesc aMedicAnimDescs[] = {
+ { ANIM_MEDIC_CPR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+AnimAssocDesc aSunbatheAnimDescs[] = {
+ { ANIM_SUNBATHE_IDLE, ASSOC_REPEAT | ASSOC_PARTIAL },
+ { ANIM_SUNBATHE_DOWN, ASSOC_REPEAT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION },
+ { ANIM_SUNBATHE_UP, ASSOC_REPEAT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION },
+ { ANIM_SUNBATHE_ESCAPE, ASSOC_REPEAT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION },
+};
+AnimAssocDesc aPlayerIdleAnimDescs[] = {
+ { ANIM_PLAYER_IDLE1, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_PLAYER_IDLE2, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_PLAYER_IDLE3, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_PLAYER_IDLE4, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+AnimAssocDesc aRiotAnimDescs[] = {
+ { ANIM_RIOT_ANGRY, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_RIOT_ANGRY_B, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_RIOT_CHANT, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_RIOT_PUNCHES, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_RIOT_SHOUT, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_RIOT_CHALLENGE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_RIOT_FUCKYOU, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+AnimAssocDesc aStripAnimDescs[] = {
+ { ANIM_STRIP_A, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STRIP_B, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STRIP_C, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STRIP_D, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STRIP_E, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STRIP_F, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+ { ANIM_STRIP_G, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
+};
+#ifdef PC_PLAYER_CONTROLS
+AnimAssocDesc aStdAnimDescsSide[] = {
+ { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION | ASSOC_WALK },
+ { ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION | ASSOC_WALK },
+ { ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION | ASSOC_WALK },
+ { ANIM_STD_IDLE, ASSOC_REPEAT },
+ { ANIM_STD_STARTWALK, ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION },
+};
+#endif
+char const* aStdAnimations[] = {
+ "walk_civi",
+ "run_civi",
+ "sprint_panic",
+ "idle_stance",
+ "walk_start",
+ "run_stop",
+ "run_stopR",
+ "idle_hbhb",
+ "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",
+ "FIGHTIDLE",
+ "FIGHT2IDLE",
+ "FIGHTsh_F",
+ "FIGHTbodyblow",
+ "FIGHThead",
+ "FIGHTkick",
+ "FIGHTknee",
+ "FIGHTLhook",
+ "FIGHTpunch",
+ "FIGHTrndhse",
+ "FIGHTlngkck",
+ "FIGHTppunch",
+ "FIGHTjab",
+ "FIGHTelbowL",
+ "FIGHTelbowR",
+ "FIGHTbkickL",
+ "FIGHTbkickR",
+ "bomber",
+ "punchR",
+ "FIGHTppunch",
+ "KICK_floor",
+ "WEAPON_throwu",
+ "FIGHTsh_back",
+ "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_jumpin_LHS",
+ "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",
+ "DrivebyL_L",
+ "DrivebyL_R",
+ "CAR_LB",
+ "DRIVE_BOAT",
+ "DRIVE_BOAT_L",
+ "DRIVE_BOAT_R",
+ "DRIVE_BOAT_back",
+ "BIKE_pickupR",
+ "BIKE_pickupL",
+ "BIKE_pullupR",
+ "BIKE_pullupL",
+ "BIKE_elbowR",
+ "BIKE_elbowL",
+ "BIKE_fall_off",
+ "BIKE_fallR",
+ "CAR_getout_RHS",
+ "CAR_getoutL_RHS",
+ "CAR_close_RHS",
+ "car_hookertalk",
+ "idle_stance",
+ "idle_stance",
+ "CAR_crawloutRHS",
+ "CAR_crawloutRHS",
+ "CAR_rollout_LHS",
+ "CAR_rollout_LHS",
+ "Getup",
+ "Getup",
+ "Getup",
+ "Getup_front",
+ "JUMP_launch",
+ "JUMP_glide",
+ "JUMP_land",
+ "FALL_fall",
+ "FALL_glide",
+ "FALL_land",
+ "FALL_collapse",
+ "FALL_back",
+ "FALL_front",
+ "EV_step",
+ "EV_dive",
+ "XPRESSscratch",
+ "roadcross",
+ "TURN_180",
+ "ARRESTgun",
+ "DROWN",
+ "DUCK_down",
+ "DUCK_low",
+ "WEAPON_crouch",
+ "RBLOCK_Cshoot",
+ "handsup",
+ "handsCOWER",
+ "FUCKU",
+ "PHONE_in",
+ "PHONE_out",
+ "PHONE_talk",
+ "SEAT_down",
+ "SEAT_up",
+ "SEAT_idle",
+ "SEAT_down",
+ "ATM",
+ "abseil",
+};
+char const* aVanAnimations[] = {
+ "VAN_openL",
+ "VAN_getinL",
+ "VAN_closeL",
+ "VAN_getoutL",
+ "VAN_open",
+ "VAN_getin",
+ "VAN_close",
+ "VAN_getout",
+};
+char const* aCoachAnimations[] = {
+ "COACH_opnL",
+ "COACH_opnL",
+ "COACH_inL",
+ "COACH_inL",
+ "COACH_outL",
+};
+char const* aBikesAnimations[] = {
+ "BIKEs_Ride",
+ "BIKEs_Still",
+ "BIKEs_Left",
+ "BIKEs_Right",
+ "BIKEs_Back",
+ "BIKEs_Fwd",
+ "BIKEs_pushes",
+ "BIKEs_jumponR",
+ "BIKEs_jumponL",
+ "BIKEs_kick",
+ "BIKEs_hit",
+ "BIKEs_getoffRHS",
+ "BIKEs_getoffLHS",
+ "BIKEs_getoffBACK",
+ "BIKEs_drivebyLHS",
+ "BIKEs_drivebyRHS",
+ "BIKEs_drivebyFT",
+ "BIKEs_passenger",
+};
+char const* aBikevAnimations[] = {
+ "BIKEv_Ride",
+ "BIKEv_Still",
+ "BIKEv_Left",
+ "BIKEv_Right",
+ "BIKEv_Back",
+ "BIKEv_Fwd",
+ "BIKEv_pushes",
+ "BIKEv_jumponR",
+ "BIKEv_jumponL",
+ "BIKEv_kick",
+ "BIKEv_hit",
+ "BIKEv_getoffRHS",
+ "BIKEv_getoffLHS",
+ "BIKEv_getoffBACK",
+ "BIKEv_drivebyLHS",
+ "BIKEv_drivebyRHS",
+ "BIKEv_drivebyFT",
+ "BIKEv_passenger",
+};
+char const* aBikehAnimations[] = {
+ "BIKEh_Ride",
+ "BIKEh_Still",
+ "BIKEh_Left",
+ "BIKEh_Right",
+ "BIKEh_Back",
+ "BIKEh_Fwd",
+ "BIKEh_pushes",
+ "BIKEh_jumponR",
+ "BIKEh_jumponL",
+ "BIKEh_kick",
+ "BIKEh_hit",
+ "BIKEh_getoffRHS",
+ "BIKEh_getoffLHS",
+ "BIKEh_getoffBACK",
+ "BIKEh_drivebyLHS",
+ "BIKEh_drivebyRHS",
+ "BIKEh_drivebyFT",
+ "BIKEh_passenger",
+};
+char const* aBikedAnimations[] = {
+ "BIKEd_Ride",
+ "BIKEd_Still",
+ "BIKEd_Left",
+ "BIKEd_Right",
+ "BIKEd_Back",
+ "BIKEd_Fwd",
+ "BIKEd_pushes",
+ "BIKEd_jumponR",
+ "BIKEd_jumponL",
+ "BIKEd_kick",
+ "BIKEd_hit",
+ "BIKEd_getoffRHS",
+ "BIKEd_getoffLHS",
+ "BIKEd_getoffBACK",
+ "BIKEd_drivebyLHS",
+ "BIKEd_drivebyRHS",
+ "BIKEd_drivebyFT",
+ "BIKEd_passenger",
+};
+char const* aUnarmedAnimations[] = {
+ "punchR",
+ "KICK_floor",
+ "FIGHTppunch",
+};
+char const* aScrewdriverAnimations[] = {
+ "FIGHTbodyblow",
+ "FIGHTbodyblow",
+ "FIGHTppunch",
+ "FIGHTIDLE",
+ "FIGHTbodyblow",
+};
+char const* aKnifeAnimations[] = {
+ "WEAPON_knife_1",
+ "WEAPON_knife_2",
+ "knife_part",
+ "WEAPON_knifeidle",
+ "WEAPON_knife_3",
+};
+char const* aBaseballbatAnimations[] = {
+ "WEAPON_bat_h",
+ "WEAPON_bat_v",
+ "BAT_PART",
+ "WEAPON_bat_h",
+ "WEAPON_golfclub",
+};
+char const* aGolfclubAnimations[] = {
+ "WEAPON_bat_h",
+ "WEAPON_golfclub",
+ "BAT_PART",
+ "WEAPON_bat_h",
+ "WEAPON_bat_v",
+};
+char const* aChainsawAnimations[] = {
+ "WEAPON_csaw",
+ "WEAPON_csawlo",
+ "csaw_part",
+};
+char const* aPythonAnimations[] = {
+ "python_fire",
+ "python_crouchfire",
+ "python_reload",
+ "python_crouchreload",
+};
+char const* aColtAnimations[] = {
+ "colt45_fire",
+ "colt45_crouchfire",
+ "colt45_reload",
+ "colt45_crouchreload",
+ "colt45_cop",
+};
+char const* aShotgunAnimations[] = {
+ "shotgun_fire",
+ "shotgun_crouchfire",
+};
+char const* aBuddyAnimations[] = {
+ "buddy_fire",
+ "buddy_crouchfire",
+};
+char const* aTecAnimations[] = {
+ "TEC_fire",
+ "TEC_crouchfire",
+ "TEC_reload",
+ "TEC_crouchreload",
+};
+char const* aUziAnimations[] = {
+ "UZI_fire",
+ "UZI_crouchfire",
+ "UZI_reload",
+ "UZI_crouchreload",
+};
+char const* aRifleAnimations[] = {
+ "RIFLE_fire",
+ "RIFLE_crouchfire",
+ "RIFLE_load",
+ "RIFLE_crouchload",
+};
+char const* aM60Animations[] = {
+ "M60_fire",
+ "M60_fire",
+ "M60_reload",
+};
+char const* aSniperAnimations[] = {
+ "WEAPON_sniper",
+};
+char const* aThrowAnimations[] = {
+ "WEAPON_throw",
+ "WEAPON_throwu",
+ "WEAPON_start_throw",
+};
+char const* aFlamethrowerAnimations[] = {
+ "FLAME_fire",
+};
+char const* aMedicAnimations[] = {
+ "CPR",
+};
+char const* aSunbatheAnimations[] = {
+ "bather",
+ "batherdown",
+ "batherup",
+ "batherscape",
+};
+char const* aPlayerIdleAnimations[] = {
+ "stretch",
+ "time",
+ "shldr",
+ "strleg",
+};
+char const* aRiotAnimations[] = {
+ "riot_angry",
+ "riot_angry_b",
+ "riot_chant",
+ "riot_punches",
+ "riot_shout",
+ "riot_challenge",
+ "riot_fuku",
+};
+char const* aStripAnimations[] = {
+ "strip_A",
+ "strip_B",
+ "strip_C",
+ "strip_D",
+ "strip_E",
+ "strip_F",
+ "strip_G",
+};
+char const* aLanceAnimations[] = {
+ "lance",
+};
+char const* aPlayerAnimations[] = {
+ "walk_player",
+ "run_player",
+ "SPRINT_civi",
+ "IDLE_STANCE",
+ "walk_start",
+};
+char const* aPlayerWithRocketAnimations[] = {
+ "walk_rocket",
+ "run_rocket",
+ "run_rocket",
+ "idle_rocket",
+ "walk_start_rocket",
+};
+char const* aPlayer1ArmedAnimations[] = {
+ "walk_player",
+ "run_1armed",
+ "SPRINT_civi",
+ "IDLE_STANCE",
+ "walk_start",
+};
+char const* aPlayer2ArmedAnimations[] = {
+ "walk_armed",
+ "run_armed",
+ "run_armed",
+ "idle_armed",
+ "walk_start_armed",
+};
+char const* aPlayerBBBatAnimations[] = {
+ "walk_player",
+ "run_player",
+ "run_player",
+ "IDLE_STANCE",
+ "walk_start",
+};
+char const* aPlayerChainsawAnimations[] = {
+ "walk_csaw",
+ "run_csaw",
+ "run_csaw",
+ "IDLE_csaw",
+ "walk_start_csaw",
+};
+char const* aShuffleAnimations[] = {
+ "WALK_shuffle",
+ "RUN_civi",
+ "SPRINT_civi",
+ "IDLE_STANCE",
+};
+char const* aOldAnimations[] = {
+ "walk_old",
+ "run_civi",
+ "sprint_civi",
+ "idle_stance",
+};
+char const* aGang1Animations[] = {
+ "walk_gang1",
+ "run_gang1",
+ "sprint_civi",
+ "idle_stance",
+};
+char const* aGang2Animations[] = {
+ "walk_gang2",
+ "run_gang1",
+ "sprint_civi",
+ "idle_stance",
+};
+char const* aFatAnimations[] = {
+ "walk_fat",
+ "run_civi",
+ "woman_runpanic",
+ "idle_stance",
+};
+char const* aOldFatAnimations[] = {
+ "walk_fatold",
+ "run_fatold",
+ "woman_runpanic",
+ "idle_stance",
+};
+char const* aJoggerAnimations[] = {
+ "JOG_maleA",
+ "run_civi",
+ "sprint_civi",
+ "idle_stance",
+};
+char const* aStdWomanAnimations[] = {
+ "woman_walknorm",
+ "woman_run",
+ "woman_runpanic",
+ "woman_idlestance",
+};
+char const* aWomanShopAnimations[] = {
+ "woman_walkshop",
+ "woman_run",
+ "woman_run",
+ "woman_idlestance",
+};
+char const* aBusyWomanAnimations[] = {
+ "woman_walkbusy",
+ "woman_run",
+ "woman_runpanic",
+ "woman_idlestance",
+};
+char const* aSexyWomanAnimations[] = {
+ "woman_walksexy",
+ "woman_run",
+ "woman_runpanic",
+ "woman_idlestance",
+};
+char const* aFatWomanAnimations[] = {
+ "walk_fat",
+ "woman_run",
+ "woman_runpanic",
+ "woman_idlestance",
+};
+char const* aOldWomanAnimations[] = {
+ "woman_walkold",
+ "woman_run",
+ "woman_runpanic",
+ "woman_idlestance",
+};
+char const* aJoggerWomanAnimations[] = {
+ "JOG_maleB",
+ "woman_run",
+ "woman_runpanic",
+ "woman_idlestance",
+};
+char const* aPanicChunkyAnimations[] = {
+ "run_fatold",
+ "woman_runpanic",
+ "woman_runpanic",
+ "idle_stance",
+};
+char const* aSkateAnimations[] = {
+ "skate_run",
+ "skate_sprint",
+ "skate_sprint",
+ "skate_idle",
+};
+#ifdef PC_PLAYER_CONTROLS
+char const* aPlayerStrafeBackAnimations[] = {
+ "walk_back",
+ "run_back",
+ "run_back",
+ "IDLE_STANCE",
+ "walk_start_back",
+};
+char const* aPlayerStrafeLeftAnimations[] = {
+ "walk_left",
+ "run_left",
+ "run_left",
+ "IDLE_STANCE",
+ "walk_start_left",
+};
+char const* aPlayerStrafeRightAnimations[] = {
+ "walk_right",
+ "run_right",
+ "run_right",
+ "IDLE_STANCE",
+ "walk_start_right",
+};
+char const* aRocketStrafeBackAnimations[] = {
+ "walk_rocket_back",
+ "run_rocket_back",
+ "run_rocket_back",
+ "idle_rocket",
+ "walkst_rocket_back",
+};
+char const* aRocketStrafeLeftAnimations[] = {
+ "walk_rocket_left",
+ "run_rocket_left",
+ "run_rocket_left",
+ "idle_rocket",
+ "walkst_rocket_left",
+};
+char const* aRocketStrafeRightAnimations[] = {
+ "walk_rocket_right",
+ "run_rocket_right",
+ "run_rocket_right",
+ "idle_rocket",
+ "walkst_rocket_right",
+};
+char const* aChainsawStrafeBackAnimations[] = {
+ "walk_csaw_back",
+ "run_csaw_back",
+ "run_csaw_back",
+ "idle_csaw",
+ "walkst_csaw_back",
+};
+char const* aChainsawStrafeLeftAnimations[] = {
+ "walk_csaw_left",
+ "run_csaw_left",
+ "run_csaw_left",
+ "idle_csaw",
+ "walkst_csaw_left",
+};
+char const* aChainsawStrafeRightAnimations[] = {
+ "walk_csaw_right",
+ "run_csaw_right",
+ "run_csaw_right",
+ "idle_csaw",
+ "walkst_csaw_right",
+};
+#endif
+
+#define awc(a) ARRAY_SIZE(a), a
+const AnimAssocDefinition CAnimManager::ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS] = {
+ { "man", "ped", MI_COP, awc(aStdAnimations), aStdAnimDescs },
+ { "van", "van", MI_COP, awc(aVanAnimations), aVanAnimDescs },
+ { "coach", "coach", MI_COP, awc(aCoachAnimations), aCoachAnimDescs },
+ { "bikes", "bikes", MI_COP, awc(aBikesAnimations), aBikeAnimDescs },
+ { "bikev", "bikev", MI_COP, awc(aBikevAnimations), aBikeAnimDescs },
+ { "bikeh", "bikeh", MI_COP, awc(aBikehAnimations), aBikeAnimDescs },
+ { "biked", "biked", MI_COP, awc(aBikedAnimations), aBikeAnimDescs },
+ { "unarmed", "ped", MI_COP, awc(aUnarmedAnimations), aMeleeAnimDescs },
+ { "screwdrv", "ped", MI_COP, awc(aScrewdriverAnimations), aMeleeAnimDescs },
+ { "knife", "knife", MI_COP, awc(aKnifeAnimations), aMeleeAnimDescs },
+ { "baseball", "baseball", MI_COP, awc(aBaseballbatAnimations), aSwingAnimDescs },
+ { "golfclub", "baseball", MI_COP, awc(aGolfclubAnimations), aSwingAnimDescs },
+ { "chainsaw", "chainsaw", MI_COP, awc(aChainsawAnimations), aMeleeAnimDescs },
+ { "python", "python", MI_COP, awc(aPythonAnimations), aWeaponAnimDescs },
+ { "colt45", "colt45", MI_COP, awc(aColtAnimations), aWeaponAnimDescs },
+ { "shotgun", "shotgun", MI_COP, awc(aShotgunAnimations), aWeaponAnimDescs },
+ { "buddy", "buddy", MI_COP, awc(aBuddyAnimations), aWeaponAnimDescs },
+ { "tec", "tec", MI_COP, awc(aTecAnimations), aWeaponAnimDescs },
+ { "uzi", "uzi", MI_COP, awc(aUziAnimations), aWeaponAnimDescs },
+ { "rifle", "rifle", MI_COP, awc(aRifleAnimations), aWeaponAnimDescs },
+ { "m60", "m60", MI_COP, awc(aM60Animations), aWeaponAnimDescs },
+ { "sniper", "sniper", MI_COP, awc(aSniperAnimations), aWeaponAnimDescs },
+ { "grenade", "grenade", MI_COP, awc(aThrowAnimations), aWeaponAnimDescs },
+ { "flame", "flame", MI_COP, awc(aFlamethrowerAnimations), aWeaponAnimDescs },
+ { "medic", "medic", MI_COP, awc(aMedicAnimations), aMedicAnimDescs },
+ { "sunbathe", "sunbathe", MI_COP, 1, aSunbatheAnimations, aSunbatheAnimDescs }, // NB: not using awc here!
+ { "playidles", "playidles", MI_COP, awc(aPlayerIdleAnimations), aPlayerIdleAnimDescs },
+ { "riot", "riot", MI_COP, awc(aRiotAnimations), aRiotAnimDescs },
+ { "strip", "strip", MI_COP, awc(aStripAnimations), aStripAnimDescs },
+ { "lance", "lance", MI_COP, awc(aLanceAnimations), aSunbatheAnimDescs },
+ { "player", "ped", MI_COP, awc(aPlayerAnimations), aStdAnimDescs },
+ { "playerrocket", "ped", MI_COP, awc(aPlayerWithRocketAnimations), aStdAnimDescs },
+ { "player1armed", "ped", MI_COP, awc(aPlayer1ArmedAnimations), aStdAnimDescs },
+ { "player2armed", "ped", MI_COP, awc(aPlayer2ArmedAnimations), aStdAnimDescs },
+ { "playerBBBat", "ped", MI_COP, awc(aPlayerBBBatAnimations), aStdAnimDescs },
+ { "playercsaw", "ped", MI_COP, awc(aPlayerChainsawAnimations), aStdAnimDescs },
+ { "shuffle", "ped", MI_COP, awc(aShuffleAnimations), aStdAnimDescs },
+ { "oldman", "ped", MI_COP, awc(aOldAnimations), aStdAnimDescs },
+ { "gang1", "ped", MI_COP, awc(aGang1Animations), aStdAnimDescs },
+ { "gang2", "ped", MI_COP, awc(aGang2Animations), aStdAnimDescs },
+ { "fatman", "ped", MI_COP, awc(aFatAnimations), aStdAnimDescs },
+ { "oldfatman", "ped", MI_COP, awc(aOldFatAnimations), aStdAnimDescs },
+ { "jogger", "ped", MI_COP, awc(aJoggerAnimations), aStdAnimDescs },
+ { "woman", "ped", MI_COP, awc(aStdWomanAnimations), aStdAnimDescs },
+ { "shopping", "ped", MI_COP, awc(aWomanShopAnimations), aStdAnimDescs },
+ { "busywoman", "ped", MI_COP, awc(aBusyWomanAnimations), aStdAnimDescs },
+ { "sexywoman", "ped", MI_COP, awc(aSexyWomanAnimations), aStdAnimDescs },
+ { "fatwoman", "ped", MI_COP, awc(aFatWomanAnimations), aStdAnimDescs },
+ { "oldwoman", "ped", MI_COP, awc(aOldWomanAnimations), aStdAnimDescs },
+ { "jogwoman", "ped", MI_COP, awc(aJoggerWomanAnimations), aStdAnimDescs },
+ { "panicchunky", "ped", MI_COP, awc(aPanicChunkyAnimations), aStdAnimDescs },
+ { "skate", "skate", MI_COP, awc(aSkateAnimations), aStdAnimDescs },
+#ifdef PC_PLAYER_CONTROLS
+ { "playerback", "ped", MI_COP, awc(aPlayerStrafeBackAnimations), aStdAnimDescs },
+ { "playerleft", "ped", MI_COP, awc(aPlayerStrafeLeftAnimations), aStdAnimDescsSide },
+ { "playerright", "ped", MI_COP, awc(aPlayerStrafeRightAnimations), aStdAnimDescsSide },
+ { "rocketback", "ped", MI_COP, awc(aRocketStrafeBackAnimations), aStdAnimDescs },
+ { "rocketleft", "ped", MI_COP, awc(aRocketStrafeLeftAnimations), aStdAnimDescsSide },
+ { "rocketright", "ped", MI_COP, awc(aRocketStrafeRightAnimations), aStdAnimDescsSide },
+ { "csawback", "ped", MI_COP, awc(aChainsawStrafeBackAnimations), aStdAnimDescs },
+ { "csawleft", "ped", MI_COP, awc(aChainsawStrafeLeftAnimations), aStdAnimDescsSide },
+ { "csawright", "ped", MI_COP, awc(aChainsawStrafeRightAnimations), aStdAnimDescsSide },
+#endif
+};
+#undef awc
+
+void
+CAnimManager::Initialise(void)
+{
+ ms_numAnimations = 0;
+ ms_numAnimBlocks = 0;
+ ms_animCache.Init(25);
+}
+
+void
+CAnimManager::Shutdown(void)
+{
+ int i;
+
+ for(i = 0; i < NUMANIMBLOCKS; i++)
+ CStreaming::RemoveAnim(i);
+
+ for(i = 0; i < ms_numAnimations; i++)
+ ms_aAnimations[i].Shutdown();
+
+ ms_animCache.Shutdown();
+
+ delete[] ms_aAnimAssocGroups;
+}
+
+void
+CAnimManager::UncompressAnimation(CAnimBlendHierarchy *hier)
+{
+ if(hier->keepCompressed){
+ if(hier->totalLength == 0.0f)
+ hier->CalcTotalTimeCompressed();
+ }else{
+ 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){
+ CAnimBlendHierarchy *lastHier = ms_animCache.tail.prev->item;
+ lastHier->RemoveUncompressedData();
+ ms_animCache.Remove(ms_animCache.tail.prev);
+ lastHier->linkPtr = nil;
+ link = ms_animCache.Insert(hier);
+ }
+ hier->linkPtr = link;
+ hier->Uncompress();
+ }
+ }
+}
+
+void
+CAnimManager::RemoveFromUncompressedCache(CAnimBlendHierarchy *hier)
+{
+ if(hier->linkPtr){
+ ms_animCache.Remove(hier->linkPtr);
+ hier->linkPtr = nil;
+ }
+}
+
+CAnimBlock*
+CAnimManager::GetAnimationBlock(const char *name)
+{
+ int i;
+
+ for(i = 0; i < ms_numAnimBlocks; i++)
+ if(strcasecmp(ms_aAnimBlocks[i].name, name) == 0)
+ return &ms_aAnimBlocks[i];
+ return nil;
+}
+
+int32
+CAnimManager::GetAnimationBlockIndex(const char *name)
+{
+ int i;
+
+ for(i = 0; i < ms_numAnimBlocks; i++)
+ if(strcasecmp(ms_aAnimBlocks[i].name, name) == 0)
+ return i;
+ return -1;
+}
+
+int32
+CAnimManager::RegisterAnimBlock(const char *name)
+{
+ CAnimBlock *animBlock = GetAnimationBlock(name);
+ if(animBlock == nil){
+ animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++];
+ strncpy(animBlock->name, name, MAX_ANIMBLOCK_NAME);
+ animBlock->numAnims = 0;
+ assert(animBlock->refCount == 0);
+ }
+ return animBlock - ms_aAnimBlocks;
+}
+
+int32
+CAnimManager::GetNumRefsToAnimBlock(int32 block)
+{
+ return ms_aAnimBlocks[block].refCount;
+}
+
+void
+CAnimManager::AddAnimBlockRef(int32 block)
+{
+ ms_aAnimBlocks[block].refCount++;
+}
+
+void
+CAnimManager::RemoveAnimBlockRefWithoutDelete(int32 block)
+{
+ ms_aAnimBlocks[block].refCount--;
+}
+
+void
+CAnimManager::RemoveAnimBlockRef(int32 block)
+{
+ ms_aAnimBlocks[block].refCount--;
+ if(ms_aAnimBlocks[block].refCount == 0)
+ CStreaming::RemoveAnim(block);
+}
+
+void
+CAnimManager::RemoveAnimBlock(int32 block)
+{
+ int i;
+ CAnimBlock *animblock;
+
+ animblock = &ms_aAnimBlocks[block];
+ debug("Removing ANIMS %s\n", animblock->name);
+ for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++)
+ if(ms_aAnimAssocGroups[i].animBlock == animblock)
+ ms_aAnimAssocGroups[i].DestroyAssociations();
+ for(i = 0; i < animblock->numAnims; i++)
+ ms_aAnimations[animblock->firstIndex + i].Shutdown();
+ animblock->isLoaded = false;
+ animblock->refCount = 0;
+}
+
+CAnimBlendHierarchy*
+CAnimManager::GetAnimation(const char *name, CAnimBlock *animBlock)
+{
+ int i;
+ CAnimBlendHierarchy *hier = &ms_aAnimations[animBlock->firstIndex];
+
+ for(i = 0; i < animBlock->numAnims; i++){
+ if(strcasecmp(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)
+{
+ LoadAnimFile("ANIM\\PED.IFP");
+ ms_aAnimAssocGroups = new CAnimBlendAssocGroup[NUM_ANIM_ASSOC_GROUPS];
+ CreateAnimAssocGroups();
+}
+
+void
+CAnimManager::CreateAnimAssocGroups(void)
+{
+ int i, j;
+
+ for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++){
+ CAnimBlock *block = GetAnimationBlock(ms_aAnimAssocDefinitions[i].blockName);
+ if(block == nil || !block->isLoaded || ms_aAnimAssocGroups[i].assocList)
+ continue;
+
+ CBaseModelInfo *mi = CModelInfo::GetModelInfo(ms_aAnimAssocDefinitions[i].modelIndex);
+ RpClump *clump = (RpClump*)mi->CreateInstance();
+ RpAnimBlendClumpInit(clump);
+ CAnimBlendAssocGroup *group = &ms_aAnimAssocGroups[i];
+ const AnimAssocDefinition *def = &ms_aAnimAssocDefinitions[i];
+ group->groupId = i;
+ group->firstAnimId = def->animDescs[0].animId;
+ group->CreateAssociations(def->blockName, clump, def->animNames, def->numAnims);
+ for(j = 0; j < group->numAssociations; j++)
+ // GetAnimation(i) in III (but it's in LoadAnimFiles), GetAnimation(group->animDesc[j].animId) in VC
+ group->GetAnimation(def->animDescs[j].animId)->flags |= def->animDescs[j].flags;
+ if(IsClumpSkinned(clump))
+ RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil);
+ RpClumpDestroy(clump);
+ }
+}
+
+void
+CAnimManager::LoadAnimFile(const char *filename)
+{
+ RwStream *stream;
+ stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename);
+ assert(stream);
+ LoadAnimFile(stream, true);
+ RwStreamClose(stream, nil);
+}
+
+void
+CAnimManager::LoadAnimFile(RwStream *stream, bool compress, char (*uncompressedAnims)[32])
+{
+ #define ROUNDSIZE(x) if((x) & 3) (x) += 4 - ((x)&3)
+ struct IfpHeader {
+ char ident[4];
+ uint32 size;
+ };
+ IfpHeader anpk, info, name, dgan, cpan, anim;
+ char buf[256];
+ int j, k, l;
+ float *fbuf = (float*)buf;
+
+ // block name
+ RwStreamRead(stream, &anpk, sizeof(IfpHeader));
+ ROUNDSIZE(anpk.size);
+ RwStreamRead(stream, &info, sizeof(IfpHeader));
+ ROUNDSIZE(info.size);
+ RwStreamRead(stream, buf, info.size);
+ CAnimBlock *animBlock = GetAnimationBlock(buf+4);
+ if(animBlock){
+ if(animBlock->numAnims == 0){
+ animBlock->numAnims = *(int*)buf;
+ animBlock->firstIndex = ms_numAnimations;
+ }
+ }else{
+ animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++];
+ strncpy(animBlock->name, buf+4, MAX_ANIMBLOCK_NAME);
+ animBlock->numAnims = *(int*)buf;
+ animBlock->firstIndex = ms_numAnimations;
+ }
+
+ debug("Loading ANIMS %s\n", animBlock->name);
+ animBlock->isLoaded = true;
+
+ int animIndex = animBlock->firstIndex;
+ for(j = 0; j < animBlock->numAnims; j++){
+ assert(animIndex < ARRAY_SIZE(ms_aAnimations));
+ CAnimBlendHierarchy *hier = &ms_aAnimations[animIndex++];
+
+ // animation name
+ RwStreamRead(stream, &name, sizeof(IfpHeader));
+ ROUNDSIZE(name.size);
+ RwStreamRead(stream, buf, name.size);
+ hier->SetName(buf);
+
+#ifdef ANIM_COMPRESSION
+ bool compressHier = compress;
+#else
+ bool compressHier = false;
+#endif
+ if (uncompressedAnims) {
+ for (int i = 0; uncompressedAnims[i][0]; i++) {
+ if (!CGeneral::faststricmp(uncompressedAnims[i], hier->name)){
+ debug("Loading %s uncompressed\n", hier->name);
+ compressHier = false;
+ }
+ }
+ }
+
+ hier->compressed = compressHier;
+ hier->keepCompressed = false;
+
+ // DG info has number of nodes/sequences
+ RwStreamRead(stream, (char*)&dgan, sizeof(IfpHeader));
+ ROUNDSIZE(dgan.size);
+ RwStreamRead(stream, (char*)&info, sizeof(IfpHeader));
+ ROUNDSIZE(info.size);
+ RwStreamRead(stream, 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
+ RwStreamRead(stream, &cpan, sizeof(IfpHeader));
+ ROUNDSIZE(dgan.size);
+ RwStreamRead(stream, &anim, sizeof(IfpHeader));
+ ROUNDSIZE(anim.size);
+ RwStreamRead(stream, buf, anim.size);
+ int numFrames = *(int*)(buf+28);
+ seq->SetName(buf);
+ if(anim.size == 44)
+ seq->SetBoneTag(*(int*)(buf+40));
+ if(numFrames == 0)
+ continue;
+
+ bool hasScale = false;
+ bool hasTranslation = false;
+ RwStreamRead(stream, &info, sizeof(info));
+ if(strncmp(info.ident, "KRTS", 4) == 0){
+ hasScale = true;
+ seq->SetNumFrames(numFrames, true, compressHier);
+ }else if(strncmp(info.ident, "KRT0", 4) == 0){
+ hasTranslation = true;
+ seq->SetNumFrames(numFrames, true, compressHier);
+ }else if(strncmp(info.ident, "KR00", 4) == 0){
+ seq->SetNumFrames(numFrames, false, compressHier);
+ }
+ if(strstr(seq->name, "L Toe"))
+ debug("anim %s has toe keyframes\n", hier->name); // BUG: seq->name
+
+ for(l = 0; l < numFrames; l++){
+ if(hasScale){
+ RwStreamRead(stream, buf, 0x2C);
+ CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
+ rot.Invert();
+ CVector trans(fbuf[4], fbuf[5], fbuf[6]);
+
+ if(compressHier){
+ KeyFrameTransCompressed *kf = (KeyFrameTransCompressed*)seq->GetKeyFrameCompressed(l);
+ kf->SetRotation(rot);
+ kf->SetTranslation(trans);
+ // scaling ignored
+ kf->SetTime(fbuf[10]); // absolute time here
+ }else{
+ KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l);
+ kf->rotation = rot;
+ kf->translation = trans;
+ // scaling ignored
+ kf->deltaTime = fbuf[10]; // absolute time here
+ }
+ }else if(hasTranslation){
+ RwStreamRead(stream, buf, 0x20);
+ CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
+ rot.Invert();
+ CVector trans(fbuf[4], fbuf[5], fbuf[6]);
+
+ if(compressHier){
+ KeyFrameTransCompressed *kf = (KeyFrameTransCompressed*)seq->GetKeyFrameCompressed(l);
+ kf->SetRotation(rot);
+ kf->SetTranslation(trans);
+ kf->SetTime(fbuf[7]); // absolute time here
+ }else{
+ KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l);
+ kf->rotation = rot;
+ kf->translation = trans;
+ kf->deltaTime = fbuf[7]; // absolute time here
+ }
+ }else{
+ RwStreamRead(stream, buf, 0x14);
+ CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
+ rot.Invert();
+
+ if(compressHier){
+ KeyFrameCompressed *kf = (KeyFrameCompressed*)seq->GetKeyFrameCompressed(l);
+ kf->SetRotation(rot);
+ kf->SetTime(fbuf[4]); // absolute time here
+ }else{
+ KeyFrame *kf = (KeyFrame*)seq->GetKeyFrame(l);
+ kf->rotation = rot;
+ kf->deltaTime = fbuf[4]; // absolute time here
+ }
+ }
+ }
+ }
+
+ if(!compressHier){
+ hier->RemoveQuaternionFlips();
+ hier->CalcTotalTime();
+ }
+ }
+ if(animIndex > ms_numAnimations)
+ ms_numAnimations = animIndex;
+}
+
+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].Shutdown();
+ ms_aAnimBlocks[ms_numAnimBlocks].isLoaded = false;
+}
diff --git a/src/animation/AnimManager.h b/src/animation/AnimManager.h
new file mode 100644
index 0000000..213326b
--- /dev/null
+++ b/src/animation/AnimManager.h
@@ -0,0 +1,145 @@
+#pragma once
+
+#include "AnimBlendHierarchy.h"
+#include "AnimationId.h"
+
+enum AssocGroupId
+{
+ ASSOCGRP_STD,
+ ASSOCGRP_VAN,
+ ASSOCGRP_COACH,
+ ASSOCGRP_BIKE_STANDARD,
+ ASSOCGRP_BIKE_VESPA,
+ ASSOCGRP_BIKE_HARLEY,
+ ASSOCGRP_BIKE_DIRT,
+ ASSOCGRP_UNARMED,
+ ASSOCGRP_SCREWDRIVER,
+ ASSOCGRP_KNIFE,
+ ASSOCGRP_BASEBALLBAT,
+ ASSOCGRP_GOLFCLUB,
+ ASSOCGRP_CHAINSAW,
+ ASSOCGRP_PYTHON,
+ ASSOCGRP_COLT,
+ ASSOCGRP_SHOTGUN,
+ ASSOCGRP_BUDDY,
+ ASSOCGRP_TEC,
+ ASSOCGRP_UZI,
+ ASSOCGRP_RIFLE,
+ ASSOCGRP_M60,
+ ASSOCGRP_SNIPER,
+ ASSOCGRP_THROW,
+ ASSOCGRP_FLAMETHROWER,
+ ASSOCGRP_MEDIC,
+ ASSOCGRP_SUNBATHE,
+ ASSOCGRP_PLAYER_IDLE,
+ ASSOCGRP_RIOT,
+ ASSOCGRP_STRIP,
+ ASSOCGRP_LANCE,
+ ASSOCGRP_PLAYER,
+ ASSOCGRP_PLAYERROCKET,
+ ASSOCGRP_PLAYER1ARMED,
+ ASSOCGRP_PLAYER2ARMED,
+ ASSOCGRP_PLAYERBBBAT,
+ ASSOCGRP_PLAYERCHAINSAW,
+ ASSOCGRP_SHUFFLE,
+ ASSOCGRP_OLD,
+ ASSOCGRP_GANG1,
+ ASSOCGRP_GANG2,
+ ASSOCGRP_FAT,
+ ASSOCGRP_OLDFAT,
+ ASSOCGRP_JOGGER,
+ ASSOCGRP_WOMAN,
+ ASSOCGRP_WOMANSHOP,
+ ASSOCGRP_BUSYWOMAN,
+ ASSOCGRP_SEXYWOMAN,
+ ASSOCGRP_FATWOMAN,
+ ASSOCGRP_OLDWOMAN,
+ ASSOCGRP_JOGWOMAN,
+ ASSOCGRP_PANICCHUNKY,
+ ASSOCGRP_SKATE,
+#ifdef PC_PLAYER_CONTROLS
+ ASSOCGRP_PLAYERBACK,
+ ASSOCGRP_PLAYERLEFT,
+ ASSOCGRP_PLAYERRIGHT,
+ ASSOCGRP_ROCKETBACK,
+ ASSOCGRP_ROCKETLEFT,
+ ASSOCGRP_ROCKETRIGHT,
+ ASSOCGRP_CHAINSAWBACK,
+ ASSOCGRP_CHAINSAWLEFT,
+ ASSOCGRP_CHAINSAWRIGHT,
+#endif
+
+ NUM_ANIM_ASSOC_GROUPS
+};
+
+class CAnimBlendAssociation;
+class CAnimBlendAssocGroup;
+
+#define MAX_ANIMBLOCK_NAME 20
+
+// A block of hierarchies
+struct CAnimBlock
+{
+ char name[MAX_ANIMBLOCK_NAME];
+ bool isLoaded;
+ int16 refCount;
+ int32 firstIndex; // first animtion in ms_aAnimations
+ int32 numAnims;
+};
+
+struct AnimAssocDesc
+{
+ int32 animId;
+ int32 flags;
+};
+
+struct AnimAssocDefinition
+{
+ char const *name;
+ char const *blockName;
+ int32 modelIndex;
+ int32 numAnims;
+ char const **animNames;
+ AnimAssocDesc *animDescs;
+};
+
+class CAnimManager
+{
+ static const AnimAssocDefinition ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS];
+ static CAnimBlock ms_aAnimBlocks[NUMANIMBLOCKS];
+ static CAnimBlendHierarchy ms_aAnimations[NUMANIMATIONS];
+ 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 void RemoveFromUncompressedCache(CAnimBlendHierarchy *hier);
+ static CAnimBlock *GetAnimationBlock(int32 block) { return &ms_aAnimBlocks[block]; }
+ static CAnimBlock *GetAnimationBlock(const char *name);
+ static int32 GetAnimationBlockIndex(const char *name);
+ static int32 RegisterAnimBlock(const char *name);
+ static int32 GetNumRefsToAnimBlock(int32 block);
+ static void AddAnimBlockRef(int32 block);
+ static void RemoveAnimBlockRefWithoutDelete(int32 block);
+ static void RemoveAnimBlockRef(int32 block);
+ static void RemoveAnimBlock(int32 block);
+ 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(RwStream *stream, bool compress, char (*uncompressedAnims)[32] = nil);
+ static void CreateAnimAssocGroups(void);
+ static void RemoveLastAnimFile(void);
+ static CAnimBlendAssocGroup* GetAnimAssocGroups(void) { return ms_aAnimAssocGroups; }
+};
diff --git a/src/animation/AnimationId.h b/src/animation/AnimationId.h
new file mode 100644
index 0000000..0b5d8d8
--- /dev/null
+++ b/src/animation/AnimationId.h
@@ -0,0 +1,287 @@
+#pragma once
+
+enum AnimationId
+{
+ ANIM_STD_WALK,
+ ANIM_STD_RUN,
+ ANIM_STD_RUNFAST,
+ ANIM_STD_IDLE,
+ ANIM_STD_STARTWALK,
+ ANIM_STD_RUNSTOP1,
+ ANIM_STD_RUNSTOP2,
+ ANIM_STD_IDLE_CAM,
+ ANIM_STD_IDLE_HBHB,
+ ANIM_STD_IDLE_TIRED,
+ ANIM_STD_IDLE_BIGGUN,
+ ANIM_STD_CHAT,
+ ANIM_STD_HAILTAXI,
+ ANIM_STD_KO_FRONT,
+ ANIM_STD_KO_LEFT,
+ ANIM_STD_KO_BACK,
+ ANIM_STD_KO_RIGHT,
+ ANIM_STD_KO_SHOT_FACE,
+ ANIM_STD_KO_SHOT_STOMACH,
+ ANIM_STD_KO_SHOT_ARM_L,
+ ANIM_STD_KO_SHOT_ARM_R,
+ ANIM_STD_KO_SHOT_LEG_L,
+ ANIM_STD_KO_SHOT_LEG_R,
+ ANIM_STD_SPINFORWARD_LEFT,
+ ANIM_STD_SPINFORWARD_RIGHT,
+ ANIM_STD_HIGHIMPACT_FRONT,
+ ANIM_STD_HIGHIMPACT_LEFT,
+ ANIM_STD_HIGHIMPACT_BACK,
+ ANIM_STD_HIGHIMPACT_RIGHT,
+ ANIM_STD_HITBYGUN_FRONT,
+ ANIM_STD_HITBYGUN_LEFT,
+ ANIM_STD_HITBYGUN_BACK,
+ ANIM_STD_HITBYGUN_RIGHT,
+ ANIM_STD_HIT_FRONT,
+ ANIM_STD_HIT_LEFT,
+ ANIM_STD_HIT_BACK,
+ ANIM_STD_HIT_RIGHT,
+ ANIM_STD_HIT_FLOOR,
+
+ /* names made up */
+ ANIM_STD_HIT_BODYBLOW,
+ ANIM_STD_HIT_CHEST,
+ ANIM_STD_HIT_HEAD,
+ ANIM_STD_HIT_WALK,
+ /**/
+
+ ANIM_STD_HIT_WALL,
+ ANIM_STD_HIT_FLOOR_FRONT,
+ ANIM_STD_HIT_BEHIND,
+ ANIM_STD_FIGHT_IDLE,
+ ANIM_STD_FIGHT_2IDLE,
+ ANIM_STD_FIGHT_SHUFFLE_F,
+
+ /* names made up */
+ ANIM_STD_FIGHT_BODYBLOW,
+ ANIM_STD_FIGHT_HEAD,
+ ANIM_STD_FIGHT_KICK,
+ ANIM_STD_FIGHT_KNEE,
+ ANIM_STD_FIGHT_LHOOK,
+ ANIM_STD_FIGHT_PUNCH,
+ ANIM_STD_FIGHT_ROUNDHOUSE,
+ ANIM_STD_FIGHT_LONGKICK,
+ /**/
+
+ ANIM_STD_PARTIAL_PUNCH,
+
+ /* names made up */
+ ANIM_STD_FIGHT_JAB,
+ ANIM_STD_FIGHT_ELBOW_L,
+ ANIM_STD_FIGHT_ELBOW_R,
+ ANIM_STD_FIGHT_BKICK_L,
+ ANIM_STD_FIGHT_BKICK_R,
+ /**/
+
+ ANIM_STD_DETONATE,
+ ANIM_STD_PUNCH,
+ ANIM_STD_PARTIALPUNCH,
+ ANIM_STD_KICKGROUND,
+
+ ANIM_STD_THROW_UNDER,
+ ANIM_STD_FIGHT_SHUFFLE_B,
+
+ ANIM_STD_JACKEDCAR_RHS,
+ ANIM_STD_JACKEDCAR_LO_RHS,
+ ANIM_STD_JACKEDCAR_LHS,
+ ANIM_STD_JACKEDCAR_LO_LHS,
+ ANIM_STD_QUICKJACK,
+ ANIM_STD_QUICKJACKED,
+ ANIM_STD_CAR_ALIGN_DOOR_LHS,
+ ANIM_STD_CAR_ALIGNHI_DOOR_LHS,
+ ANIM_STD_CAR_OPEN_DOOR_LHS,
+ ANIM_STD_CARDOOR_LOCKED_LHS,
+ ANIM_STD_CAR_PULL_OUT_PED_LHS,
+ ANIM_STD_CAR_PULL_OUT_PED_LO_LHS,
+ ANIM_STD_CAR_GET_IN_LHS,
+ ANIM_STD_CAR_GET_IN_LO_LHS,
+ ANIM_STD_CAR_CLOSE_DOOR_LHS,
+ ANIM_STD_CAR_CLOSE_DOOR_LO_LHS,
+ ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS,
+ ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS,
+ ANIM_STD_CAR_JUMP_IN_LO_LHS,
+ ANIM_STD_GETOUT_LHS,
+ ANIM_STD_GETOUT_LO_LHS,
+ ANIM_STD_CAR_CLOSE_LHS,
+ ANIM_STD_CAR_ALIGN_DOOR_RHS,
+ ANIM_STD_CAR_ALIGNHI_DOOR_RHS,
+ ANIM_STD_CAR_OPEN_DOOR_RHS,
+ ANIM_STD_CARDOOR_LOCKED_RHS,
+ ANIM_STD_CAR_PULL_OUT_PED_RHS,
+ ANIM_STD_CAR_PULL_OUT_PED_LO_RHS,
+ ANIM_STD_CAR_GET_IN_RHS,
+ ANIM_STD_CAR_GET_IN_LO_RHS,
+ ANIM_STD_CAR_CLOSE_DOOR_RHS,
+ ANIM_STD_CAR_CLOSE_DOOR_LO_RHS,
+ ANIM_STD_CAR_SHUFFLE_RHS,
+ ANIM_STD_CAR_SHUFFLE_LO_RHS,
+ ANIM_STD_CAR_SIT,
+ ANIM_STD_CAR_SIT_LO,
+ ANIM_STD_CAR_SIT_P,
+ ANIM_STD_CAR_SIT_P_LO,
+ ANIM_STD_CAR_DRIVE_LEFT,
+ ANIM_STD_CAR_DRIVE_RIGHT,
+ ANIM_STD_CAR_DRIVE_LEFT_LO,
+ ANIM_STD_CAR_DRIVE_RIGHT_LO,
+ ANIM_STD_CAR_DRIVEBY_LEFT,
+ ANIM_STD_CAR_DRIVEBY_RIGHT,
+ ANIM_STD_CAR_DRIVEBY_LEFT_LO,
+ ANIM_STD_CAR_DRIVEBY_RIGHT_LO,
+ ANIM_STD_CAR_LOOKBEHIND,
+ ANIM_STD_BOAT_DRIVE,
+ ANIM_STD_BOAT_DRIVE_LEFT,
+ ANIM_STD_BOAT_DRIVE_RIGHT,
+ ANIM_STD_BOAT_LOOKBEHIND,
+
+ ANIM_STD_BIKE_PICKUP_LHS,
+ ANIM_STD_BIKE_PICKUP_RHS,
+ ANIM_STD_BIKE_PULLUP_LHS,
+ ANIM_STD_BIKE_PULLUP_RHS,
+ ANIM_STD_BIKE_ELBOW_LHS,
+ ANIM_STD_BIKE_ELBOW_RHS,
+ ANIM_STD_BIKE_FALLOFF,
+ ANIM_STD_BIKE_FALLBACK,
+
+ ANIM_STD_GETOUT_RHS,
+ ANIM_STD_GETOUT_LO_RHS,
+ ANIM_STD_CAR_CLOSE_RHS,
+ ANIM_STD_CAR_HOOKERTALK,
+
+ ANIM_STD_TRAIN_GETIN,
+ ANIM_STD_TRAIN_GETOUT,
+
+ ANIM_STD_CRAWLOUT_LHS,
+ ANIM_STD_CRAWLOUT_RHS,
+ ANIM_STD_ROLLOUT_LHS,
+ ANIM_STD_ROLLOUT_RHS,
+
+ ANIM_STD_GET_UP,
+ ANIM_STD_GET_UP_LEFT,
+ ANIM_STD_GET_UP_RIGHT,
+ ANIM_STD_GET_UP_FRONT,
+ ANIM_STD_JUMP_LAUNCH,
+ ANIM_STD_JUMP_GLIDE,
+ ANIM_STD_JUMP_LAND,
+ ANIM_STD_FALL,
+ ANIM_STD_FALL_GLIDE,
+ ANIM_STD_FALL_LAND,
+ ANIM_STD_FALL_COLLAPSE,
+ ANIM_STD_FALL_ONBACK,
+ ANIM_STD_FALL_ONFRONT,
+
+ ANIM_STD_EVADE_STEP,
+ ANIM_STD_EVADE_DIVE,
+ ANIM_STD_XPRESS_SCRATCH,
+ ANIM_STD_ROADCROSS,
+ ANIM_STD_TURN180,
+ ANIM_STD_ARREST,
+ ANIM_STD_DROWN,
+ ANIM_STD_DUCK_DOWN,
+ ANIM_STD_DUCK_LOW,
+
+ ANIM_STD_DUCK_WEAPON,
+
+ ANIM_STD_RBLOCK_SHOOT,
+ ANIM_STD_HANDSUP,
+ ANIM_STD_HANDSCOWER,
+ ANIM_STD_PARTIAL_FUCKU,
+ ANIM_STD_PHONE_IN,
+ ANIM_STD_PHONE_OUT,
+ ANIM_STD_PHONE_TALK,
+
+ ANIM_STD_SEAT_DOWN,
+ ANIM_STD_SEAT_UP,
+ ANIM_STD_SEAT_IDLE,
+ ANIM_STD_SEAT_RVRS,
+ ANIM_STD_ATM,
+ ANIM_STD_ABSEIL,
+
+ ANIM_STD_NUM,
+
+ ANIM_STD_VAN_OPEN_DOOR_REAR_LHS,
+ ANIM_STD_VAN_GET_IN_REAR_LHS,
+ ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS,
+ ANIM_STD_VAN_GET_OUT_REAR_LHS,
+ ANIM_STD_VAN_OPEN_DOOR_REAR_RHS,
+ ANIM_STD_VAN_GET_IN_REAR_RHS,
+ ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS,
+ ANIM_STD_VAN_GET_OUT_REAR_RHS,
+
+ ANIM_STD_COACH_OPEN_LHS,
+ ANIM_STD_COACH_OPEN_RHS,
+ ANIM_STD_COACH_GET_IN_LHS,
+ ANIM_STD_COACH_GET_IN_RHS,
+ ANIM_STD_COACH_GET_OUT_LHS,
+
+ ANIM_BIKE_RIDE,
+ ANIM_BIKE_READY,
+ ANIM_BIKE_LEFT,
+ ANIM_BIKE_RIGHT,
+ ANIM_BIKE_LEANB,
+ ANIM_BIKE_LEANF,
+ ANIM_BIKE_WALKBACK,
+ ANIM_BIKE_JUMPON_LHS,
+ ANIM_BIKE_JUMPON_RHS,
+ ANIM_BIKE_KICK,
+ ANIM_BIKE_HIT,
+ ANIM_BIKE_GETOFF_LHS,
+ ANIM_BIKE_GETOFF_RHS,
+ ANIM_BIKE_GETOFF_BACK,
+ ANIM_BIKE_DRIVEBY_LHS,
+ ANIM_BIKE_DRIVEBY_RHS,
+ ANIM_BIKE_DRIVEBY_FORWARD,
+ ANIM_BIKE_RIDE_P,
+
+ ANIM_ATTACK_1,
+ ANIM_ATTACK_2,
+ ANIM_ATTACK_EXTRA1,
+ ANIM_ATTACK_EXTRA2,
+ ANIM_ATTACK_3,
+
+ // our synonyms... because originals are hard to understand
+ ANIM_WEAPON_FIRE = ANIM_ATTACK_1,
+ ANIM_WEAPON_CROUCHFIRE,
+ ANIM_WEAPON_FIRE_2ND = ANIM_WEAPON_CROUCHFIRE,
+ ANIM_WEAPON_RELOAD,
+ ANIM_WEAPON_CROUCHRELOAD,
+ ANIM_WEAPON_FIRE_3RD,
+ ANIM_THROWABLE_THROW = ANIM_ATTACK_1,
+ ANIM_THROWABLE_THROWU,
+ ANIM_THROWABLE_START_THROW,
+ ANIM_MELEE_ATTACK = ANIM_ATTACK_1,
+ ANIM_MELEE_ATTACK_2ND,
+ ANIM_MELEE_ATTACK_START,
+ ANIM_MELEE_IDLE_FIGHTMODE,
+ ANIM_MELEE_ATTACK_FINISH,
+
+ ANIM_SUNBATHE_IDLE,
+ ANIM_SUNBATHE_DOWN,
+ ANIM_SUNBATHE_UP,
+ ANIM_SUNBATHE_ESCAPE,
+
+ ANIM_MEDIC_CPR,
+
+ ANIM_PLAYER_IDLE1,
+ ANIM_PLAYER_IDLE2,
+ ANIM_PLAYER_IDLE3,
+ ANIM_PLAYER_IDLE4,
+
+ ANIM_RIOT_ANGRY,
+ ANIM_RIOT_ANGRY_B,
+ ANIM_RIOT_CHANT,
+ ANIM_RIOT_PUNCHES,
+ ANIM_RIOT_SHOUT,
+ ANIM_RIOT_CHALLENGE,
+ ANIM_RIOT_FUCKYOU,
+
+ ANIM_STRIP_A,
+ ANIM_STRIP_B,
+ ANIM_STRIP_C,
+ ANIM_STRIP_D,
+ ANIM_STRIP_E,
+ ANIM_STRIP_F,
+ ANIM_STRIP_G,
+}; \ No newline at end of file
diff --git a/src/animation/Bones.cpp b/src/animation/Bones.cpp
new file mode 100644
index 0000000..87f3b6e
--- /dev/null
+++ b/src/animation/Bones.cpp
@@ -0,0 +1,59 @@
+#include "common.h"
+#include "PedModelInfo.h"
+#include "Bones.h"
+
+int
+ConvertPedNode2BoneTag(int node)
+{
+ switch(node){
+ case PED_MID: return BONE_spine1;
+ case PED_HEAD: return BONE_head;
+ case PED_UPPERARML: return BONE_l_upperarm;
+ case PED_UPPERARMR: return BONE_r_upperarm;
+ case PED_HANDL: return BONE_l_hand;
+ case PED_HANDR: return BONE_r_hand;
+ case PED_UPPERLEGL: return BONE_l_thigh;
+ case PED_UPPERLEGR: return BONE_r_thigh;
+ case PED_FOOTL: return BONE_l_foot;
+ case PED_FOOTR: return BONE_r_foot;
+ case PED_LOWERLEGR: return BONE_r_calf;
+ case PED_LOWERLEGL: return BONE_l_calf;
+ case PED_FOREARML: return BONE_l_forearm;
+ case PED_FOREARMR: return BONE_r_forearm;
+ case PED_CLAVICLEL: return BONE_l_clavicle;
+ case PED_CLAVICLER: return BONE_r_clavicle;
+ case PED_NECK: return BONE_neck;
+ }
+ assert(0 && "this node has no bone");
+ return -1;
+}
+
+const char*
+ConvertBoneTag2BoneName(int tag)
+{
+ switch(tag){
+ case BONE_root: return "Root";
+ case BONE_pelvis: return "Pelvis";
+ case BONE_spine: return "Spine";
+ case BONE_spine1: return "Spine1";
+ case BONE_neck: return "Neck";
+ case BONE_head: return "Head";
+ case BONE_r_clavicle: return "Bip01 R Clavicle";
+ case BONE_r_upperarm: return "R UpperArm";
+ case BONE_r_forearm: return "R Forearm";
+ case BONE_r_hand: return "R Hand";
+ case BONE_r_finger: return "R Fingers";
+ case BONE_l_clavicle: return "Bip01 L Clavicle";
+ case BONE_l_upperarm: return "L UpperArm";
+ case BONE_l_forearm: return "L Forearm";
+ case BONE_l_hand: return "L Hand";
+ case BONE_l_finger: return "L Fingers";
+ case BONE_l_thigh: return "L Thigh";
+ case BONE_l_calf: return "L Calf";
+ case BONE_l_foot: return "L Foot";
+ case BONE_r_thigh: return "R Thigh";
+ case BONE_r_calf: return "R Calf";
+ case BONE_r_foot: return "R Foot";
+ }
+ return nil;
+}
diff --git a/src/animation/Bones.h b/src/animation/Bones.h
new file mode 100644
index 0000000..e133fd7
--- /dev/null
+++ b/src/animation/Bones.h
@@ -0,0 +1,30 @@
+#pragma once
+
+enum BoneTag
+{
+ BONE_root = 0,
+ BONE_pelvis = 1,
+ BONE_spine = 2,
+ BONE_spine1 = 3,
+ BONE_neck = 4,
+ BONE_head = 5,
+ BONE_l_clavicle = 31,
+ BONE_l_upperarm = 32,
+ BONE_l_forearm = 33,
+ BONE_l_hand = 34,
+ BONE_l_finger = 35,
+ BONE_r_clavicle = 21,
+ BONE_r_upperarm = 22,
+ BONE_r_forearm = 23,
+ BONE_r_hand = 24,
+ BONE_r_finger = 25,
+ BONE_l_thigh = 41,
+ BONE_l_calf = 42,
+ BONE_l_foot = 43,
+ BONE_r_thigh = 51,
+ BONE_r_calf = 52,
+ BONE_r_foot = 53,
+};
+
+int ConvertPedNode2BoneTag(int node);
+const char *ConvertBoneTag2BoneName(int tag);
diff --git a/src/animation/CutsceneMgr.cpp b/src/animation/CutsceneMgr.cpp
new file mode 100644
index 0000000..633618b
--- /dev/null
+++ b/src/animation/CutsceneMgr.cpp
@@ -0,0 +1,683 @@
+#include "common.h"
+
+#include "General.h"
+#include "CutsceneMgr.h"
+#include "Directory.h"
+#include "Camera.h"
+#include "Streaming.h"
+#include "FileMgr.h"
+#include "main.h"
+#include "AnimManager.h"
+#include "AnimBlendAssociation.h"
+#include "AnimBlendAssocGroup.h"
+#include "AnimBlendClumpData.h"
+#include "Pad.h"
+#include "DMAudio.h"
+#include "World.h"
+#include "PlayerPed.h"
+#include "Wanted.h"
+#include "RpAnimBlend.h"
+#include "ModelIndices.h"
+#include "TempColModels.h"
+#include "ColStore.h"
+#include "Radar.h"
+#include "Pools.h"
+
+const struct {
+ const char *szTrackName;
+ int iTrackId;
+} musicNameIdAssoc[] = {
+ { "ASS_1", STREAMED_SOUND_CUTSCENE_ASS_1 },
+ { "ASS_2", STREAMED_SOUND_CUTSCENE_ASS_2 },
+ { "BANK_1", STREAMED_SOUND_CUTSCENE_BANK_1 },
+ { "BANK_2A", STREAMED_SOUND_CUTSCENE_BANK_2A },
+ { "BANK_2B", STREAMED_SOUND_CUTSCENE_BANK_2B },
+ { "BANK_3A", STREAMED_SOUND_CUTSCENE_BANK_3A },
+ { "BANK_3B", STREAMED_SOUND_CUTSCENE_BANK_3B },
+ { "BANK_4", STREAMED_SOUND_CUTSCENE_BANK_4 },
+ { "BIKE_1", STREAMED_SOUND_CUTSCENE_BIKE_1 },
+ { "BIKE_2", STREAMED_SOUND_CUTSCENE_BIKE_2 },
+ { "BIKE_3", STREAMED_SOUND_CUTSCENE_BIKE_3 },
+ { "BUD_1", STREAMED_SOUND_CUTSCENE_BUD_1 },
+ { "BUD_2", STREAMED_SOUND_CUTSCENE_BUD_2 },
+ { "BUD_3", STREAMED_SOUND_CUTSCENE_BUD_3 },
+ { "CAP_1", STREAMED_SOUND_CUTSCENE_CAP_1 },
+ { "CAR_1", STREAMED_SOUND_CUTSCENE_CAR_1 },
+ { "CNT_1A", STREAMED_SOUND_CUTSCENE_CNT_1A },
+ { "CNT_1B", STREAMED_SOUND_CUTSCENE_CNT_1B },
+ { "CNT_2", STREAMED_SOUND_CUTSCENE_CNT_2 },
+ { "COK_1", STREAMED_SOUND_CUTSCENE_COK_1 },
+ { "COK_2A", STREAMED_SOUND_CUTSCENE_COK_2A },
+ { "COK_2B", STREAMED_SOUND_CUTSCENE_COK_2B },
+ { "COK_3", STREAMED_SOUND_CUTSCENE_COK_3 },
+ { "COK_4A", STREAMED_SOUND_CUTSCENE_COK_4A },
+ { "COK_4A2", STREAMED_SOUND_CUTSCENE_COK_4A2 },
+ { "COK_4B", STREAMED_SOUND_CUTSCENE_COK_4B },
+ { "COL_1", STREAMED_SOUND_CUTSCENE_COL_1 },
+ { "COL_2", STREAMED_SOUND_CUTSCENE_COL_2 },
+ { "COL_3A", STREAMED_SOUND_CUTSCENE_COL_3A },
+ { "COL_4A", STREAMED_SOUND_CUTSCENE_COL_4A },
+ { "COL_5A", STREAMED_SOUND_CUTSCENE_COL_5A },
+ { "COL_5B", STREAMED_SOUND_CUTSCENE_COL_5B },
+ { "CUB_1", STREAMED_SOUND_CUTSCENE_CUB_1 },
+ { "CUB_2", STREAMED_SOUND_CUTSCENE_CUB_2 },
+ { "CUB_3", STREAMED_SOUND_CUTSCENE_CUB_3 },
+ { "CUB_4", STREAMED_SOUND_CUTSCENE_CUB_4 },
+ { "DRUG_1", STREAMED_SOUND_CUTSCENE_DRUG_1 },
+ { "FIN", STREAMED_SOUND_CUTSCENE_FIN },
+ { "FIN_2", STREAMED_SOUND_CUTSCENE_FIN2 },
+ { "FINALE", STREAMED_SOUND_CUTSCENE_FINALE },
+ { "HAT_1", STREAMED_SOUND_CUTSCENE_HAT_1 },
+ { "HAT_2", STREAMED_SOUND_CUTSCENE_HAT_2 },
+ { "HAT_3", STREAMED_SOUND_CUTSCENE_HAT_3 },
+ { "ICE_1", STREAMED_SOUND_CUTSCENE_ICE_1 },
+ { "INT_A", STREAMED_SOUND_CUTSCENE_INT_A },
+ { "INT_B", STREAMED_SOUND_CUTSCENE_INT_B },
+ { "INT_D", STREAMED_SOUND_CUTSCENE_INT_D },
+ { "INT_M", STREAMED_SOUND_CUTSCENE_INT_M },
+ { "LAW_1A", STREAMED_SOUND_CUTSCENE_LAW_1A },
+ { "LAW_1B", STREAMED_SOUND_CUTSCENE_LAW_1B },
+ { "LAW_2A", STREAMED_SOUND_CUTSCENE_LAW_2A },
+ { "LAW_2B", STREAMED_SOUND_CUTSCENE_LAW_2B },
+ { "LAW_2C", STREAMED_SOUND_CUTSCENE_LAW_2C },
+ { "LAW_3", STREAMED_SOUND_CUTSCENE_LAW_3 },
+ { "LAW_4", STREAMED_SOUND_CUTSCENE_LAW_4 },
+ { "PHIL_1", STREAMED_SOUND_CUTSCENE_PHIL_1 },
+ { "PHIL_2", STREAMED_SOUND_CUTSCENE_PHIL_2 },
+ { "PORN_1", STREAMED_SOUND_CUTSCENE_PORN_1 },
+ { "PORN_2", STREAMED_SOUND_CUTSCENE_PORN_2 },
+ { "PORN_3", STREAMED_SOUND_CUTSCENE_PORN_3 },
+ { "PORN_4", STREAMED_SOUND_CUTSCENE_PORN_4 },
+ { "RESC_1A", STREAMED_SOUND_CUTSCENE_RESC_1A },
+ { "ROK_1", STREAMED_SOUND_CUTSCENE_ROK_1 },
+ { "ROK_2", STREAMED_SOUND_CUTSCENE_ROK_2 },
+ { "ROK_3A", STREAMED_SOUND_CUTSCENE_ROK_3A },
+ { "STRIPA", STREAMED_SOUND_CUTSCENE_STRIPA },
+ { "TAX_1", STREAMED_SOUND_CUTSCENE_TAX_1 },
+ { "TEX_1", STREAMED_SOUND_CUTSCENE_TEX_1 },
+ { "TEX_2", STREAMED_SOUND_CUTSCENE_TEX_2 },
+ { "TEX_3", STREAMED_SOUND_CUTSCENE_TEX_3 },
+ { "GSPOT", STREAMED_SOUND_CUTSCENE_GLIGHT },
+ { "FIST", STREAMED_SOUND_CUTSCENE_FIST },
+ { "EL_PH1", STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1 },
+ { "EL_PH2", STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2 },
+ { NULL, 0 }
+};
+
+int
+FindCutsceneAudioTrackId(const char *szCutsceneName)
+{
+ for (int i = 0; musicNameIdAssoc[i].szTrackName; i++) {
+ if (!CGeneral::faststricmp(musicNameIdAssoc[i].szTrackName, szCutsceneName))
+ return musicNameIdAssoc[i].iTrackId;
+ }
+ return -1;
+}
+
+bool CCutsceneMgr::ms_running;
+bool CCutsceneMgr::ms_cutsceneProcessing;
+CDirectory *CCutsceneMgr::ms_pCutsceneDir;
+CCutsceneObject *CCutsceneMgr::ms_pCutsceneObjects[NUMCUTSCENEOBJECTS];
+int32 CCutsceneMgr::ms_numCutsceneObjs;
+bool CCutsceneMgr::ms_loaded;
+bool CCutsceneMgr::ms_animLoaded;
+bool CCutsceneMgr::ms_useLodMultiplier;
+char CCutsceneMgr::ms_cutsceneName[CUTSCENENAMESIZE];
+CAnimBlendAssocGroup CCutsceneMgr::ms_cutsceneAssociations;
+CVector CCutsceneMgr::ms_cutsceneOffset;
+float CCutsceneMgr::ms_cutsceneTimer;
+bool CCutsceneMgr::ms_wasCutsceneSkipped;
+uint32 CCutsceneMgr::ms_cutsceneLoadStatus;
+bool CCutsceneMgr::ms_useCutsceneShadows = true;
+
+bool bCamLoaded;
+bool bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver; // pls don't shrink the name :P
+int32 NumberOfSavedWeapons;
+eWeaponType SavedWeaponIDs[TOTAL_WEAPON_SLOTS];
+int32 SavedWeaponAmmo[TOTAL_WEAPON_SLOTS];
+char uncompressedAnims[8][32];
+uint32 numUncompressedAnims;
+
+
+RpAtomic *
+CalculateBoundingSphereRadiusCB(RpAtomic *atomic, void *data)
+{
+ float radius = RpAtomicGetBoundingSphere(atomic)->radius;
+ RwV3d center = RpAtomicGetBoundingSphere(atomic)->center;
+
+ for (RwFrame *frame = RpAtomicGetFrame(atomic); RwFrameGetParent(frame); frame = RwFrameGetParent(frame))
+ RwV3dTransformPoints(&center, &center, 1, RwFrameGetMatrix(frame));
+
+ float size = RwV3dLength(&center) + radius;
+ if (size > *(float *)data)
+ *(float *)data = size;
+ return atomic;
+}
+
+void
+CCutsceneMgr::Initialise(void)
+{
+ ms_numCutsceneObjs = 0;
+ ms_loaded = false;
+ ms_wasCutsceneSkipped = false;
+ ms_running = false;
+ ms_useLodMultiplier = false;
+ ms_animLoaded = false;
+ ms_cutsceneProcessing = false;
+
+ ms_pCutsceneDir = new CDirectory(CUTSCENEDIRSIZE);
+ ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR");
+
+ numUncompressedAnims = 0;
+ uncompressedAnims[0][0] = '\0';
+}
+
+void
+CCutsceneMgr::Shutdown(void)
+{
+ delete ms_pCutsceneDir;
+}
+
+void
+CCutsceneMgr::LoadCutsceneData(const char *szCutsceneName)
+{
+ int file;
+ uint32 size;
+ uint32 offset;
+ CPlayerPed *pPlayerPed;
+
+ ms_cutsceneProcessing = true;
+ ms_wasCutsceneSkipped = false;
+ CTimer::Suspend();
+ if (!bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver)
+ CStreaming::RemoveCurrentZonesModels();
+
+ ms_pCutsceneDir->numEntries = 0;
+ ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR");
+
+ CStreaming::RemoveUnusedModelsInLoadedList();
+ CGame::DrasticTidyUpMemory(true);
+
+ strcpy(ms_cutsceneName, szCutsceneName);
+
+ RwStream *stream;
+ stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "ANIM\\CUTS.IMG");
+ assert(stream);
+
+ // Load animations
+ sprintf(gString, "%s.IFP", szCutsceneName);
+ if (ms_pCutsceneDir->FindItem(gString, offset, size)) {
+ CStreaming::MakeSpaceFor(size << 11);
+ CStreaming::ImGonnaUseStreamingMemory();
+ RwStreamSkip(stream, offset << 11);
+ CAnimManager::LoadAnimFile(stream, true, uncompressedAnims);
+ ms_cutsceneAssociations.CreateAssociations(szCutsceneName);
+ CStreaming::IHaveUsedStreamingMemory();
+ ms_animLoaded = true;
+ } else {
+ ms_animLoaded = false;
+ }
+ RwStreamClose(stream, nil);
+
+ // Load camera data
+ file = CFileMgr::OpenFile("ANIM\\CUTS.IMG", "rb");
+ sprintf(gString, "%s.DAT", szCutsceneName);
+ if (ms_pCutsceneDir->FindItem(gString, offset, size)) {
+ CStreaming::ImGonnaUseStreamingMemory();
+ CFileMgr::Seek(file, offset << 11, SEEK_SET);
+ TheCamera.LoadPathSplines(file);
+ CStreaming::IHaveUsedStreamingMemory();
+ bCamLoaded = true;
+ } else {
+ bCamLoaded = false;
+ }
+
+ CFileMgr::CloseFile(file);
+
+ if (CGeneral::faststricmp(ms_cutsceneName, "finale")) {
+ DMAudio.ChangeMusicMode(MUSICMODE_CUTSCENE);
+ int trackId = FindCutsceneAudioTrackId(szCutsceneName);
+ if (trackId != -1) {
+ printf("Start preload audio %s\n", szCutsceneName);
+ DMAudio.PreloadCutSceneMusic(trackId);
+ printf("End preload audio %s\n", szCutsceneName);
+ }
+ }
+
+ ms_cutsceneTimer = 0.0f;
+ ms_loaded = true;
+ ms_cutsceneOffset = CVector(0.0f, 0.0f, 0.0f);
+
+ pPlayerPed = FindPlayerPed();
+ pPlayerPed->m_pWanted->ClearQdCrimes();
+ pPlayerPed->bIsVisible = false;
+ pPlayerPed->m_fCurrentStamina = pPlayerPed->m_fMaxStamina;
+ CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CUTSCENE);
+ CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(true);
+
+ CTimer::Resume();
+}
+
+void
+CCutsceneMgr::FinishCutscene()
+{
+ ms_wasCutsceneSkipped = true;
+ if (bCamLoaded) {
+ CCutsceneMgr::ms_cutsceneTimer = TheCamera.GetCutSceneFinishTime() * 0.001f;
+ TheCamera.FinishCutscene();
+ }
+
+ FindPlayerPed()->bIsVisible = true;
+ CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false);
+}
+
+void
+CCutsceneMgr::SetupCutsceneToStart(void)
+{
+ if (bCamLoaded) {
+ TheCamera.SetCamCutSceneOffSet(ms_cutsceneOffset);
+ TheCamera.TakeControlWithSpline(JUMP_CUT);
+ TheCamera.SetWideScreenOn();
+ }
+
+ ms_cutsceneOffset.z++;
+
+ for (int i = ms_numCutsceneObjs - 1; i >= 0; i--) {
+ assert(RwObjectGetType(ms_pCutsceneObjects[i]->m_rwObject) == rpCLUMP);
+ if (CAnimBlendAssociation *pAnimBlendAssoc = RpAnimBlendClumpGetFirstAssociation((RpClump*)ms_pCutsceneObjects[i]->m_rwObject)) {
+ assert(pAnimBlendAssoc->hierarchy->sequences[0].HasTranslation());
+ if (ms_pCutsceneObjects[i]->m_pAttachTo != nil) {
+ pAnimBlendAssoc->flags &= (~ASSOC_HAS_TRANSLATION);
+ } else {
+ if (pAnimBlendAssoc->hierarchy->IsCompressed()){
+ KeyFrameTransCompressed *keyFrames = ((KeyFrameTransCompressed*)pAnimBlendAssoc->hierarchy->sequences[0].GetKeyFrameCompressed(0));
+ CVector trans;
+ keyFrames->GetTranslation(&trans);
+ ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + trans);
+ }else{
+ KeyFrameTrans *keyFrames = ((KeyFrameTrans*)pAnimBlendAssoc->hierarchy->sequences[0].GetKeyFrame(0));
+ ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + keyFrames->translation);
+ }
+ }
+ pAnimBlendAssoc->SetRun();
+ } else {
+ ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset);
+ }
+ CWorld::Add(ms_pCutsceneObjects[i]);
+ if (RwObjectGetType(ms_pCutsceneObjects[i]->m_rwObject) == rpCLUMP) {
+ ms_pCutsceneObjects[i]->UpdateRpHAnim();
+ }
+ }
+
+ CTimer::Update();
+ CTimer::Update();
+ ms_running = true;
+ ms_cutsceneTimer = 0.0f;
+}
+
+void
+CCutsceneMgr::SetCutsceneAnim(const char *animName, CObject *pObject)
+{
+ CAnimBlendAssociation *pNewAnim;
+ CAnimBlendClumpData *pAnimBlendClumpData;
+
+ assert(RwObjectGetType(pObject->m_rwObject) == rpCLUMP);
+ debug("Give cutscene anim %s\n", animName);
+ RpAnimBlendClumpRemoveAllAssociations((RpClump*)pObject->m_rwObject);
+
+ pNewAnim = ms_cutsceneAssociations.GetAnimation(animName);
+ if (!pNewAnim) {
+ debug("\n\nHaven't I told you I can't find the fucking animation %s\n\n\n", animName);
+ return;
+ }
+
+ if (pNewAnim->hierarchy->IsCompressed())
+ pNewAnim->hierarchy->keepCompressed = true;
+
+ CStreaming::ImGonnaUseStreamingMemory();
+ pNewAnim = ms_cutsceneAssociations.CopyAnimation(animName);
+ CStreaming::IHaveUsedStreamingMemory();
+
+ pNewAnim->SetCurrentTime(0.0f);
+ pNewAnim->flags |= ASSOC_HAS_TRANSLATION;
+ pNewAnim->flags &= ~ASSOC_RUNNING;
+
+ pAnimBlendClumpData = *RPANIMBLENDCLUMPDATA(pObject->m_rwObject);
+ pAnimBlendClumpData->link.Prepend(&pNewAnim->link);
+
+ if (pNewAnim->hierarchy->keepCompressed)
+ pAnimBlendClumpData->frames->flag |= AnimBlendFrameData::COMPRESSED;
+}
+
+void
+CCutsceneMgr::SetCutsceneAnimToLoop(const char* animName)
+{
+ ms_cutsceneAssociations.GetAnimation(animName)->flags |= ASSOC_REPEAT;
+}
+
+CCutsceneHead *
+CCutsceneMgr::AddCutsceneHead(CObject *pObject, int modelId)
+{
+ return nil;
+}
+
+void UpdateCutsceneObjectBoundingBox(RpClump* clump, int modelId)
+{
+ if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) {
+ CColModel* pColModel = &CTempColModels::ms_colModelCutObj[modelId - MI_CUTOBJ01];
+ float radius = 0.0f;
+ RpClumpForAllAtomics(clump, CalculateBoundingSphereRadiusCB, &radius);
+ pColModel->boundingSphere.radius = radius;
+ pColModel->boundingBox.min = CVector(-radius, -radius, -radius);
+ pColModel->boundingBox.max = CVector(radius, radius, radius);
+ }
+}
+
+CCutsceneObject *
+CCutsceneMgr::CreateCutsceneObject(int modelId)
+{
+ CBaseModelInfo *pModelInfo;
+ CColModel *pColModel;
+ CCutsceneObject *pCutsceneObject;
+
+ CStreaming::ImGonnaUseStreamingMemory();
+ debug("Created cutscene object %s\n", CModelInfo::GetModelInfo(modelId)->GetModelName());
+ if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) {
+ pModelInfo = CModelInfo::GetModelInfo(modelId);
+ pColModel = &CTempColModels::ms_colModelCutObj[modelId - MI_CUTOBJ01];
+ pModelInfo->SetColModel(pColModel);
+ UpdateCutsceneObjectBoundingBox((RpClump*)pModelInfo->GetRwObject(), modelId);
+ } else if (modelId >= MI_SPECIAL01 && modelId <= MI_SPECIAL21) {
+ pModelInfo = CModelInfo::GetModelInfo(modelId);
+ if (pModelInfo->GetColModel() == &CTempColModels::ms_colModelPed1) {
+ CColModel *colModel = new CColModel();
+ colModel->boundingSphere.radius = 2.0f;
+ colModel->boundingSphere.center = CVector(0.0f, 0.0f, 0.0f);
+ pModelInfo->SetColModel(colModel, true);
+ }
+ pColModel = pModelInfo->GetColModel();
+ float radius = 2.0f;
+ pColModel->boundingSphere.radius = radius;
+ pColModel->boundingBox.min = CVector(-radius, -radius, -radius);
+ pColModel->boundingBox.max = CVector(radius, radius, radius);
+ }
+
+ pCutsceneObject = new CCutsceneObject();
+ pCutsceneObject->SetModelIndex(modelId);
+ if (ms_useCutsceneShadows)
+ pCutsceneObject->CreateShadow();
+ ms_pCutsceneObjects[ms_numCutsceneObjs++] = pCutsceneObject;
+ CStreaming::IHaveUsedStreamingMemory();
+ return pCutsceneObject;
+}
+
+void
+CCutsceneMgr::DeleteCutsceneData(void)
+{
+ if (!ms_loaded) return;
+ CTimer::Suspend();
+
+ ms_cutsceneProcessing = false;
+ ms_useLodMultiplier = false;
+ ms_useCutsceneShadows = true;
+
+ for (--ms_numCutsceneObjs; ms_numCutsceneObjs >= 0; ms_numCutsceneObjs--) {
+ CWorld::Remove(ms_pCutsceneObjects[ms_numCutsceneObjs]);
+ ms_pCutsceneObjects[ms_numCutsceneObjs]->DeleteRwObject();
+ delete ms_pCutsceneObjects[ms_numCutsceneObjs];
+ ms_pCutsceneObjects[ms_numCutsceneObjs] = nil;
+ }
+ ms_numCutsceneObjs = 0;
+
+ for (int i = MI_SPECIAL01; i < MI_SPECIAL21; i++) {
+ CBaseModelInfo *minfo = CModelInfo::GetModelInfo(i);
+ CColModel *colModel = minfo->GetColModel();
+ if (colModel != &CTempColModels::ms_colModelPed1) {
+ delete colModel;
+ minfo->SetColModel(&CTempColModels::ms_colModelPed1);
+ }
+ }
+
+ if (ms_animLoaded)
+ CAnimManager::RemoveLastAnimFile();
+
+ ms_animLoaded = false;
+ numUncompressedAnims = 0;
+ uncompressedAnims[0][0] = '\0';
+
+ if (bCamLoaded) {
+ TheCamera.RestoreWithJumpCut();
+ TheCamera.SetWideScreenOff();
+ TheCamera.DeleteCutSceneCamDataMemory();
+ }
+ ms_running = false;
+ ms_loaded = false;
+
+ FindPlayerPed()->bIsVisible = true;
+ CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CUTSCENE);
+ CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false);
+
+ if (CGeneral::faststricmp(ms_cutsceneName, "finale")) {
+ DMAudio.StopCutSceneMusic();
+ DMAudio.ChangeMusicMode(MUSICMODE_GAME);
+ }
+
+ CStreaming::ms_disableStreaming = false;
+ CWorld::bProcessCutsceneOnly = false;
+
+ if(bCamLoaded)
+ CGame::DrasticTidyUpMemory(TheCamera.GetScreenFadeStatus() == FADE_2);
+
+ CPad::GetPad(0)->Clear(false);
+ if (bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver) {
+ CStreaming::LoadInitialPeds();
+ CStreaming::LoadInitialWeapons();
+ CStreaming::LoadInitialVehicles();
+ bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver = false;
+
+ CPlayerPed *pPlayerPed = FindPlayerPed();
+ for (int i = 0; i < NumberOfSavedWeapons; i++) {
+ int32 weaponModelId = CWeaponInfo::GetWeaponInfo(SavedWeaponIDs[i])->m_nModelId;
+ uint8 flags = CStreaming::ms_aInfoForModel[weaponModelId].m_flags;
+ CStreaming::RequestModel(weaponModelId, STREAMFLAGS_DONT_REMOVE);
+ CStreaming::LoadAllRequestedModels(false);
+ if (CWeaponInfo::GetWeaponInfo(SavedWeaponIDs[i])->m_nModel2Id != -1) {
+ CStreaming::RequestModel(CWeaponInfo::GetWeaponInfo(SavedWeaponIDs[i])->m_nModel2Id, 0);
+ CStreaming::LoadAllRequestedModels(false);
+ }
+ if (!(flags & STREAMFLAGS_DONT_REMOVE))
+ CStreaming::SetModelIsDeletable(weaponModelId);
+ pPlayerPed->GiveWeapon(SavedWeaponIDs[i], SavedWeaponAmmo[i], true);
+ }
+ NumberOfSavedWeapons = 0;
+ }
+
+ CTimer::Resume();
+}
+
+void
+CCutsceneMgr::Update(void)
+{
+ enum {
+ CUTSCENE_LOADING_0 = 0,
+ CUTSCENE_LOADING_AUDIO,
+ CUTSCENE_LOADING_2,
+ CUTSCENE_LOADING_3,
+ CUTSCENE_LOADING_4
+ };
+
+ switch (ms_cutsceneLoadStatus) {
+ case CUTSCENE_LOADING_AUDIO:
+ SetupCutsceneToStart();
+ if (CGeneral::faststricmp(ms_cutsceneName, "finale"))
+ DMAudio.PlayPreloadedCutSceneMusic();
+ ms_cutsceneLoadStatus++;
+ break;
+ case CUTSCENE_LOADING_2:
+ case CUTSCENE_LOADING_3:
+ ms_cutsceneLoadStatus++;
+ break;
+ case CUTSCENE_LOADING_4:
+ ms_cutsceneLoadStatus = CUTSCENE_LOADING_0;
+ break;
+ default:
+ break;
+ }
+
+ if (!ms_running) return;
+
+ ms_cutsceneTimer += CTimer::GetTimeStepNonClippedInSeconds();
+
+ for (int i = 0; i < ms_numCutsceneObjs; i++) {
+ int modelId = ms_pCutsceneObjects[i]->GetModelIndex();
+ if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05)
+ UpdateCutsceneObjectBoundingBox(ms_pCutsceneObjects[i]->GetClump(), modelId);
+
+ if (ms_pCutsceneObjects[i]->m_pAttachTo != nil && modelId >= MI_SPECIAL01 && modelId <= MI_SPECIAL21)
+ UpdateCutsceneObjectBoundingBox(ms_pCutsceneObjects[i]->GetClump(), modelId);
+ }
+
+ if (bCamLoaded)
+ if (CGeneral::faststricmp(ms_cutsceneName, "finale") && TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FLYBY && ms_cutsceneLoadStatus == CUTSCENE_LOADING_0) {
+ if (CPad::GetPad(0)->GetCrossJustDown()
+ || (CGame::playingIntro && CPad::GetPad(0)->GetStartJustDown())
+ || CPad::GetPad(0)->GetLeftMouseJustDown()
+ || CPad::GetPad(0)->GetEnterJustDown()
+ || CPad::GetPad(0)->GetCharJustDown(' '))
+ FinishCutscene();
+ }
+}
+
+bool CCutsceneMgr::HasCutsceneFinished(void) { return !bCamLoaded || TheCamera.GetPositionAlongSpline() == 1.0f; }
+
+void
+CCutsceneMgr::LoadAnimationUncompressed(char const* name)
+{
+ strcpy(uncompressedAnims[numUncompressedAnims], name);
+
+ // Because that's how CAnimManager knows the end of array
+ ++numUncompressedAnims;
+ assert(numUncompressedAnims < ARRAY_SIZE(uncompressedAnims));
+ uncompressedAnims[numUncompressedAnims][0] = '\0';
+}
+
+void
+CCutsceneMgr::AttachObjectToParent(CObject *pObject, CEntity *pAttachTo)
+{
+ ((CCutsceneObject*)pObject)->m_pAttachmentObject = nil;
+ ((CCutsceneObject*)pObject)->m_pAttachTo = RpClumpGetFrame(pAttachTo->GetClump());
+
+ debug("Attach %s to %s\n", CModelInfo::GetModelInfo(pObject->GetModelIndex())->GetModelName(), CModelInfo::GetModelInfo(pAttachTo->GetModelIndex())->GetModelName());
+}
+
+void
+CCutsceneMgr::AttachObjectToFrame(CObject *pObject, CEntity *pAttachTo, const char *frame)
+{
+ ((CCutsceneObject*)pObject)->m_pAttachmentObject = nil;
+ ((CCutsceneObject*)pObject)->m_pAttachTo = RpAnimBlendClumpFindFrame(pAttachTo->GetClump(), frame)->frame;
+ debug("Attach %s to component %s of %s\n",
+ CModelInfo::GetModelInfo(pObject->GetModelIndex())->GetModelName(),
+ frame,
+ CModelInfo::GetModelInfo(pAttachTo->GetModelIndex())->GetModelName());
+ if (RwObjectGetType(pObject->m_rwObject) == rpCLUMP) {
+ RpClump *clump = (RpClump*)pObject->m_rwObject;
+ if (IsClumpSkinned(clump))
+ RpAtomicGetBoundingSphere(GetFirstAtomic(clump))->radius *= 1.1f;
+ }
+}
+
+void
+CCutsceneMgr::AttachObjectToBone(CObject *pObject, CObject *pAttachTo, int bone)
+{
+ RpHAnimHierarchy *hanim = GetAnimHierarchyFromSkinClump(pAttachTo->GetClump());
+ RwInt32 id = RpHAnimIDGetIndex(hanim, bone);
+ RwMatrix *matrixArray = RpHAnimHierarchyGetMatrixArray(hanim);
+ ((CCutsceneObject*)pObject)->m_pAttachmentObject = pAttachTo;
+ ((CCutsceneObject*)pObject)->m_pAttachTo = &matrixArray[id];
+ debug("Attach %s to %s\n",
+ CModelInfo::GetModelInfo(pObject->GetModelIndex())->GetModelName(),
+ CModelInfo::GetModelInfo(pAttachTo->GetModelIndex())->GetModelName());
+}
+
+void
+CCutsceneMgr::RemoveEverythingFromTheWorldForTheBiggestFuckoffCutsceneEver()
+{
+ CStreaming::ms_disableStreaming = true;
+ CColStore::RemoveAllCollision();
+ CWorld::bProcessCutsceneOnly = true;
+ ms_cutsceneProcessing = true;
+
+ for (int i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--) {
+ CPed *pPed = CPools::GetPedPool()->GetSlot(i);
+ if (pPed) {
+ if (!pPed->IsPlayer() && pPed->CanBeDeleted()) {
+ CWorld::Remove(pPed);
+ delete pPed;
+ }
+ }
+ }
+
+ for (int i = CPools::GetVehiclePool()->GetSize() - 1; i >= 0; i--) {
+ CVehicle *pVehicle = CPools::GetVehiclePool()->GetSlot(i);
+ if (pVehicle) {
+ if (pVehicle->CanBeDeleted()) {
+ CWorld::Remove(pVehicle);
+ delete pVehicle;
+ }
+ }
+ }
+
+ bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver = true;
+ CStreaming::RemoveCurrentZonesModels();
+ CStreaming::SetModelIsDeletable(MI_MALE01);
+ CStreaming::SetModelTxdIsDeletable(MI_MALE01);
+ CStreaming::SetModelIsDeletable(MI_TAXI_D);
+ CStreaming::SetModelTxdIsDeletable(MI_TAXI_D);
+ CStreaming::SetModelIsDeletable(MI_NIGHTSTICK);
+ CStreaming::SetModelTxdIsDeletable(MI_NIGHTSTICK);
+ CStreaming::SetModelIsDeletable(MI_MISSILE);
+ CStreaming::SetModelTxdIsDeletable(MI_MISSILE);
+ CStreaming::SetModelIsDeletable(MI_POLICE);
+ CStreaming::SetModelTxdIsDeletable(MI_POLICE);
+
+ while (CStreaming::RemoveLoadedVehicle()) ;
+
+ CRadar::RemoveRadarSections();
+
+ for (int i = CPools::GetDummyPool()->GetSize() - 1; i >= 0; i--) {
+ CDummy* pDummy = CPools::GetDummyPool()->GetSlot(i);
+ if (pDummy)
+ pDummy->DeleteRwObject();
+ }
+
+ for (int i = CPools::GetObjectPool()->GetSize() - 1; i >= 0; i--) {
+ CObject* pObject = CPools::GetObjectPool()->GetSlot(i);
+ if (pObject)
+ pObject->DeleteRwObject();
+ }
+
+ for (int i = CPools::GetBuildingPool()->GetSize() - 1; i >= 0; i--) {
+ CBuilding* pBuilding = CPools::GetBuildingPool()->GetSlot(i);
+ if (pBuilding && pBuilding->m_rwObject != nil && pBuilding->bIsBIGBuilding && pBuilding->bStreamBIGBuilding) {
+ if (pBuilding->bIsBIGBuilding)
+ CStreaming::RequestModel(pBuilding->GetModelIndex(), 0);
+ if (!pBuilding->bImBeingRendered)
+ pBuilding->DeleteRwObject();
+ }
+ }
+
+ CPlayerPed *pPlayerPed = FindPlayerPed();
+ pPlayerPed->RemoveWeaponAnims(0, -1000.0f);
+ NumberOfSavedWeapons = 0;
+
+ for (int i = 0; i < TOTAL_WEAPON_SLOTS; i++) {
+ if (pPlayerPed->m_weapons[i].m_eWeaponType != WEAPONTYPE_UNARMED) {
+ SavedWeaponIDs[NumberOfSavedWeapons] = pPlayerPed->m_weapons[i].m_eWeaponType;
+ SavedWeaponAmmo[NumberOfSavedWeapons] = pPlayerPed->m_weapons[i].m_nAmmoTotal;
+ NumberOfSavedWeapons++;
+ }
+ }
+
+ pPlayerPed->ClearWeapons();
+ CGame::DrasticTidyUpMemory(true);
+} \ No newline at end of file
diff --git a/src/animation/CutsceneMgr.h b/src/animation/CutsceneMgr.h
new file mode 100644
index 0000000..51ef6c0
--- /dev/null
+++ b/src/animation/CutsceneMgr.h
@@ -0,0 +1,61 @@
+#pragma once
+#include "CutsceneObject.h"
+
+#define CUTSCENENAMESIZE 8
+
+class CDirectory;
+class CAnimBlendAssocGroup;
+class CCutsceneHead;
+
+class CCutsceneMgr
+{
+ static bool ms_running;
+ static CCutsceneObject *ms_pCutsceneObjects[NUMCUTSCENEOBJECTS];
+
+ static int32 ms_numCutsceneObjs;
+ static bool ms_loaded;
+ static bool ms_animLoaded;
+ static bool ms_useLodMultiplier;
+
+ static char ms_cutsceneName[CUTSCENENAMESIZE];
+ static CAnimBlendAssocGroup ms_cutsceneAssociations;
+ static CVector ms_cutsceneOffset;
+ static float ms_cutsceneTimer;
+ static bool ms_wasCutsceneSkipped;
+ static bool ms_cutsceneProcessing;
+ static bool ms_useCutsceneShadows;
+public:
+ static CDirectory *ms_pCutsceneDir;
+ static uint32 ms_cutsceneLoadStatus;
+
+ static void StartCutsceneProcessing() { ms_cutsceneProcessing = true; }
+ static bool IsRunning(void) { return ms_running; }
+ static bool HasLoaded(void) { return ms_loaded; }
+ static bool IsCutsceneProcessing(void) { return ms_cutsceneProcessing; }
+ static bool WasCutsceneSkipped(void) { return ms_wasCutsceneSkipped; }
+ static bool UseLodMultiplier(void) { return ms_useLodMultiplier; }
+ static CCutsceneObject* GetCutsceneObject(int id) { return ms_pCutsceneObjects[id]; }
+ static int GetCutsceneTimeInMilleseconds(void) { return 1000.0f * ms_cutsceneTimer; }
+ static char *GetCutsceneName(void) { return ms_cutsceneName; }
+ static void SetCutsceneOffset(const CVector& vec) { ms_cutsceneOffset = vec; }
+ static bool HasCutsceneFinished(void);
+
+ static void Initialise(void);
+ static void Shutdown(void);
+ static void LoadCutsceneData(const char *szCutsceneName);
+ static void FinishCutscene(void);
+ static void SetupCutsceneToStart(void);
+ static void SetCutsceneAnim(const char *animName, CObject *pObject);
+ static void SetCutsceneAnimToLoop(const char *animName);
+ static CCutsceneHead *AddCutsceneHead(CObject *pObject, int modelId);
+ static CCutsceneObject *CreateCutsceneObject(int modelId);
+ static void DeleteCutsceneData(void);
+ static void LoadAnimationUncompressed(char const*);
+ static void Update(void);
+
+ static void AttachObjectToParent(CObject *pObject, CEntity *pAttachTo);
+ static void AttachObjectToFrame(CObject *pObject, CEntity *pAttachTo, const char *frame);
+ static void AttachObjectToBone(CObject *pObject, CObject *pAttachTo, int frame);
+ static void RemoveEverythingFromTheWorldForTheBiggestFuckoffCutsceneEver();
+ static void DisableCutsceneShadows() { ms_useCutsceneShadows = false; }
+};
diff --git a/src/animation/FrameUpdate.cpp b/src/animation/FrameUpdate.cpp
new file mode 100644
index 0000000..d3094bb
--- /dev/null
+++ b/src/animation/FrameUpdate.cpp
@@ -0,0 +1,668 @@
+#include "common.h"
+
+#include "NodeName.h"
+#include "VisibilityPlugins.h"
+#include "AnimBlendClumpData.h"
+#include "AnimBlendAssociation.h"
+#include "RpAnimBlend.h"
+
+CAnimBlendClumpData *gpAnimBlendClump;
+
+// PS2 names without "NonSkinned"
+void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackWithVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg);
+
+void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackWithVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackWith3dVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg);
+
+void FrameUpdateCallBackNonSkinnedCompressed(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackSkinnedCompressed(AnimBlendFrameData *frame, void *arg);
+
+void
+FrameUpdateCallBackNonSkinned(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->velocity2d){
+ if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D)
+ FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(frame, arg);
+ else
+ FrameUpdateCallBackWithVelocityExtractionNonSkinned(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;
+#ifdef FIX_BUGS
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+#endif
+ rot += q;
+ }
+ ++*node;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+ RwMatrixSetIdentity(mat);
+ rot.Normalise();
+ 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
+FrameUpdateCallBackWithVelocityExtractionNonSkinned(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);
+#ifdef FIX_BUGS
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+#endif
+ 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);
+ rot.Normalise();
+ rot.Get(mat);
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ gpAnimBlendClump->velocity2d->x = transx - curx;
+ gpAnimBlendClump->velocity2d->y = transy - cury;
+ if(looped){
+ gpAnimBlendClump->velocity2d->x += endx;
+ gpAnimBlendClump->velocity2d->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
+FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(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);
+#ifdef FIX_BUGS
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+#endif
+ 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);
+ rot.Normalise();
+ rot.Get(mat);
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ *gpAnimBlendClump->velocity3d = trans - cur;
+ if(looped)
+ *gpAnimBlendClump->velocity3d += 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);
+}
+
+void
+FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg)
+{
+ CVector vec, pos(0.0f, 0.0f, 0.0f);
+ float transBlendAmount = 0.0f;
+ CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f);
+ float totalBlendAmount = 0.0f;
+ RpHAnimStdInterpFrame *xform = frame->hanimFrame;
+ CAnimBlendNode **node;
+ AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg;
+
+ if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION &&
+ gpAnimBlendClump->velocity2d){
+ if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D)
+ FrameUpdateCallBackWith3dVelocityExtractionSkinned(frame, arg);
+ else
+ FrameUpdateCallBackWithVelocityExtractionSkinned(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;
+ transBlendAmount += (*node)->association->blendAmount;
+ }
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+ rot += q;
+ }
+ ++*node;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+ rot.Normalise();
+ xform->q.imag.x = rot.x;
+ xform->q.imag.y = rot.y;
+ xform->q.imag.z = rot.z;
+ xform->q.real = rot.w;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ xform->t.x = transBlendAmount*pos.x;
+ xform->t.y = transBlendAmount*pos.y;
+ xform->t.z = transBlendAmount*pos.z;
+ xform->t.x += (1.0f-transBlendAmount)*frame->resetPos.x;
+ xform->t.y += (1.0f-transBlendAmount)*frame->resetPos.y;
+ xform->t.z += (1.0f-transBlendAmount)*frame->resetPos.z;
+ }
+}
+
+void
+FrameUpdateCallBackWithVelocityExtractionSkinned(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;
+ RpHAnimStdInterpFrame *xform = frame->hanimFrame;
+ 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);
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+ 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){
+ rot.Normalise();
+ xform->q.imag.x = rot.x;
+ xform->q.imag.y = rot.y;
+ xform->q.imag.z = rot.z;
+ xform->q.real = rot.w;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ gpAnimBlendClump->velocity2d->x = transx - curx;
+ gpAnimBlendClump->velocity2d->y = transy - cury;
+ if(looped){
+ gpAnimBlendClump->velocity2d->x += endx;
+ gpAnimBlendClump->velocity2d->y += endy;
+ }
+ xform->t.x = pos.x - transx;
+ xform->t.y = pos.y - transy;
+ xform->t.z = pos.z;
+ if(xform->t.z >= -0.8f) {
+ if(xform->t.z < -0.4f)
+ xform->t.z += (2.5f * xform->t.z + 2.0f) * frame->resetPos.z;
+ else
+ xform->t.z += frame->resetPos.z;
+ }
+ xform->t.x += frame->resetPos.x;
+ xform->t.y += frame->resetPos.y;
+ }
+}
+
+void
+FrameUpdateCallBackWith3dVelocityExtractionSkinned(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;
+ RpHAnimStdInterpFrame *xform = frame->hanimFrame;
+ 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);
+#ifdef FIX_BUGS
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+#endif
+ 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){
+ rot.Normalise();
+ xform->q.imag.x = rot.x;
+ xform->q.imag.y = rot.y;
+ xform->q.imag.z = rot.z;
+ xform->q.real = rot.w;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ *gpAnimBlendClump->velocity3d = trans - cur;
+ if(looped)
+ *gpAnimBlendClump->velocity3d += end;
+ xform->t.x = (pos - trans).x + frame->resetPos.x;
+ xform->t.y = (pos - trans).y + frame->resetPos.y;
+ xform->t.z = (pos - trans).z + frame->resetPos.z;
+ }
+}
+
+void
+FrameUpdateCallBackOffscreen(AnimBlendFrameData *frame, void *arg)
+{
+ if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION && gpAnimBlendClump->velocity2d)
+ FrameUpdateCallBackWithVelocityExtractionSkinned(frame, arg);
+}
+
+
+void
+FrameUpdateCallBackNonSkinnedCompressed(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(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION &&
+ gpAnimBlendClump->velocity2d){
+ 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)->GetCurrentTranslationCompressed(vec, 1.0f-totalBlendAmount);
+ cur += vec;
+ }
+ }
+
+ for(node = updateData->nodes; *node; node++){
+ if((*node)->sequence){
+ bool nodelooped = (*node)->UpdateCompressed(vec, q, 1.0f-totalBlendAmount);
+#ifdef FIX_BUGS
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+#endif
+ rot += q;
+ if((*node)->sequence->HasTranslation()){
+ pos += vec;
+ if((*node)->association->HasTranslation()){
+ trans += vec;
+ looped |= nodelooped;
+ if(nodelooped){
+ (*node)->GetEndTranslationCompressed(vec, 1.0f-totalBlendAmount);
+ end += vec;
+ }
+ }
+ }
+ }
+ ++*node;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+ RwMatrixSetIdentity(mat);
+ rot.Normalise();
+ rot.Get(mat);
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ *gpAnimBlendClump->velocity3d = trans - cur;
+ if(looped)
+ *gpAnimBlendClump->velocity3d += 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);
+ }else{
+ 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)->UpdateCompressed(vec, q, 1.0f-totalBlendAmount);
+ if((*node)->sequence->HasTranslation())
+ pos += vec;
+#ifdef FIX_BUGS
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+#endif
+ rot += q;
+ }
+ ++*node;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+ RwMatrixSetIdentity(mat);
+ rot.Normalise();
+ 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
+FrameUpdateCallBackSkinnedCompressed(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;
+ RpHAnimStdInterpFrame *xform = frame->hanimFrame;
+ CAnimBlendNode **node;
+ AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg;
+
+ if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION &&
+ gpAnimBlendClump->velocity2d){
+ 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)->GetCurrentTranslationCompressed(vec, 1.0f-totalBlendAmount);
+ cur += vec;
+ }
+ }
+
+ for(node = updateData->nodes; *node; node++){
+ if((*node)->sequence){
+ bool nodelooped = (*node)->UpdateCompressed(vec, q, 1.0f-totalBlendAmount);
+#ifdef FIX_BUGS
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+#endif
+ rot += q;
+ if((*node)->sequence->HasTranslation()){
+ pos += vec;
+ if((*node)->association->HasTranslation()){
+ trans += vec;
+ looped |= nodelooped;
+ if(nodelooped){
+ (*node)->GetEndTranslationCompressed(vec, 1.0f-totalBlendAmount);
+ end += vec;
+ }
+ }
+ }
+ }
+ ++*node;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+ rot.Normalise();
+ xform->q.imag.x = rot.x;
+ xform->q.imag.y = rot.y;
+ xform->q.imag.z = rot.z;
+ xform->q.real = rot.w;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ *gpAnimBlendClump->velocity3d = trans - cur;
+ if(looped)
+ *gpAnimBlendClump->velocity3d += end;
+ xform->t.x = (pos - trans).x + frame->resetPos.x;
+ xform->t.y = (pos - trans).y + frame->resetPos.y;
+ xform->t.z = (pos - trans).z + frame->resetPos.z;
+ }
+ }else{
+ float transBlendAmount = 0.0f;
+
+ 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)->UpdateCompressed(vec, q, 1.0f-totalBlendAmount);
+ if((*node)->sequence->HasTranslation()){
+ pos += vec;
+ transBlendAmount += (*node)->association->blendAmount;
+ }
+ if(DotProduct(rot, q) < 0.0f)
+ rot -= q;
+ else
+ rot += q;
+ }
+ ++*node;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+ rot.Normalise();
+ xform->q.imag.x = rot.x;
+ xform->q.imag.y = rot.y;
+ xform->q.imag.z = rot.z;
+ xform->q.real = rot.w;
+ }
+
+ if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+ xform->t.x = transBlendAmount*pos.x;
+ xform->t.y = transBlendAmount*pos.y;
+ xform->t.z = transBlendAmount*pos.z;
+ xform->t.x += (1.0f-transBlendAmount)*frame->resetPos.x;
+ xform->t.y += (1.0f-transBlendAmount)*frame->resetPos.y;
+ xform->t.z += (1.0f-transBlendAmount)*frame->resetPos.z;
+ }
+ }
+}
diff --git a/src/animation/RpAnimBlend.cpp b/src/animation/RpAnimBlend.cpp
new file mode 100644
index 0000000..9a11d8b
--- /dev/null
+++ b/src/animation/RpAnimBlend.cpp
@@ -0,0 +1,517 @@
+#include "common.h"
+
+#include "RwHelper.h"
+#include "General.h"
+#include "NodeName.h"
+#include "VisibilityPlugins.h"
+#include "Bones.h"
+#include "AnimBlendClumpData.h"
+#include "AnimBlendHierarchy.h"
+#include "AnimBlendAssociation.h"
+#include "AnimManager.h"
+#include "RpAnimBlend.h"
+#include "PedModelInfo.h"
+
+RwInt32 ClumpOffset;
+
+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)
+ return 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;
+}
+
+// FrameInitCallBack on PS2
+void
+FrameInitCBnonskin(AnimBlendFrameData *frameData, void*)
+{
+ frameData->flag = 0;
+ frameData->resetPos = *RwMatrixGetPos(RwFrameGetMatrix(frameData->frame));
+}
+
+void
+FrameInitCBskin(AnimBlendFrameData *frameData, void*)
+{
+ frameData->flag = 0;
+}
+
+void
+RpAnimBlendClumpInitSkinned(RpClump *clump)
+{
+ int i;
+ RwV3d boneTab[64];
+ CAnimBlendClumpData *clumpData;
+ RpAtomic *atomic;
+ RpSkin *skin;
+ RpHAnimHierarchy *hier;
+ int numBones;
+
+ RpAnimBlendAllocateData(clump);
+ clumpData = *RPANIMBLENDCLUMPDATA(clump);
+ atomic = GetFirstAtomic(clump);
+ assert(atomic);
+ skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic));
+ assert(skin);
+ numBones = RpSkinGetNumBones(skin);
+ clumpData->SetNumberOfBones(numBones);
+ hier = GetAnimHierarchyFromSkinClump(clump);
+ assert(hier);
+ memset(boneTab, 0, sizeof(boneTab));
+ SkinGetBonePositionsToTable(clump, boneTab);
+
+ AnimBlendFrameData *frames = clumpData->frames;
+ for(i = 0; i < numBones; i++){
+ frames[i].nodeID = HIERNODEID(hier, i);
+ frames[i].resetPos = boneTab[i];
+#ifdef LIBRW
+ frames[i].hanimFrame = (RpHAnimStdInterpFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i);
+#else
+ frames[i].hanimFrame = (RpHAnimStdInterpFrame*)rtANIMGETINTERPFRAME(hier->currentAnim, i);
+#endif
+ }
+ clumpData->ForAllFrames(FrameInitCBskin, nil);
+ clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION;
+}
+
+void
+RpAnimBlendClumpInitNotSkinned(RpClump *clump)
+{
+ 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(FrameInitCBnonskin, nil);
+ clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION;
+}
+
+void
+RpAnimBlendClumpInit(RpClump *clump)
+{
+ if(IsClumpSkinned(clump))
+ RpAnimBlendClumpInitSkinned(clump);
+ else
+ RpAnimBlendClumpInitNotSkinned(clump);
+}
+
+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.0f;
+ float secondBlend = 0.0f;
+ 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.0f;
+ 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(!RpAnimBlendClumpIsInitialized(clump))
+ return nil;
+ if(clumpData->link.next)
+ return CAnimBlendAssociation::FromLink(clumpData->link.next);
+ return nil;
+}
+
+// FillFrameArrayCallBack on PS2
+void
+FillFrameArrayCBnonskin(AnimBlendFrameData *frame, void *arg)
+{
+ AnimBlendFrameData **frames = (AnimBlendFrameData**)arg;
+ frames[CVisibilityPlugins::GetFrameHierarchyId(frame->frame)] = frame;
+}
+
+void
+RpAnimBlendClumpFillFrameArraySkin(RpClump *clump, AnimBlendFrameData **frames)
+{
+ int i;
+ CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump);
+ RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump);
+ for(i = PED_MID; i < PED_NODE_MAX; i++)
+ frames[i] = &clumpData->frames[RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(i))];
+}
+
+void
+RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames)
+{
+ if(IsClumpSkinned(clump))
+ RpAnimBlendClumpFillFrameArraySkin(clump, frames);
+ else
+ (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCBnonskin, frames);
+}
+
+AnimBlendFrameData *pFrameDataFound;
+
+void
+FrameFindByNameCBnonskin(AnimBlendFrameData *frame, void *arg)
+{
+ char *nodename = GetFrameNodeName(frame->frame);
+ if(!CGeneral::faststricmp(nodename, (char*)arg))
+ pFrameDataFound = frame;
+}
+
+void
+FrameFindByNameCBskin(AnimBlendFrameData *frame, void *arg)
+{
+ const char *name = ConvertBoneTag2BoneName(frame->nodeID);
+ if(name && CGeneral::faststricmp(name, (char*)arg) == 0)
+ pFrameDataFound = frame;
+}
+
+void
+FrameFindByBoneCB(AnimBlendFrameData *frame, void *arg)
+{
+ if(frame->nodeID == (int32)(uintptr)arg)
+ pFrameDataFound = frame;
+}
+
+AnimBlendFrameData*
+RpAnimBlendClumpFindFrame(RpClump *clump, const char *name)
+{
+ pFrameDataFound = nil;
+ if(IsClumpSkinned(clump))
+ (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBskin, (void*)name);
+ else
+ (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBnonskin, (void*)name);
+ return pFrameDataFound;
+}
+
+AnimBlendFrameData*
+RpAnimBlendClumpFindBone(RpClump *clump, uint32 boneTag)
+{
+ pFrameDataFound = nil;
+ (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByBoneCB, (void*)boneTag);
+ return pFrameDataFound;
+}
+
+void
+RpAnimBlendNodeUpdateKeyframes(AnimBlendFrameData *frames, AnimBlendFrameUpdateData *updateData, int32 numNodes)
+{
+ CAnimBlendNode **node;
+ int i;
+
+ for(node = updateData->nodes; *node; node++){
+ CAnimBlendAssociation *a = (*node)->association;
+ for(i = 0; i < numNodes; i++)
+ if((frames[i].flag & AnimBlendFrameData::VELOCITY_EXTRACTION) == 0 ||
+ gpAnimBlendClump->velocity2d == nil){
+ if((*node)[i].sequence)
+ (*node)[i].FindKeyFrame(a->currentTime - a->timeStep);
+ }
+ }
+}
+
+// TODO:
+// CAnimBlendClumpData::LoadFramesIntoSPR
+// CAnimBlendClumpData::ForAllFramesInSPR
+void
+RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta, bool doRender)
+{
+ int i;
+ CAnimBlendAssociation *assoc;
+ 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;
+ assoc = CAnimBlendAssociation::FromLink(link);
+ if(assoc->UpdateBlend(timeDelta)){
+ if(assoc->hierarchy->sequences){
+ CAnimManager::UncompressAnimation(assoc->hierarchy);
+ if(i < 11)
+ 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;
+ }else
+ debug("anim %s is not loaded\n", assoc->hierarchy->name);
+ }
+ }
+
+ for(link = clumpData->link.next; link; link = link->next){
+ assoc = CAnimBlendAssociation::FromLink(link);
+ assoc->UpdateTimeStep(timeDelta, totalLength == 0.0f ? 1.0f : totalBlend/totalLength);
+ }
+
+ updateData.nodes[i] = nil;
+
+#ifdef ANIM_COMPRESSION
+ if(clumpData->frames[0].flag & AnimBlendFrameData::COMPRESSED){
+ if(IsClumpSkinned(clump))
+ clumpData->ForAllFrames(FrameUpdateCallBackSkinnedCompressed, &updateData);
+ else
+ clumpData->ForAllFrames(FrameUpdateCallBackNonSkinnedCompressed, &updateData);
+ }else
+#endif
+ if(doRender){
+ if(clumpData->frames[0].flag & AnimBlendFrameData::UPDATE_KEYFRAMES)
+ RpAnimBlendNodeUpdateKeyframes(clumpData->frames, &updateData, clumpData->numFrames);
+ if(IsClumpSkinned(clump))
+ clumpData->ForAllFrames(FrameUpdateCallBackSkinned, &updateData);
+ else
+ clumpData->ForAllFrames(FrameUpdateCallBackNonSkinned, &updateData);
+ clumpData->frames[0].flag &= ~AnimBlendFrameData::UPDATE_KEYFRAMES;
+ }else{
+ clumpData->ForAllFrames(FrameUpdateCallBackOffscreen, &updateData);
+ clumpData->frames[0].flag |= AnimBlendFrameData::UPDATE_KEYFRAMES;
+ }
+
+ for(link = clumpData->link.next; link; link = link->next){
+ assoc = CAnimBlendAssociation::FromLink(link);
+ assoc->UpdateTime(timeDelta, totalLength == 0.0f ? 1.0f : totalBlend/totalLength);
+ }
+ RwFrameUpdateObjects(RpClumpGetFrame(clump));
+}
diff --git a/src/animation/RpAnimBlend.h b/src/animation/RpAnimBlend.h
new file mode 100644
index 0000000..6e9e0f0
--- /dev/null
+++ b/src/animation/RpAnimBlend.h
@@ -0,0 +1,48 @@
+#pragma once
+
+class CAnimBlendNode;
+class CAnimBlendAssociation;
+class CAnimBlendClumpData;
+struct AnimBlendFrameData;
+
+struct AnimBlendFrameUpdateData
+{
+ int foobar; // TODO: figure out what this actually means
+ 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);
+void RpAnimBlendClumpFillFrameArray(RpClump* clump, AnimBlendFrameData** frames);
+AnimBlendFrameData *RpAnimBlendClumpFindFrame(RpClump *clump, const char *name);
+AnimBlendFrameData *RpAnimBlendClumpFindBone(RpClump *clump, uint32 boneTag);
+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);
+void RpAnimBlendNodeUpdateKeyframes(AnimBlendFrameData *frames, AnimBlendFrameUpdateData *updateData, int32 numNodes);
+void RpAnimBlendClumpUpdateAnimations(RpClump* clump, float timeDelta, bool doRender = true);
+
+
+extern CAnimBlendClumpData *gpAnimBlendClump;
+void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackOffscreen(AnimBlendFrameData *frame, void *arg);
+
+void FrameUpdateCallBackNonSkinnedCompressed(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackSkinnedCompressed(AnimBlendFrameData *frame, void *arg);