code:
#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"

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 },
#if GTA_VERSION <= GTA3_PS2_160
	{ ANIM_STD_HIT_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
#endif
	{ 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_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_KICKGROUND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_WEAPON_BAT_H, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_WEAPON_BAT_V, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_WEAPON_HGUN_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK  },
	{ ANIM_STD_WEAPON_AK_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_WEAPON_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_WEAPON_SNIPER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_WEAPON_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_THROW_UNDER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_START_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_DETONATE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK  },
	{ ANIM_STD_HGUN_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK  },
	{ ANIM_STD_AK_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK  },
#ifdef PC_PLAYER_CONTROLS
	// maybe wrong define, but unused anyway
	{ ANIM_FPS_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_FPS_BAT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_FPS_UZI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_FPS_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_FPS_AK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_FPS_M16, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_FPS_ROCKET, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
#endif
	{ 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_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_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 },
	{ ANIM_STD_CAR_DRIVE_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_CAR_DRIVE_LEFT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_CAR_DRIVE_RIGHT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_CAR_DRIVEBY_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_CAR_DRIVEBY_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_CAR_LOOKBEHIND, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_BOAT_DRIVE, ASSOC_DELETEFADEDOUT },
	{ 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_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 },
	{ 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_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 },
	{ 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_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_MEDIC_CPR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_DUCK_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_DUCK_LOW, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_RBLOCK_SHOOT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
	{ ANIM_STD_THROW_UNDER2, ASSOC_FADEOUTWHENDONE | 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_PARTIAL },
	{ ANIM_STD_PHONE_OUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL },
	{ ANIM_STD_PHONE_TALK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL },
};
#ifdef PC_PLAYER_CONTROLS
AnimAssocDesc aStdAnimDescsSide[] = {
	{ ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION },
	{ ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION },
	{ ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION },
	{ 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_cam",
	"idle_hbhb",
	"idle_tired",
	"idle_armed",
	"idle_chat",
	"idle_taxi",
	"KO_shot_front",
	"KO_shot_front",
	"KO_shot_front",
	"KO_shot_front",
	"KO_shot_face",
	"KO_shot_stom",
	"KO_shot_arml",
	"KO_shot_armR",
	"KO_shot_legl",
	"KO_shot_legR",
	"KD_left",
	"KD_right",
	"KO_skid_front",
	"KO_spin_R",
	"KO_skid_back",
	"KO_spin_L",
	"SHOT_partial",
	"SHOT_leftP",
	"SHOT_partial",
	"SHOT_rightP",
	"HIT_front",
	"HIT_L",
	"HIT_back",
	"HIT_R",
	"FLOOR_hit",
#if GTA_VERSION <= GTA3_PS2_160
	"HIT_body",
#endif
	"HIT_bodyblow",
	"HIT_chest",
	"HIT_head",
	"HIT_walk",
	"HIT_wall",
	"FLOOR_hit_f",
	"HIT_behind",
	"punchR",
	"KICK_floor",
	"WEAPON_bat_h",
	"WEAPON_bat_v",
	"WEAPON_hgun_body",
	"WEAPON_AK_body",
	"WEAPON_pump",
	"WEAPON_sniper",
	"WEAPON_throw",
	"WEAPON_throwu",
	"WEAPON_start_throw",
	"bomber",
	"WEAPON_hgun_rload",
	"WEAPON_AK_rload",
#ifdef PC_PLAYER_CONTROLS
	// maybe wrong define, but unused anyway
	"FPS_PUNCH",
	"FPS_BAT",
	"FPS_UZI",
	"FPS_PUMP",
	"FPS_AK",
	"FPS_M16",
	"FPS_ROCKET",
#endif
	"FIGHTIDLE",
	"FIGHT2IDLE",
	"FIGHTsh_F",
	"FIGHTbodyblow",
	"FIGHThead",
	"FIGHTkick",
	"FIGHTknee",
	"FIGHTLhook",
	"FIGHTpunch",
	"FIGHTrndhse",
	"FIGHTlngkck",
	"FIGHTppunch",
	"car_jackedRHS",
	"car_LjackedRHS",
	"car_jackedLHS",
	"car_LjackedLHS",
	"CAR_Qjack",
	"CAR_Qjacked",
	"CAR_align_LHS",
	"CAR_alignHI_LHS",
	"CAR_open_LHS",
	"CAR_doorlocked_LHS",
	"CAR_pullout_LHS",
	"CAR_pulloutL_LHS",
	"CAR_getin_LHS",
	"CAR_getinL_LHS",
	"CAR_closedoor_LHS",
	"CAR_closedoorL_LHS",
	"CAR_rolldoor",
	"CAR_rolldoorLO",
	"CAR_getout_LHS",
	"CAR_getoutL_LHS",
	"CAR_close_LHS",
	"CAR_align_RHS",
	"CAR_alignHI_RHS",
	"CAR_open_RHS",
	"CAR_doorlocked_RHS",
	"CAR_pullout_RHS",
	"CAR_pulloutL_RHS",
	"CAR_getin_RHS",
	"CAR_getinL_RHS",
	"CAR_closedoor_RHS",
	"CAR_closedoorL_RHS",
	"CAR_shuffle_RHS",
	"CAR_Lshuffle_RHS",
	"CAR_sit",
	"CAR_Lsit",
	"CAR_sitp",
	"CAR_sitpLO",
	"DRIVE_L",
	"Drive_R",
	"Drive_LO_l",
	"Drive_LO_R",
	"Driveby_L",
	"Driveby_R",
	"CAR_LB",
	"DRIVE_BOAT",
	"CAR_getout_RHS",
	"CAR_getoutL_RHS",
	"CAR_close_RHS",
	"car_hookertalk",
	"COACH_opnL",
	"COACH_opnR",
	"COACH_inL",
	"COACH_inR",
	"COACH_outL",
	"TRAIN_getin",
	"TRAIN_getout",
	"CAR_crawloutRHS",
	"CAR_crawloutRHS",
	"VAN_openL",
	"VAN_getinL",
	"VAN_closeL",
	"VAN_getoutL",
	"VAN_open",
	"VAN_getin",
	"VAN_close",
	"VAN_getout",
	"Getup",
	"Getup",
	"Getup",
	"Getup_front",
	"JUMP_launch",
	"JUMP_glide",
	"JUMP_land",
	"FALL_fall",
	"FALL_glide",
	"FALL_land",
	"FALL_collapse",
	"EV_step",
	"EV_dive",
	"XPRESSscratch",
	"roadcross",
	"TURN_180",
	"ARRESTgun",
	"DROWN",
	"CPR",
	"DUCK_down",
	"DUCK_low",
	"RBLOCK_Cshoot",
	"WEAPON_throwu",
	"handsup",
	"handsCOWER",
	"FUCKU",
	"PHONE_in",
	"PHONE_out",
	"PHONE_talk",
};
char 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_player",
	"run_armed",
	"run_armed",
	"idle_stance",
	"walk_start",
};
char const *aPlayerBBBatAnimations[] = {
	"walk_player",
	"run_player",
	"run_player",
	"IDLE_STANCE",
	"walk_start",
};
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 *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 *aOldWomanAnimations[] = {
	"woman_walkold",
	"woman_run",
	"woman_runpanic",
	"woman_idlestance",
};
char const *aFatWomanAnimations[] = {
	"walk_fat",
	"woman_run",
	"woman_runpanic",
	"woman_idlestance",
};
char const *aPanicChunkyAnimations[] = {
	"run_fatold",
	"woman_runpanic",
	"woman_runpanic",
	"idle_stance",
};
#ifdef PC_PLAYER_CONTROLS
char const *aPlayerStrafeBackAnimations[] = {
	"walk_player_back",
	"run_player_back",
	"run_player_back",
	"IDLE_STANCE",
	"walk_start_back",
};
char const *aPlayerStrafeLeftAnimations[] = {
	"walk_player_left",
	"run_left",
	"run_left",
	"IDLE_STANCE",
	"walk_start_left",
};
char const *aPlayerStrafeRightAnimations[] = {
	"walk_player_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",
};
#endif

#define awc(a) ARRAY_SIZE(a), a
const AnimAssocDefinition CAnimManager::ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS] = {
	{ "man", "ped", MI_COP, awc(aStdAnimations), aStdAnimDescs },
	{ "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 },
	{ "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 },
	{ "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 },
	{ "oldwoman", "ped", MI_COP, awc(aOldWomanAnimations), aStdAnimDescs },
	{ "fatwoman", "ped", MI_COP, awc(aFatWomanAnimations), aStdAnimDescs },
	{ "panicchunky", "ped", MI_COP, awc(aPanicChunkyAnimations), 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 },
#endif
};
#undef awc

void
CAnimManager::Initialise(void)
{
	ms_numAnimations = 0;
	ms_numAnimBlocks = 0;
	ms_animCache.Init(25);

//	dumpanimdata();
}

void
CAnimManager::Shutdown(void)
{
	int i;

	ms_animCache.Shutdown();

	for(i = 0; i < ms_numAnimations; i++)
		ms_aAnimations[i].Shutdown();

	delete[] ms_aAnimAssocGroups;
}

void
CAnimManager::UncompressAnimation(CAnimBlendHierarchy *hier)
{
	if(!hier->compressed){
		if(hier->linkPtr){
			hier->linkPtr->Remove();
			ms_animCache.head.Insert(hier->linkPtr);
		}
	}else{
		CLink<CAnimBlendHierarchy*> *link = ms_animCache.Insert(hier);
		if(link == nil){
			ms_animCache.tail.prev->item->RemoveUncompressedData();
			ms_animCache.Remove(ms_animCache.tail.prev);
			link = ms_animCache.Insert(hier);
		}
		hier->linkPtr = link;
		hier->Uncompress();
	}
}

CAnimBlock*
CAnimManager::GetAnimationBlock(const char *name)
{
	int i;

	for(i = 0; i < ms_numAnimBlocks; i++)
		if(strcasecmp(ms_aAnimBlocks[i].name, name) == 0)
			return &ms_aAnimBlocks[i];
	return nil;
}

CAnimBlendHierarchy*
CAnimManager::GetAnimation(const char *name, CAnimBlock *animBlock)
{
	int i;
	CAnimBlendHierarchy *hier = &ms_aAnimations[animBlock->firstIndex];

	for(i = 0; i < animBlock->numAnims; i++){
		if(!CGeneral::faststricmp(hier->name, name))
			return hier;
		hier++;
	}
	return nil;
}

const char*
CAnimManager::GetAnimGroupName(AssocGroupId groupId)
{
	return ms_aAnimAssocDefinitions[groupId].name;
}

CAnimBlendAssociation*
CAnimManager::CreateAnimAssociation(AssocGroupId groupId, AnimationId animId)
{
	return ms_aAnimAssocGroups[groupId].CopyAnimation(animId);
}

CAnimBlendAssociation*
CAnimManager::GetAnimAssociation(AssocGroupId groupId, AnimationId animId)
{
	return ms_aAnimAssocGroups[groupId].GetAnimation(animId);
}

CAnimBlendAssociation*
CAnimManager::GetAnimAssociation(AssocGroupId groupId, const char *name)
{
	return ms_aAnimAssocGroups[groupId].GetAnimation(name);
}

CAnimBlendAssociation*
CAnimManager::AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId)
{
	CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId);
	CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump);
	if(anim->IsMovement()){
		CAnimBlendAssociation *syncanim = nil;
		CAnimBlendLink *link;
		for(link = clumpData->link.next; link; link = link->next){
			syncanim = CAnimBlendAssociation::FromLink(link);
			if(syncanim->IsMovement())
				break;
		}
		if(link){
			anim->SyncAnimation(syncanim);
			anim->flags |= ASSOC_RUNNING;
		}else
			anim->Start(0.0f);
	}else
		anim->Start(0.0f);

	clumpData->link.Prepend(&anim->link);
	return anim;
}

CAnimBlendAssociation*
CAnimManager::AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId)
{
	CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId);
	CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump);
	if (anim->IsMovement() && syncanim){
		anim->SyncAnimation(syncanim);
		anim->flags |= ASSOC_RUNNING;
	}else
		anim->Start(0.0f);

	clumpData->link.Prepend(&anim->link);
	return anim;
}

CAnimBlendAssociation*
CAnimManager::BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta)
{
	int removePrevAnim = 0;
	CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump);
	CAnimBlendAssociation *anim = GetAnimAssociation(groupId, animId);
	bool isMovement = anim->IsMovement();
	bool isPartial = anim->IsPartial();
	CAnimBlendLink *link;
	CAnimBlendAssociation *found = nil, *movementAnim = nil;
	for(link = clumpData->link.next; link; link = link->next){
		anim = CAnimBlendAssociation::FromLink(link);
		if(isMovement && anim->IsMovement())
			movementAnim = anim;
		if(anim->animId == animId)
			found = anim;
		else{
			if(isPartial == anim->IsPartial()){
				if(anim->blendAmount > 0.0f){
					float blendDelta = -delta*anim->blendAmount;
					if(blendDelta < anim->blendDelta || !isPartial)
						anim->blendDelta = blendDelta;
				}else{
					anim->blendDelta = -1.0f;
				}
				anim->flags |= ASSOC_DELETEFADEDOUT;
				removePrevAnim = 1;
			}
		}
	}
	if(found){
		found->blendDelta = (1.0f - found->blendAmount)*delta;
		if(!found->IsRunning() && found->currentTime == found->hierarchy->totalLength)
			found->Start(0.0f);
	}else{
		found = AddAnimationAndSync(clump, movementAnim, groupId, animId);
		if(!removePrevAnim && !isPartial){
			found->blendAmount = 1.0f;
			return found;
		}
		found->blendAmount = 0.0f;
		found->blendDelta = delta;
	}
	UncompressAnimation(found->hierarchy);
	return found;
}

void
CAnimManager::LoadAnimFiles(void)
{
	int i, j;

	LoadAnimFile("ANIM\\PED.IFP");

	// Create all assoc groups
	ms_aAnimAssocGroups = new CAnimBlendAssocGroup[NUM_ANIM_ASSOC_GROUPS];
	for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++){
		CBaseModelInfo *mi = CModelInfo::GetModelInfo(ms_aAnimAssocDefinitions[i].modelIndex);
		RpClump *clump = (RpClump*)mi->CreateInstance();
		RpAnimBlendClumpInit(clump);
		CAnimBlendAssocGroup *group = &ms_aAnimAssocGroups[i];
		const AnimAssocDefinition *def = &ms_aAnimAssocDefinitions[i];
		group->CreateAssociations(def->blockName, clump, def->animNames, def->numAnims);
		for(j = 0; j < group->numAssociations; j++)
			group->GetAnimation(j)->flags |= def->animDescs[j].flags;
#ifdef PED_SKIN
		// forgot on xbox/android
		if(IsClumpSkinned(clump))
			RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil);
#endif
		RpClumpDestroy(clump);
	}
}

void
CAnimManager::LoadAnimFile(const char *filename)
{
	int fd;
	fd = CFileMgr::OpenFile(filename, "rb");
	assert(fd > 0);
	LoadAnimFile(fd, true);
	CFileMgr::CloseFile(fd);
}

void
CAnimManager::LoadAnimFile(int fd, bool compress)
{
	#define ROUNDSIZE(x) if((x) & 3) (x) += 4 - ((x)&3)
	struct IfpHeader {
		char ident[4];
		uint32 size;
	};
	IfpHeader anpk, info, name, dgan, cpan, anim;
	int numANPK;
	char buf[256];
	int i, j, k, l;
	float *fbuf = (float*)buf;

	CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader));
	if(!CGeneral::faststrncmp(anpk.ident, "ANLF", 4)) {
		ROUNDSIZE(anpk.size);
		CFileMgr::Read(fd, buf, anpk.size);
		numANPK = *(int*)buf;
	} else if(!CGeneral::faststrncmp(anpk.ident, "ANPK", 4)) {
		CFileMgr::Seek(fd, -8, 1);
		numANPK = 1;
	}

	for(i = 0; i < numANPK; i++){
		// block name
		CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader));
		ROUNDSIZE(anpk.size);
		CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader));
		ROUNDSIZE(info.size);
		CFileMgr::Read(fd, buf, info.size);
		CAnimBlock *animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++];
		strncpy(animBlock->name, buf+4, 24);
		animBlock->numAnims = *(int*)buf;

		animBlock->firstIndex = ms_numAnimations;

		for(j = 0; j < animBlock->numAnims; j++){
			CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++];

			// animation name
			CFileMgr::Read(fd, (char*)&name, sizeof(IfpHeader));
			ROUNDSIZE(name.size);
			CFileMgr::Read(fd, buf, name.size);
			hier->SetName(buf);

			// DG info has number of nodes/sequences
			CFileMgr::Read(fd, (char*)&dgan, sizeof(IfpHeader));
			ROUNDSIZE(dgan.size);
			CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader));
			ROUNDSIZE(info.size);
			CFileMgr::Read(fd, buf, info.size);
			hier->numSequences = *(int*)buf;
			hier->sequences = new CAnimBlendSequence[hier->numSequences];

			CAnimBlendSequence *seq = hier->sequences;
			for(k = 0; k < hier->numSequences; k++, seq++){
				// Each node has a name and key frames
				CFileMgr::Read(fd, (char*)&cpan, sizeof(IfpHeader));
				ROUNDSIZE(dgan.size);
				CFileMgr::Read(fd, (char*)&anim, sizeof(IfpHeader));
				ROUNDSIZE(anim.size);
				CFileMgr::Read(fd, buf, anim.size);
				int numFrames = *(int*)(buf+28);
				seq->SetName(buf);
#ifdef PED_SKIN
				if(anim.size == 44)
					seq->SetBoneTag(*(int*)(buf+40));
#endif
				if(numFrames == 0)
					continue;

				bool hasScale = false;
				bool hasTranslation = false;
				CFileMgr::Read(fd, (char*)&info, sizeof(info));
				if(!CGeneral::faststrncmp(info.ident, "KRTS", 4)) {
					hasScale = true;
					seq->SetNumFrames(numFrames, true);
				}else if(!CGeneral::faststrncmp(info.ident, "KRT0", 4)) {
					hasTranslation = true;
					seq->SetNumFrames(numFrames, true);
				}else if(!CGeneral::faststrncmp(info.ident, "KR00", 4)){
					seq->SetNumFrames(numFrames, false);
				}

				for(l = 0; l < numFrames; l++){
					if(hasScale){
						CFileMgr::Read(fd, buf, 0x2C);
						CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
						rot.Invert();
						CVector trans(fbuf[4], fbuf[5], fbuf[6]);

						KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l);
						kf->rotation = rot;
						kf->translation = trans;
						// scaling ignored
						kf->deltaTime = fbuf[10];	// absolute time here
					}else if(hasTranslation){
						CFileMgr::Read(fd, buf, 0x20);
						CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
						rot.Invert();
						CVector trans(fbuf[4], fbuf[5], fbuf[6]);

						KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l);
						kf->rotation = rot;
						kf->translation = trans;
						kf->deltaTime = fbuf[7];	// absolute time here
					}else{
						CFileMgr::Read(fd, buf, 0x14);
						CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
						rot.Invert();

						KeyFrame *kf = (KeyFrame*)seq->GetKeyFrame(l);
						kf->rotation = rot;
						kf->deltaTime = fbuf[4];	// absolute time here
					}
				}

				// convert absolute time to deltas
				for(l = seq->numFrames-1; l > 0; l--){
					KeyFrame *kf1 = seq->GetKeyFrame(l);
					KeyFrame *kf2 = seq->GetKeyFrame(l-1);
					kf1->deltaTime -= kf2->deltaTime;
				}
			}

			hier->RemoveQuaternionFlips();
			if(compress)
				hier->RemoveUncompressedData();
			else
				hier->CalcTotalTime();
		}
	}
}

void
CAnimManager::RemoveLastAnimFile(void)
{
	int i;
	ms_numAnimBlocks--;
	ms_numAnimations = ms_aAnimBlocks[ms_numAnimBlocks].firstIndex;
	for(i = 0; i < ms_aAnimBlocks[ms_numAnimBlocks].numAnims; i++)
		ms_aAnimations[ms_aAnimBlocks[ms_numAnimBlocks].firstIndex + i].RemoveAnimSequences();
}


code:
#pragma once

#include "AnimBlendHierarchy.h"
#include "AnimationId.h"

enum AssocGroupId
{
	ASSOCGRP_STD,
	ASSOCGRP_PLAYER,
	ASSOCGRP_PLAYERROCKET,
	ASSOCGRP_PLAYER1ARMED,
	ASSOCGRP_PLAYER2ARMED,
	ASSOCGRP_PLAYERBBBAT,
	ASSOCGRP_SHUFFLE,
	ASSOCGRP_OLD,
	ASSOCGRP_GANG1,
	ASSOCGRP_GANG2,
	ASSOCGRP_FAT,
	ASSOCGRP_OLDFAT,
	ASSOCGRP_WOMAN,
	ASSOCGRP_WOMANSHOP,
	ASSOCGRP_BUSYWOMAN,
	ASSOCGRP_SEXYWOMAN,
	ASSOCGRP_OLDWOMAN,
	ASSOCGRP_FATWOMAN,
	ASSOCGRP_PANICCHUNKY,
#ifdef PC_PLAYER_CONTROLS
	ASSOCGRP_PLAYERBACK,
	ASSOCGRP_PLAYERLEFT,
	ASSOCGRP_PLAYERRIGHT,
	ASSOCGRP_ROCKETBACK,
	ASSOCGRP_ROCKETLEFT,
	ASSOCGRP_ROCKETRIGHT,
#endif

	NUM_ANIM_ASSOC_GROUPS
};

class CAnimBlendAssociation;
class CAnimBlendAssocGroup;

// A block of hierarchies
struct CAnimBlock
{
	char name[24];
	int32 firstIndex;
	int32 numAnims;
};

struct AnimAssocDesc
{
	int32 animId;
	int32 flags;
};

struct AnimAssocDefinition
{
	char 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 CAnimBlock *GetAnimationBlock(const char *name);
	static CAnimBlendHierarchy *GetAnimation(const char *name, CAnimBlock *animBlock);
	static CAnimBlendHierarchy *GetAnimation(int32 n) { return &ms_aAnimations[n]; }
	static const char *GetAnimGroupName(AssocGroupId groupId);
	static CAnimBlendAssociation *CreateAnimAssociation(AssocGroupId groupId, AnimationId animId);
	static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, AnimationId animId);
	static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, const char *name);
	static CAnimBlendAssociation *AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId);
	static CAnimBlendAssociation *AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId);
	static CAnimBlendAssociation *BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta);
	static void LoadAnimFiles(void);
	static void LoadAnimFile(const char *filename);
	static void LoadAnimFile(int fd, bool compress);
	static void RemoveLastAnimFile(void);
};

license: mit


code:
#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 */
#if GTA_VERSION <= GTA3_PS2_160
	ANIM_STD_HIT_BODY,
#endif
	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_PUNCH,
	ANIM_STD_KICKGROUND,

	/* names made up */
	ANIM_STD_WEAPON_BAT_H,
	ANIM_STD_WEAPON_BAT_V,
	ANIM_STD_WEAPON_HGUN_BODY,
	ANIM_STD_WEAPON_AK_BODY,
	ANIM_STD_WEAPON_PUMP,
	ANIM_STD_WEAPON_SNIPER,
	ANIM_STD_WEAPON_THROW,
	/**/

	ANIM_STD_THROW_UNDER,

	/* names made up */
	ANIM_STD_START_THROW,
	/**/

	ANIM_STD_DETONATE,

	/* names made up */
	ANIM_STD_HGUN_RELOAD,
	ANIM_STD_AK_RELOAD,
#ifdef PC_PLAYER_CONTROLS
	// maybe wrong define, but unused anyway
	ANIM_FPS_PUNCH,
	ANIM_FPS_BAT,
	ANIM_FPS_UZI,
	ANIM_FPS_PUMP,
	ANIM_FPS_AK,
	ANIM_FPS_M16,
	ANIM_FPS_ROCKET,
#endif
	/**/

	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,
	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_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_LOOKBEHIND,
	ANIM_STD_BOAT_DRIVE,
	ANIM_STD_GETOUT_RHS,
	ANIM_STD_GETOUT_LO_RHS,
	ANIM_STD_CAR_CLOSE_RHS,
	ANIM_STD_CAR_HOOKERTALK,
	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_STD_TRAIN_GETIN,
	ANIM_STD_TRAIN_GETOUT,
	ANIM_STD_CRAWLOUT_LHS,
	ANIM_STD_CRAWLOUT_RHS,
	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_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_EVADE_STEP,
	ANIM_STD_EVADE_DIVE,
	ANIM_STD_XPRESS_SCRATCH,
	ANIM_STD_ROADCROSS,
	ANIM_STD_TURN180,
	ANIM_STD_ARREST,
	ANIM_STD_DROWN,
	ANIM_MEDIC_CPR,
	ANIM_STD_DUCK_DOWN,
	ANIM_STD_DUCK_LOW,
	ANIM_STD_RBLOCK_SHOOT,

	/* names made up */
	ANIM_STD_THROW_UNDER2,
	/**/

	ANIM_STD_HANDSUP,
	ANIM_STD_HANDSCOWER,
	ANIM_STD_PARTIAL_FUCKU,
	ANIM_STD_PHONE_IN,
	ANIM_STD_PHONE_OUT,
	ANIM_STD_PHONE_TALK,

	ANIM_STD_NUM
};


code:
#include "common.h"
#include "PedModelInfo.h"
#include "Bones.h"

#ifdef PED_SKIN

int
ConvertPedNode2BoneTag(int node)
{
	switch(node){
	case PED_TORSO:	return BONE_waist;
	case PED_MID:	return BONE_torso;	// this is what Xbox/Mobile use
	//		return BONE_mid;	// this is what PS2/PC use
	case PED_HEAD:		return BONE_head;
	case PED_UPPERARML:	return BONE_upperarml;
	case PED_UPPERARMR:	return BONE_upperarmr;
	case PED_HANDL:		return BONE_Lhand;
	case PED_HANDR:		return BONE_Rhand;
	case PED_UPPERLEGL:	return BONE_upperlegl;
	case PED_UPPERLEGR:	return BONE_upperlegr;
	case PED_FOOTL:		return BONE_footl;
	case PED_FOOTR:		return BONE_footr;
	case PED_LOWERLEGR:	return BONE_lowerlegl;
	}
	return -1;
}

const char*
ConvertBoneTag2BoneName(int tag)
{
	switch(tag){
	case BONE_waist:	return "Swaist";
	case BONE_upperlegr:	return "Supperlegr";
	case BONE_lowerlegr:	return "Slowerlegr";
	case BONE_footr:	return "Sfootr";
	case BONE_upperlegl:	return "Supperlegl";
	case BONE_lowerlegl:	return "Slowerlegl";
	case BONE_footl:	return "Sfootl";
	case BONE_mid:	return "Smid";
	case BONE_torso:	return "Storso";
	case BONE_head:	return "Shead";
	case BONE_upperarmr:	return "Supperarmr";
	case BONE_lowerarmr:	return "Slowerarmr";
	case BONE_Rhand:	return "SRhand";
	case BONE_upperarml:	return "Supperarml";
	case BONE_lowerarml:	return "Slowerarml";
	case BONE_Lhand:	return "SLhand";
	}
	return nil;
}

#endif


code:
#pragma once

enum BoneTag
{
	BONE_waist,
	BONE_upperlegr,
	BONE_lowerlegr,
	BONE_footr,
	BONE_upperlegl,
	BONE_lowerlegl,
	BONE_footl,
	BONE_mid,
	BONE_torso,
	BONE_head,
	BONE_upperarmr,
	BONE_lowerarmr,
	BONE_Rhand,
	BONE_upperarml,
	BONE_lowerarml,
	BONE_Lhand,
};

int ConvertPedNode2BoneTag(int node);
const char *ConvertBoneTag2BoneName(int tag);


code:
#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 "CutsceneHead.h"
#include "RpAnimBlend.h"
#include "ModelIndices.h"
#include "TempColModels.h"

const struct {
	const char *szTrackName;
	int iTrackId;
} musicNameIdAssoc[] = {
	{ "JB",			STREAMED_SOUND_NEWS_INTRO },
	{ "BET",		STREAMED_SOUND_BANK_INTRO },
	{ "L1_LG",		STREAMED_SOUND_CUTSCENE_LUIGI1_LG },
	{ "L2_DSB",		STREAMED_SOUND_CUTSCENE_LUIGI2_DSB },
	{ "L3_DM",		STREAMED_SOUND_CUTSCENE_LUIGI3_DM },
	{ "L4_PAP",		STREAMED_SOUND_CUTSCENE_LUIGI4_PAP },
	{ "L5_TFB",		STREAMED_SOUND_CUTSCENE_LUIGI5_TFB },
	{ "J0_DM2",		STREAMED_SOUND_CUTSCENE_JOEY0_DM2 },
	{ "J1_LFL",		STREAMED_SOUND_CUTSCENE_JOEY1_LFL },
	{ "J2_KCL",		STREAMED_SOUND_CUTSCENE_JOEY2_KCL },
	{ "J3_VH",		STREAMED_SOUND_CUTSCENE_JOEY3_VH },
	{ "J4_ETH",		STREAMED_SOUND_CUTSCENE_JOEY4_ETH },
	{ "J5_DST",		STREAMED_SOUND_CUTSCENE_JOEY5_DST },
	{ "J6_TBJ",		STREAMED_SOUND_CUTSCENE_JOEY6_TBJ },
	{ "T1_TOL",		STREAMED_SOUND_CUTSCENE_TONI1_TOL },
	{ "T2_TPU",		STREAMED_SOUND_CUTSCENE_TONI2_TPU },
	{ "T3_MAS",		STREAMED_SOUND_CUTSCENE_TONI3_MAS },
	{ "T4_TAT",		STREAMED_SOUND_CUTSCENE_TONI4_TAT },
	{ "T5_BF",		STREAMED_SOUND_CUTSCENE_TONI5_BF },
	{ "S0_MAS",		STREAMED_SOUND_CUTSCENE_SAL0_MAS },
	{ "S1_PF",		STREAMED_SOUND_CUTSCENE_SAL1_PF },
	{ "S2_CTG",		STREAMED_SOUND_CUTSCENE_SAL2_CTG },
	{ "S3_RTC",		STREAMED_SOUND_CUTSCENE_SAL3_RTC },
	{ "S5_LRQ",		STREAMED_SOUND_CUTSCENE_SAL5_LRQ },
	{ "S4_BDBA",	STREAMED_SOUND_CUTSCENE_SAL4_BDBA },
	{ "S4_BDBB",	STREAMED_SOUND_CUTSCENE_SAL4_BDBB },
	{ "S2_CTG2",	STREAMED_SOUND_CUTSCENE_SAL2_CTG2 },
	{ "S4_BDBD",	STREAMED_SOUND_CUTSCENE_SAL4_BDBD },
	{ "S5_LRQB",	STREAMED_SOUND_CUTSCENE_SAL5_LRQB },
	{ "S5_LRQC",	STREAMED_SOUND_CUTSCENE_SAL5_LRQC },
	{ "A1_SS0",		STREAMED_SOUND_CUTSCENE_ASUKA_1_SSO },
	{ "A2_PP",		STREAMED_SOUND_CUTSCENE_ASUKA_2_PP },
	{ "A3_SS",		STREAMED_SOUND_CUTSCENE_ASUKA_3_SS },
	{ "A4_PDR",		STREAMED_SOUND_CUTSCENE_ASUKA_4_PDR },
	{ "A5_K2FT",	STREAMED_SOUND_CUTSCENE_ASUKA_5_K2FT},
	{ "K1_KBO",		STREAMED_SOUND_CUTSCENE_KENJI1_KBO },
	{ "K2_GIS",		STREAMED_SOUND_CUTSCENE_KENJI2_GIS },
	{ "K3_DS",		STREAMED_SOUND_CUTSCENE_KENJI3_DS },
	{ "K4_SHI",		STREAMED_SOUND_CUTSCENE_KENJI4_SHI },
	{ "K5_SD",		STREAMED_SOUND_CUTSCENE_KENJI5_SD },
	{ "R0_PDR2",	STREAMED_SOUND_CUTSCENE_RAY0_PDR2 },
	{ "R1_SW",		STREAMED_SOUND_CUTSCENE_RAY1_SW },
	{ "R2_AP",		STREAMED_SOUND_CUTSCENE_RAY2_AP },
	{ "R3_ED",		STREAMED_SOUND_CUTSCENE_RAY3_ED },
	{ "R4_GF",		STREAMED_SOUND_CUTSCENE_RAY4_GF },
	{ "R5_PB",		STREAMED_SOUND_CUTSCENE_RAY5_PB },
	{ "R6_MM",		STREAMED_SOUND_CUTSCENE_RAY6_MM },
	{ "D1_STOG",	STREAMED_SOUND_CUTSCENE_DONALD1_STOG },
	{ "D2_KK",		STREAMED_SOUND_CUTSCENE_DONALD2_KK },
	{ "D3_ADO",		STREAMED_SOUND_CUTSCENE_DONALD3_ADO },
	{ "D5_ES",		STREAMED_SOUND_CUTSCENE_DONALD5_ES },
	{ "D7_MLD",		STREAMED_SOUND_CUTSCENE_DONALD7_MLD },
	{ "D4_GTA",		STREAMED_SOUND_CUTSCENE_DONALD4_GTA },
	{ "D4_GTA2",	STREAMED_SOUND_CUTSCENE_DONALD4_GTA2 },
	{ "D6_STS",		STREAMED_SOUND_CUTSCENE_DONALD6_STS },
	{ "A6_BAIT",	STREAMED_SOUND_CUTSCENE_ASUKA6_BAIT },
	{ "A7_ETG",		STREAMED_SOUND_CUTSCENE_ASUKA7_ETG },
	{ "A8_PS",		STREAMED_SOUND_CUTSCENE_ASUKA8_PS },
	{ "A9_ASD",		STREAMED_SOUND_CUTSCENE_ASUKA9_ASD },
	{ "K4_SHI2",	STREAMED_SOUND_CUTSCENE_KENJI4_SHI2 },
	{ "C1_TEX",		STREAMED_SOUND_CUTSCENE_CATALINA1_TEX },
	{ "EL_PH1",		STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1 },
	{ "EL_PH2",		STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2 },
	{ "EL_PH3",		STREAMED_SOUND_CUTSCENE_ELBURRO3_PH3 },
	{ "EL_PH4",		STREAMED_SOUND_CUTSCENE_ELBURRO4_PH4 },
	{ "YD_PH1",		STREAMED_SOUND_CUTSCENE_YARDIE_PH1 },
	{ "YD_PH2",		STREAMED_SOUND_CUTSCENE_YARDIE_PH2 },
	{ "YD_PH3",		STREAMED_SOUND_CUTSCENE_YARDIE_PH3 },
	{ "YD_PH4",		STREAMED_SOUND_CUTSCENE_YARDIE_PH4 },
	{ "HD_PH1",		STREAMED_SOUND_CUTSCENE_HOODS_PH1 },
	{ "HD_PH2",		STREAMED_SOUND_CUTSCENE_HOODS_PH2 },
	{ "HD_PH3",		STREAMED_SOUND_CUTSCENE_HOODS_PH3 },
	{ "HD_PH4",		STREAMED_SOUND_CUTSCENE_HOODS_PH4 },
	{ "HD_PH5",		STREAMED_SOUND_CUTSCENE_HOODS_PH5 },
	{ "MT_PH1",		STREAMED_SOUND_CUTSCENE_MARTY_PH1 },
	{ "MT_PH2",		STREAMED_SOUND_CUTSCENE_MARTY_PH2 },
	{ "MT_PH3",		STREAMED_SOUND_CUTSCENE_MARTY_PH3 },
	{ "MT_PH4",		STREAMED_SOUND_CUTSCENE_MARTY_PH4 },
	{ 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;
uint32 CCutsceneMgr::ms_cutsceneLoadStatus;

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_running = false;
	ms_animLoaded = false;
	ms_cutsceneProcessing = false;
	ms_useLodMultiplier = false;

	ms_pCutsceneDir = new CDirectory(CUTSCENEDIRSIZE);
	ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR");
}

void
CCutsceneMgr::Shutdown(void)
{
	delete ms_pCutsceneDir;
}

void
CCutsceneMgr::LoadCutsceneData(const char *szCutsceneName)
{
	int file;
	uint32 size;
	uint32 offset;
	CPlayerPed *pPlayerPed;

	ms_cutsceneProcessing = true;
	if (!strcasecmp(szCutsceneName, "jb"))
		ms_useLodMultiplier = true;
	CTimer::Stop();

	ms_pCutsceneDir->numEntries = 0;
	ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR");

	CStreaming::RemoveUnusedModelsInLoadedList();
	CGame::DrasticTidyUpMemory(true);

	strcpy(ms_cutsceneName, szCutsceneName);
	file = CFileMgr::OpenFile("ANIM\\CUTS.IMG", "rb");

	// Load animations
	sprintf(gString, "%s.IFP", szCutsceneName);
	if (ms_pCutsceneDir->FindItem(gString, offset, size)) {
		CStreaming::MakeSpaceFor(size << 11);
		CStreaming::ImGonnaUseStreamingMemory();
		CFileMgr::Seek(file, offset << 11, SEEK_SET);
		CAnimManager::LoadAnimFile(file, false);
		ms_cutsceneAssociations.CreateAssociations(szCutsceneName);
		CStreaming::IHaveUsedStreamingMemory();
		ms_animLoaded = true;
	} else {
		ms_animLoaded = false;
	}

	// Load camera data
	sprintf(gString, "%s.DAT", szCutsceneName);
	if (ms_pCutsceneDir->FindItem(gString, offset, size)) {
		CFileMgr::Seek(file, offset << 11, SEEK_SET);
		TheCamera.LoadPathSplines(file);
	}

	CFileMgr::CloseFile(file);

	if (CGeneral::faststricmp(ms_cutsceneName, "end")) {
		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();
	CTimer::Update();

	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);
}

void
CCutsceneMgr::SetHeadAnim(const char *animName, CObject *pObject)
{
	CCutsceneHead *pCutsceneHead = (CCutsceneHead*)pObject;
	char szAnim[CUTSCENENAMESIZE * 2];

	sprintf(szAnim, "%s_%s", ms_cutsceneName, animName);
	pCutsceneHead->PlayAnimation(szAnim);
}

void
CCutsceneMgr::FinishCutscene()
{
	CCutsceneMgr::ms_cutsceneTimer = TheCamera.GetCutSceneFinishTime() * 0.001f;
	TheCamera.FinishCutscene();

	FindPlayerPed()->bIsVisible = true;
	CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false);
}

void
CCutsceneMgr::SetupCutsceneToStart(void)
{
	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());
			ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + ((KeyFrameTrans*)pAnimBlendAssoc->hierarchy->sequences[0].GetKeyFrame(0))->translation);
			CWorld::Add(ms_pCutsceneObjects[i]);
			pAnimBlendAssoc->SetRun();
		} else {
			ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset);
		}
	}

	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);
	RpAnimBlendClumpRemoveAllAssociations((RpClump*)pObject->m_rwObject);

	pNewAnim = ms_cutsceneAssociations.CopyAnimation(animName);
	pNewAnim->SetCurrentTime(0.0f);
	pNewAnim->flags |= ASSOC_HAS_TRANSLATION;
	pNewAnim->flags &= ~ASSOC_RUNNING;

	pAnimBlendClumpData = *RPANIMBLENDCLUMPDATA(pObject->m_rwObject);
	pAnimBlendClumpData->link.Prepend(&pNewAnim->link);
}

CCutsceneHead *
CCutsceneMgr::AddCutsceneHead(CObject *pObject, int modelId)
{
	CCutsceneHead *pHead = new CCutsceneHead(pObject);
	pHead->SetModelIndex(modelId);
	CWorld::Add(pHead);
	ms_pCutsceneObjects[ms_numCutsceneObjs++] = pHead;
	return pHead;
}

CCutsceneObject *
CCutsceneMgr::CreateCutsceneObject(int modelId)
{
	CBaseModelInfo *pModelInfo;
	CColModel *pColModel;
	float radius;
	RpClump *clump;
	CCutsceneObject *pCutsceneObject;

	if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) {
		pModelInfo = CModelInfo::GetModelInfo(modelId);
		pColModel = &CTempColModels::ms_colModelCutObj[modelId - MI_CUTOBJ01];
		radius = 0.0f;

		pModelInfo->SetColModel(pColModel);
		clump = (RpClump*)pModelInfo->GetRwObject();
		assert(RwObjectGetType((RwObject*)clump) == rpCLUMP);
		RpClumpForAllAtomics(clump, CalculateBoundingSphereRadiusCB, &radius);

		pColModel->boundingSphere.radius = radius;
		pColModel->boundingBox.min = CVector(-radius, -radius, -radius);
		pColModel->boundingBox.max = CVector(radius, radius, radius);
	}

	pCutsceneObject = new CCutsceneObject();
	pCutsceneObject->SetModelIndex(modelId);
	ms_pCutsceneObjects[ms_numCutsceneObjs++] = pCutsceneObject;
	return pCutsceneObject;
}

void
CCutsceneMgr::DeleteCutsceneData(void)
{
	if (!ms_loaded) return;

	ms_cutsceneProcessing = false;
	ms_useLodMultiplier = false;

	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;

	if (ms_animLoaded)
		CAnimManager::RemoveLastAnimFile();

	ms_animLoaded = false;
	TheCamera.RestoreWithJumpCut();
	TheCamera.SetWideScreenOff();
	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, "end")) {
		DMAudio.StopCutSceneMusic();
		if (CGeneral::faststricmp(ms_cutsceneName, "bet"))
			DMAudio.ChangeMusicMode(MUSICMODE_GAME);
	}
	CTimer::Stop();
	CGame::DrasticTidyUpMemory(TheCamera.GetScreenFadeStatus() == FADE_2);
	CTimer::Update();
}

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, "end"))
			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();
	if (CGeneral::faststricmp(ms_cutsceneName, "end") && 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 TheCamera.GetPositionAlongSpline() == 1.0f; }


code:
#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_cutsceneProcessing;
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 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 SetHeadAnim(const char *animName, CObject *pObject);
	static void SetupCutsceneToStart(void);
	static void SetCutsceneAnim(const char *animName, CObject *pObject);
	static CCutsceneHead *AddCutsceneHead(CObject *pObject, int modelId);
	static CCutsceneObject *CreateCutsceneObject(int modelId);
	static void DeleteCutsceneData(void);
	static void Update(void);
};


code:
#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
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);
}

#ifdef PED_SKIN

void
FrameUpdateCallBackSkinned(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;
	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;
#ifdef FIX_BUGS
			if(DotProduct(rot, q) < 0.0f)
				rot -= q;
			else
#endif
				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 = pos.x;
		xform->t.y = pos.y;
		xform->t.z = pos.z;
		xform->t.x += frame->resetPos.x;
		xform->t.y += frame->resetPos.y;
		xform->t.z += 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);
#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){
		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;
	}
}

#endif


code:
#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"
#ifdef PED_SKIN
#include "PedModelInfo.h"
#endif

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;
}

#ifdef PED_SKIN
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 = IsClumpSkinned(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];
		frames[i].hanimFrame = (RpHAnimStdInterpFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i);
	}
	clumpData->ForAllFrames(FrameInitCBskin, nil);
	clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION;
}
#endif

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)
{
#ifdef PED_SKIN
	if(IsClumpSkinned(clump))
		RpAnimBlendClumpInitSkinned(clump);
	else
#endif
		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;
}

#ifdef PED_SKIN
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))];
}
#endif

void
RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames)
{
#ifdef PED_SKIN
	if(IsClumpSkinned(clump))
		RpAnimBlendClumpFillFrameArraySkin(clump, frames);
	else
#endif
		(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCBnonskin, frames);
}

AnimBlendFrameData *pFrameDataFound;

// FrameFindCallBack on PS2
void
FrameFindByNameCBnonskin(AnimBlendFrameData *frame, void *arg)
{
	char *nodename = GetFrameNodeName(frame->frame);
	if(!CGeneral::faststricmp(nodename, (char*)arg))
		pFrameDataFound = frame;
}

#ifdef PED_SKIN
void
FrameFindByNameCBskin(AnimBlendFrameData *frame, void *arg)
{
	const char *name = ConvertBoneTag2BoneName(frame->nodeID);
	if(name && CGeneral::faststricmp(name, (char*)arg) == 0)
		pFrameDataFound = frame;
}
#endif

AnimBlendFrameData*
RpAnimBlendClumpFindFrame(RpClump *clump, const char *name)
{
	pFrameDataFound = nil;
#ifdef PED_SKIN
	if(IsClumpSkinned(clump))
		(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBskin, (void*)name);
	else
#endif
		(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBnonskin, (void*)name);
	return pFrameDataFound;
}

void
RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta)
{
	int i;
	AnimBlendFrameUpdateData updateData;
	float totalLength = 0.0f;
	float totalBlend = 0.0f;
	CAnimBlendLink *link, *next;
	CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump);
	gpAnimBlendClump = clumpData;

	if(clumpData->link.next == nil)
		return;

	// Update blend and get node array
	i = 0;
	updateData.foobar = 0;
	for(link = clumpData->link.next; link; link = next){
		next = link->next;
		CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link);
		if(assoc->UpdateBlend(timeDelta)){
			CAnimManager::UncompressAnimation(assoc->hierarchy);
			updateData.nodes[i++] = assoc->GetNode(0);
			if(assoc->flags & ASSOC_MOVEMENT){
				totalLength += assoc->hierarchy->totalLength/assoc->speed * assoc->blendAmount;
				totalBlend += assoc->blendAmount;
			}else
				updateData.foobar = 1;
		}
	}
	updateData.nodes[i] = nil;

#ifdef PED_SKIN
	if(IsClumpSkinned(clump))
		clumpData->ForAllFrames(FrameUpdateCallBackSkinned, &updateData);
	else
#endif
		clumpData->ForAllFrames(FrameUpdateCallBackNonSkinned, &updateData);

	for(link = clumpData->link.next; link; link = link->next){
		CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link);
		float relSpeed = totalLength == 0.0f ? 1.0f : totalBlend/totalLength;
		assoc->UpdateTime(timeDelta, relSpeed);
	}
	RwFrameUpdateObjects(RpClumpGetFrame(clump));
}


code:
#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);
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 RpAnimBlendClumpUpdateAnimations(RpClump* clump, float timeDelta);

extern CAnimBlendClumpData *gpAnimBlendClump;
void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg);
void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg);


code:
#include "common.h"

#include "DMAudio.h"
#include "Entity.h"
#include "AudioCollision.h"
#include "AudioManager.h"
#include "AudioSamples.h"
#include "SurfaceTable.h"
#include "sampman.h"

void
cAudioManager::ReportCollision(CEntity *entity1, CEntity *entity2, uint8 surface1, uint8 surface2, float collisionPower,
                               float velocity)
{
	float distSquared;
	CVector v1;
	CVector v2;

	if(!m_bIsInitialised || m_nCollisionEntity < 0 || m_bIsPaused ||
	   (velocity < 0.0016f && collisionPower < 0.01f))
		return;

	if(entity1->IsBuilding()) {
		v1 = v2 = entity2->GetPosition();
	} else if(entity2->IsBuilding()) {
		v1 = v2 = entity1->GetPosition();
	} else {
		v1 = entity1->GetPosition();
		v2 = entity2->GetPosition();
	}
	CVector pos = (v1 + v2) * 0.5f;
	distSquared = GetDistanceSquared(pos);
	if(distSquared < SQR(COLLISION_MAX_DIST)) {
		m_sCollisionManager.m_sQueue.m_pEntity1 = entity1;
		m_sCollisionManager.m_sQueue.m_pEntity2 = entity2;
		m_sCollisionManager.m_sQueue.m_bSurface1 = surface1;
		m_sCollisionManager.m_sQueue.m_bSurface2 = surface2;
		m_sCollisionManager.m_sQueue.m_fIntensity1 = collisionPower;
		m_sCollisionManager.m_sQueue.m_fIntensity2 = velocity;
		m_sCollisionManager.m_sQueue.m_vecPosition = pos;
		m_sCollisionManager.m_sQueue.m_fDistance = distSquared;
		m_sCollisionManager.AddCollisionToRequestedQueue();
	}
}

void
cAudioCollisionManager::AddCollisionToRequestedQueue()
{
	uint32 collisionsIndex;
	uint32 i;

	if (m_bCollisionsInQueue < NUMAUDIOCOLLISIONS)
		collisionsIndex = m_bCollisionsInQueue++;
	else {
		collisionsIndex = m_bIndicesTable[NUMAUDIOCOLLISIONS - 1];
		if (m_sQueue.m_fDistance >= m_asCollisions1[collisionsIndex].m_fDistance) return;
	}

	m_asCollisions1[collisionsIndex] = m_sQueue;

	i = 0;
	if(collisionsIndex) {
		while(m_asCollisions1[m_bIndicesTable[i]].m_fDistance <= m_asCollisions1[collisionsIndex].m_fDistance) {
			if(++i >= collisionsIndex) {
				m_bIndicesTable[i] = collisionsIndex;
				return;
			}
		}
		memmove(&m_bIndicesTable[i + 1], &m_bIndicesTable[i], NUMAUDIOCOLLISIONS - 1 - i);
	}
	m_bIndicesTable[i] = collisionsIndex;
}

void
cAudioManager::ServiceCollisions()
{
	int i, j;
	bool8 abRepeatedCollision1[NUMAUDIOCOLLISIONS];
	bool8 abRepeatedCollision2[NUMAUDIOCOLLISIONS];

	m_sQueueSample.m_nEntityIndex = m_nCollisionEntity;

	for (int i = 0; i < NUMAUDIOCOLLISIONS; i++)
		abRepeatedCollision1[i] = abRepeatedCollision2[i] = FALSE;

	for (i = 0; i < m_sCollisionManager.m_bCollisionsInQueue; i++) {
		for (j = 0; j < NUMAUDIOCOLLISIONS; j++) {
			int index = m_sCollisionManager.m_bIndicesTable[i];
			if ((m_sCollisionManager.m_asCollisions1[index].m_pEntity1 == m_sCollisionManager.m_asCollisions2[j].m_pEntity1)
				&& (m_sCollisionManager.m_asCollisions1[index].m_pEntity2 == m_sCollisionManager.m_asCollisions2[j].m_pEntity2)
				&& (m_sCollisionManager.m_asCollisions1[index].m_bSurface1 == m_sCollisionManager.m_asCollisions2[j].m_bSurface1)
				&& (m_sCollisionManager.m_asCollisions1[index].m_bSurface2 == m_sCollisionManager.m_asCollisions2[j].m_bSurface2)
				) {
				abRepeatedCollision1[index] = TRUE;
				abRepeatedCollision2[j] = TRUE;
				m_sCollisionManager.m_asCollisions1[index].m_nBaseVolume = ++m_sCollisionManager.m_asCollisions2[j].m_nBaseVolume;
				SetUpLoopingCollisionSound(m_sCollisionManager.m_asCollisions1[index], j);
				break;
			}
		}
	}

	for (i = 0; i < NUMAUDIOCOLLISIONS; i++) {
		if (!abRepeatedCollision2[i]) {
			m_sCollisionManager.m_asCollisions2[i].m_pEntity1 = nil;
			m_sCollisionManager.m_asCollisions2[i].m_pEntity2 = nil;
			m_sCollisionManager.m_asCollisions2[i].m_bSurface1 = SURFACE_DEFAULT;
			m_sCollisionManager.m_asCollisions2[i].m_bSurface2 = SURFACE_DEFAULT;
			m_sCollisionManager.m_asCollisions2[i].m_fIntensity2 = 0.0f;
			m_sCollisionManager.m_asCollisions2[i].m_fIntensity1 = 0.0f;
			m_sCollisionManager.m_asCollisions2[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f);
			m_sCollisionManager.m_asCollisions2[i].m_fDistance = 0.0f;
		}
	}

	for (i = 0; i < m_sCollisionManager.m_bCollisionsInQueue; i++) {
		int index = m_sCollisionManager.m_bIndicesTable[i];
		if (!abRepeatedCollision1[index]) {
			for (j = 0; j < NUMAUDIOCOLLISIONS; j++) {
				if (!abRepeatedCollision2[j]) {
					m_sCollisionManager.m_asCollisions2[j].m_nBaseVolume = 1;
					m_sCollisionManager.m_asCollisions2[j].m_pEntity1 = m_sCollisionManager.m_asCollisions1[index].m_pEntity1;
					m_sCollisionManager.m_asCollisions2[j].m_pEntity2 = m_sCollisionManager.m_asCollisions1[index].m_pEntity2;
					m_sCollisionManager.m_asCollisions2[j].m_bSurface1 = m_sCollisionManager.m_asCollisions1[index].m_bSurface1;
					m_sCollisionManager.m_asCollisions2[j].m_bSurface2 = m_sCollisionManager.m_asCollisions1[index].m_bSurface2;
					break;
				}
			}
			SetUpOneShotCollisionSound(m_sCollisionManager.m_asCollisions1[index]);
			SetUpLoopingCollisionSound(m_sCollisionManager.m_asCollisions1[index], j);
		}
	}

	for (int i = 0; i < NUMAUDIOCOLLISIONS; i++)
		m_sCollisionManager.m_bIndicesTable[i] = NUMAUDIOCOLLISIONS;
	m_sCollisionManager.m_bCollisionsInQueue = 0;
}

static const uint32 gOneShotCol[] = {SFX_COL_TARMAC_1,
                                    SFX_COL_TARMAC_1,
                                    SFX_COL_GRASS_1,
                                    SFX_COL_GRAVEL_1,
                                    SFX_COL_MUD_1,
                                    SFX_COL_TARMAC_1,
                                    SFX_COL_CAR_1,
                                    SFX_COL_GRASS_1,
                                    SFX_COL_SCAFFOLD_POLE_1,
                                    SFX_COL_GARAGE_DOOR_1,
                                    SFX_COL_CAR_PANEL_1,
                                    SFX_COL_THICK_METAL_PLATE_1,
                                    SFX_COL_SCAFFOLD_POLE_1,
                                    SFX_COL_LAMP_POST_1,
                                    SFX_COL_HYDRANT_1,
                                    SFX_COL_HYDRANT_1,
                                    SFX_COL_METAL_CHAIN_FENCE_1,
                                    SFX_COL_PED_1,
                                    SFX_COL_SAND_1,
                                    SFX_SPLASH_1,
                                    SFX_COL_WOOD_CRATES_1,
                                    SFX_COL_WOOD_BENCH_1,
                                    SFX_COL_WOOD_SOLID_1,
                                    SFX_COL_GRASS_1,
                                    SFX_COL_GRASS_1,
                                    SFX_COL_VEG_1,
                                    SFX_COL_TARMAC_1,
                                    SFX_COL_CONTAINER_1,
                                    SFX_COL_NEWS_VENDOR_1,
                                    SFX_TYRE_BUMP,
                                    SFX_COL_CARDBOARD_1,
                                    SFX_COL_TARMAC_1,
                                    SFX_COL_GATE};

void
cAudioManager::SetUpOneShotCollisionSound(const cAudioCollision &col)
{
	uint16 s1;
	uint16 s2;

	uint32 emittingVol;
	float ratio;

	static uint16 counter = 28;

	for(int32 i = 0; i < 2; i++) {
		if(i) {
			s1 = col.m_bSurface2;
			s2 = col.m_bSurface1;
		} else {
			s1 = col.m_bSurface1;
			s2 = col.m_bSurface2;
		}
		ratio = GetCollisionOneShotRatio(s1, col.m_fIntensity1);
		if(s1 == SURFACE_CAR && s2 == SURFACE_PED) ratio /= 4.0f;
		if(s1 == SURFACE_CAR && ratio < 0.6f) {
			s1 = SURFACE_CAR_PANEL;
			ratio = Min(1.f, 2.f * ratio);
		}
		emittingVol = 40 * ratio;
		if(emittingVol) {
			m_sQueueSample.m_fDistance = Sqrt(col.m_fDistance);
			m_sQueueSample.m_nVolume =
			    ComputeVolume(emittingVol, COLLISION_MAX_DIST, m_sQueueSample.m_fDistance);
			if(m_sQueueSample.m_nVolume > 0) {
				m_sQueueSample.m_nSampleIndex = gOneShotCol[s1];
				switch(m_sQueueSample.m_nSampleIndex) {
				case SFX_COL_TARMAC_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 5;
					break;
				case SFX_COL_CAR_PANEL_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[0] % 6;
					break;
				case SFX_COL_LAMP_POST_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 2;
					break;
				case SFX_COL_METAL_CHAIN_FENCE_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 4;
					break;
				case SFX_COL_PED_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[4] % 5;
					break;
				case SFX_COL_WOOD_CRATES_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[4] % 4;
					break;
				case SFX_COL_WOOD_BENCH_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 4;
					break;
				case SFX_COL_VEG_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[2] % 5;
					break;
				case SFX_COL_NEWS_VENDOR_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[2] % 3;
					break;
				case SFX_COL_CAR_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 5;
					break;
				case SFX_COL_CARDBOARD_1:
					m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 2;
					break;
				default: break;
				}
				switch(s1) {
				case SURFACE_GLASS: m_sQueueSample.m_nFrequency = 13500; break;
				case SURFACE_GIRDER: m_sQueueSample.m_nFrequency = 8819; break;
				case SURFACE_WATER:
					m_sQueueSample.m_nFrequency =
					    2 * SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex);
					break;
				case SURFACE_RUBBER: m_sQueueSample.m_nFrequency = 6000; break;
				case SURFACE_PLASTIC: m_sQueueSample.m_nFrequency = 8000; break;
				default:
					m_sQueueSample.m_nFrequency =
					    SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex);
					break;
				}
				m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 16);
				m_sQueueSample.m_nCounter = counter++;
				if(counter >= 255) counter = 28;
				m_sQueueSample.m_vecPos = col.m_vecPosition;
				m_sQueueSample.m_nBankIndex = SFX_BANK_0;
				m_sQueueSample.m_bIs2D = FALSE;
				m_sQueueSample.m_nPriority = 11;
				m_sQueueSample.m_nLoopCount = 1;
				SET_EMITTING_VOLUME(emittingVol);
				RESET_LOOP_OFFSETS
				m_sQueueSample.m_fSpeedMultiplier = 4.0f;
				m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST;
				m_sQueueSample.m_bStatic = TRUE;
				m_sQueueSample.m_bReverb = TRUE;
				SET_SOUND_REFLECTION(FALSE);
				AddSampleToRequestedQueue();
			}
		}
	}
}

void
cAudioManager::SetUpLoopingCollisionSound(const cAudioCollision &col, uint8 counter)
{
	if(col.m_fIntensity2 > 0.0016f) {
		uint8 emittingVol = SetLoopingCollisionRequestedSfxFreqAndGetVol(col);
		if(emittingVol) {
			m_sQueueSample.m_fDistance = Sqrt(col.m_fDistance);
			m_sQueueSample.m_nVolume =
			    ComputeVolume(emittingVol, COLLISION_MAX_DIST, m_sQueueSample.m_fDistance);
			if(m_sQueueSample.m_nVolume > 0) {
				m_sQueueSample.m_nCounter = counter;
				m_sQueueSample.m_vecPos = col.m_vecPosition;
				m_sQueueSample.m_nBankIndex = SFX_BANK_0;
				m_sQueueSample.m_bIs2D = FALSE;
				m_sQueueSample.m_nPriority = 7;
				m_sQueueSample.m_nLoopCount = 0;
				SET_EMITTING_VOLUME(emittingVol);
				SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex);
				m_sQueueSample.m_fSpeedMultiplier = 4.0f;
				m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST;
				m_sQueueSample.m_bStatic = FALSE;
				m_sQueueSample.m_nFramesToPlay = 5;
				m_sQueueSample.m_bReverb = TRUE;
				SET_SOUND_REFLECTION(FALSE);
				AddSampleToRequestedQueue();
			}
		}
	}
}

uint32
cAudioManager::SetLoopingCollisionRequestedSfxFreqAndGetVol(const cAudioCollision &audioCollision)
{
	uint8 surface1 = audioCollision.m_bSurface1;
	uint8 surface2 = audioCollision.m_bSurface2;
	int32 vol;
	float ratio;

	if(surface1 == SURFACE_GRASS || surface2 == SURFACE_GRASS || surface1 == SURFACE_HEDGE ||
	   surface2 == SURFACE_HEDGE) {
		ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f);
		m_sQueueSample.m_nSampleIndex = SFX_RAIN;
		m_sQueueSample.m_nFrequency = 13000.f * ratio + 35000;
		vol = 50.f * ratio;
	} else if(surface1 == SURFACE_WATER || surface2 == SURFACE_WATER) {
		ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f);
		m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP;
		m_sQueueSample.m_nFrequency = 6050.f * ratio + 16000;
		vol = 30.f * ratio;
	} else if(surface1 == SURFACE_GRAVEL || surface2 == SURFACE_GRAVEL || surface1 == SURFACE_MUD_DRY ||
		surface2 == SURFACE_MUD_DRY || surface1 == SURFACE_SAND || surface2 == SURFACE_SAND) {
		ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f);
		m_sQueueSample.m_nSampleIndex = SFX_GRAVEL_SKID;
		m_sQueueSample.m_nFrequency = 6000.f * ratio + 10000;
		vol = 50.f * ratio;
	} else if(surface1 == SURFACE_PED || surface2 == SURFACE_PED) {
		return 0;
	} else {
		ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f);
		m_sQueueSample.m_nSampleIndex = SFX_SCRAPE_CAR_1;
		m_sQueueSample.m_nFrequency = 10000.f * ratio + 10000;
		vol = 40.f * ratio;
	}
	if(audioCollision.m_nBaseVolume < 2) vol = audioCollision.m_nBaseVolume * vol / 2;
	return vol;
}

float
cAudioManager::GetCollisionOneShotRatio(uint32 a, float b)
{
	switch(a) {
	case SURFACE_DEFAULT:
	case SURFACE_TARMAC:
	case SURFACE_PAVEMENT:
	case SURFACE_STEEP_CLIFF:
	case SURFACE_TRANSPARENT_STONE: return GetCollisionRatio(b, 10.f, 60.f, 50.f);
	case SURFACE_GRASS:
	case SURFACE_CARDBOARDBOX:
	case SURFACE_GRAVEL:
	case SURFACE_MUD_DRY: return GetCollisionRatio(b, 0.f, 2.f, 2.f);
	case SURFACE_CAR: return GetCollisionRatio(b, 6.f, 50.f, 44.f);
	case SURFACE_GLASS:
	case SURFACE_METAL_CHAIN_FENCE: return GetCollisionRatio(b, 0.1f, 10.f, 9.9f);
	case SURFACE_TRANSPARENT_CLOTH:
	case SURFACE_THICK_METAL_PLATE: return GetCollisionRatio(b, 30.f, 130.f, 100.f);
	case SURFACE_GARAGE_DOOR: return GetCollisionRatio(b, 20.f, 100.f, 80.f);
	case SURFACE_CAR_PANEL: return GetCollisionRatio(b, 0.f, 4.f, 4.f);
	case SURFACE_SCAFFOLD_POLE:
	case SURFACE_METAL_GATE:
	case SURFACE_LAMP_POST: return GetCollisionRatio(b, 1.f, 10.f, 9.f);
	case SURFACE_FIRE_HYDRANT: return GetCollisionRatio(b, 1.f, 15.f, 14.f);
	case SURFACE_GIRDER: return GetCollisionRatio(b, 8.f, 50.f, 42.f);
	case SURFACE_PED: return GetCollisionRatio(b, 0.f, 20.f, 20.f);
	case SURFACE_SAND:
	case SURFACE_WATER:
	case SURFACE_RUBBER:
	case SURFACE_WHEELBASE: return GetCollisionRatio(b, 0.f, 10.f, 10.f);
	case SURFACE_WOOD_CRATES: return GetCollisionRatio(b, 1.f, 4.f, 3.f);
	case SURFACE_WOOD_BENCH: return GetCollisionRatio(b, 0.1f, 5.f, 4.9f);
	case SURFACE_WOOD_SOLID: return GetCollisionRatio(b, 0.1f, 40.f, 39.9f);
	case SURFACE_PLASTIC: return GetCollisionRatio(b, 0.1f, 4.f, 3.9f);
	case SURFACE_HEDGE: return GetCollisionRatio(b, 0.f, 0.5f, 0.5f);
	case SURFACE_CONTAINER: return GetCollisionRatio(b, 4.f, 40.f, 36.f);
	case SURFACE_NEWS_VENDOR: return GetCollisionRatio(b, 0.f, 5.f, 5.f);
	default: break;
	}

	return 0.f;
}

float
cAudioManager::GetCollisionLoopingRatio(uint32 a, uint32 b, float c)
{
	return GetCollisionRatio(c, 0.0f, 0.02f, 0.02f);
}

float
cAudioManager::GetCollisionRatio(float a, float b, float c, float d)
{
	float e;
	e = a;
	if(a <= b) return 0.0f;
	if(c <= a) e = c;
	return (e - b) / d;
}


code:
#pragma once

#define NUMAUDIOCOLLISIONS 10

class CEntity;

class cAudioCollision
{
public:
	CEntity *m_pEntity1;
	CEntity *m_pEntity2;
	uint8 m_bSurface1;
	uint8 m_bSurface2;
	float m_fIntensity1;
	float m_fIntensity2;
	CVector m_vecPosition;
	float m_fDistance;
	int32 m_nBaseVolume;

	cAudioCollision() { Reset(); }

	void Reset()
	{
		m_pEntity1 = nil;
		m_pEntity2 = nil;
		m_bSurface1 = 0;
		m_bSurface2 = 0;
		m_fIntensity1 = m_fIntensity2 = 0.0f;
		m_vecPosition = CVector(0.0f, 0.0f, 0.0f);
		m_fDistance = 0.0f;
	}
};

VALIDATE_SIZE(cAudioCollision, 40);

class cAudioCollisionManager
{
public:
	cAudioCollision m_asCollisions1[NUMAUDIOCOLLISIONS];
	cAudioCollision m_asCollisions2[NUMAUDIOCOLLISIONS];
	uint8 m_bIndicesTable[NUMAUDIOCOLLISIONS];
	uint8 m_bCollisionsInQueue;
	cAudioCollision m_sQueue;

	cAudioCollisionManager()
	{
		m_sQueue.Reset();

		for(int i = 0; i < NUMAUDIOCOLLISIONS; i++)
			m_bIndicesTable[i] = NUMAUDIOCOLLISIONS;

		m_bCollisionsInQueue = 0;
	}
	void AddCollisionToRequestedQueue();
};

VALIDATE_SIZE(cAudioCollisionManager, 852);