/******************************************************************
  
  Module:  animcontroller.cpp
  
  Author: Sean Craig
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "animcontroller.h"
#include "gameobj.h"
#include "registry.h"
#include "animdefs.h"
#include "titan.h"

#include "graphic.h"
#include "SyScene.h"
#include "SyCSprite.h"
#include "tinyxml.h"
#include "nameid.h"
#include "stats.h"
#include "physics.h"
#include "tuning.h"
#include "netpacket.h"
#include "TitanPeeringNetwork.h"
#include "database.h"
#include "spell.h"
#include "areaeffect.h"
#include "intel.h"
#include "cameracontroller.h"
#include "debugoverlay.h"
#include "inventory.h"
#include "gameerror.h"

//---------------------------------------------- Class Declarations

#define ANIM_HANDLE_NONE (-1)
     
static const float ON_GROUND_HEIGHT = 0.001f;
static const float ON_GROUND_VEL = 0.001f;

class cAnimState;
class cAnimCharController : public cAnimCharControllerInterface
{
public:

  cAnimCharController();
  virtual ~cAnimCharController();
  virtual void            Init(cGameObject *owner);
  virtual void            Reset();
  virtual void            Exit();
  virtual void            Update(float time);
  virtual cGameObject *   GetOwner(){return mOwner;};
  virtual bool            CheckForDelete();
  virtual void            EnterScope(); // called when a character enters scope (inside simulation bubble)
  virtual void            ExitScope(); // called when an object leaves scope (outside simulation bubble)

  bool                    ChangeState(eAnimState state);
  float                   GetStateTime(){return mStateTime;};
  void                    ClearStateTime(){mStateTime = 0.0f;};

  void                    NetworkSendBroadcast(eAnimState state);
  virtual void            NetworkReceiveBroadcast(const char *state, int statelen);

  void                    PlayImpact(tGameObjectID id, float recoveryTime);
  void                    ExtractImpact(); // before and after a transition...
  void                    InsertImpact(); 
  void                    ClearImpact();
  int                     GetImpactIndex(){return mImpactIndex;};
  void                    UpdateImpactBlend();


  virtual float           GetDamageMultiplier(eComboType combo);
  virtual float           GetStrikethroughMultiplier(eComboType combo);
  virtual float           GetTargetRecoveryTime(eComboType combo);
  virtual float           GetRange(eComboType combo);

  virtual eAnimState      GetAnimState() const {return mCurState;};
          eAnimState      GetPrevAnimState() const {return mPrevState;};
  virtual const char *    GetAnimStateName() const;
  virtual const char *    GetAnimName(int32 animID) const;
  virtual int             GetContactNode() const;

  virtual bool            IsAnimStateActive();
  virtual int             GetHitPriority();

  virtual bool            IsDodging();

  virtual float           GetAnimTime(){return mStateTime;};
  virtual float           GetActionFrameTime();
  virtual float           GetAnimDuration();
  virtual float           GetAnimSpeed();
  virtual tGameID         GetComboSpellID(eComboType combo);
  virtual tGameObjectID   GetCarriedObject();
  virtual void            RefreshTargeting(float heading, float pitch, float distance);

  void                    SetCombat(); // something has happened which makes us go into combat anim
  bool                    GetCombat();

  tAnimSetID              GetAnimSet() { return mAnimSetID; }
  virtual int32           GetAnimHandle(tAnimIDValues animNum);

  // tuning vars
  static float smActionTurnThreshold;
  static float smActionDefaultTurnSpeed;

  static void RegisterTuningVariables();
protected:
  void                    CheckAnims();
  void                    CheckAnim(int32 id);

  cGameObject *           mOwner;
  cAnimState *            mStates[NUM_ANIM_STATES]; 
  eAnimState              mCurState;
  eAnimState              mPrevState;
  int32                   mPrevSlot; // current slot playing; used to detect if an anim is finished
  float                   mStateTime; // current amount of time the controller has been in this state
  int32                   mCurAbility; // cur ability being performed
  tAnimSetID              mAnimSetID; // action frame info, etc...

  bool                    mbCombat; // if true, we're in our combat idle
  float                   mCombatTime; // time we've been in a noncombat state

  int                     mImpactIndex;
  float                   mImpactTime;
  float32                 mImpactFrame;
  tAnimIDValues           mImpactAnimID; 

  static float            smImpactBlendMaxBlend; // max percentage of blending
  static float            smImpactBlendAttack;  // amount of time it takes to acheive max blend
  static float            smImpactBlendSustain; // amount of time we sustain max blend
  static float            smImpactBlendRelease; // amount of time it takes to go from max blend to no blend
};  


typedef enum
{
  AT_SINGLE,
  AT_ARC,
  AT_EXPLOSION,
  AT_CHARGE,
  NUM_ATTACK_TYPES
} eAttackType;


typedef enum
{
  AE_NONE,
  AE_KNOCKBACK,
  AE_STUN,
  NUM_ATTACK_EFFECTS
} eAttackEffect;


class cAnimControllerSetting
{
public:

  cAnimControllerSetting();

  cNameID mAnimSet;
  eAnimState mAnim;

  float   mDamageMultiplier;
  float   mStrikethroughMultiplier;

  float   mActionFrame;  // in secs
  float   mActionFrameEnd;  // in secs

  float   mSpeed;
  float   mRange;

  float   mComboInputStart; // at what percentage of the anim we will allow a button press to combo
  float   mComboAnimStart;  // If we try to combo before this percentage, the comboed anim will actually play on this frame
  float   mComboEnd;

  float   mNoInterruptStart; // before and after these points we can still block, jump etc.
  float   mNoInterruptEnd;  

  float   mAllowMovementFrame;  // we can run again after this frame of the animation

  float   mTurnSpeed;           // how fast we can pivot during anim
  float   mTargetRecoveryTime;  // how long target takes to recover from attack
  float   mIntroBlend;

  int     mHitPriority;

  tGameID mSpellID;
  SyRefID mContactNode;

  tGameID mWeaponFXID;
  float   mWeaponFXStart;
  float   mWeaponFXEnd;

  float   mCameraShakeFrame;
  float   mCameraShakeAmount;
  float   mCameraShakeTime;
};

class cAnimState
{
public:
  cAnimState() ;
  virtual ~cAnimState(){};

  virtual void Init(cAnimCharController *owner){mOwner = owner;};

  virtual bool AcceptTransition(){return true;}
  virtual void Enter(){};
  virtual bool Update(float time)=0;
  virtual void Exit(){};
  virtual bool Transitions(){return false;};
  virtual bool CheckForDelete(){return false;};
  virtual void EnterScope() {}
  virtual void ExitScope() {}

  virtual void CommonActions(); // take potions, pick up items
  virtual bool IsCombat() const {return true;}
  virtual bool IsActive() {return false;}
  virtual cAnimControllerSetting* GetSetting();
  virtual void RefreshTargeting(float heading, float pitch, float distance) {}

  float GetDuration() const {return mAnimDuration;}
  float GetSpeed();
  float GetActionFrame();

  virtual tGameObjectID GetCarriedObject() {return ID_NONE;}
protected:

          void ActivateObject();
          int  GetTurnAnim(float delta);
          bool TurnAnimsCompatable(int playing_anim,int next_anim);

  cGameObject *GetGameObject();
  SyCSprite   *GetSprite();
  SyScene     *GetScene();
  cAnimCharControllerInput *GetInput();

  bool         PlayAnim(int32 animid, float speedMult = 1.0f, bool bForceShortBlend = false);
  float        TurnTo(float32 angle,float32 turnSpeed, float32 threshold);
  void         ClearInput();

  virtual bool UpdateHitReactionStateChanges();
  virtual bool UpdateActions(float time);
  virtual void UpdatePivot(float time, bool bTurnExact = false);

  cAnimCharController *mOwner;
  int32           mPlaybackIndex; // for our animation...
  int32           mBlendIndex; 
  bool            mTurning;
  bool            mPlayingTurnAnim;
  float           mAnimDuration;
};


class cAS_Stand : public cAnimState
{
public:

  cAS_Stand(int32 animID);
  virtual ~cAS_Stand(){};
  virtual void Enter();
  virtual bool Update(float time);
  static void RegisterTuningVariables();
  virtual bool IsCombat() const {return false;}
public:

  void PlayTurnAnim();
  static float   smTurnSpeed; // if delta angle is greater than this, turn.
  static float   smTurnThreshold; // if delta angle is greater than this, turn.
  static float   smTurnSpeedMultiplier;
         float   mTurnTo;
         float   mTurnSpeed;
         int32   mAnimID;
};

class cAS_StandWithProp : public cAS_Stand
{
public:
  cAS_StandWithProp() : cAS_Stand(ANIM_STAND_HOLD_PROP), mPropID(ID_NONE) {}

  virtual void Enter();
  virtual bool Update(float time);

  virtual tGameObjectID GetCarriedObject() {return mPropID;}

protected:
  virtual bool UpdateActions(float time);

  tGameObjectID mPropID;
};


class cAS_Fidget : public cAS_Stand
{
public:

  cAS_Fidget(int32 animID1,int32 animID2, int32 animID3) : cAS_Stand(animID1),mAnimID2(animID2),mAnimID3(animID3){} ;
  virtual ~cAS_Fidget(){};
  virtual bool AcceptTransition();
  virtual void Enter();
  virtual bool Update(float time);

private:
         int32   mAnimID2;
         int32   mAnimID3;
         int32   mPlayingAnimID;
};


class cAS_Running : public cAnimState
{
public:
  cAS_Running(int32 walkAnimID=ANIM_B_MOVE_F_WALK, int32 runAnimID=ANIM_B_MOVE_F_RUN);
  virtual ~cAS_Running(){};
  virtual void Enter();
  virtual bool Update(float time);
  static void RegisterTuningVariables();
  virtual bool IsCombat() const {return false;}

  static float   smTurnSpeed;

protected:
  int32   mWalkPlaybackIndex;
  float   mCurrentBlend;
  int32   mWalkAnimID;
  int32   mRunAnimID;


  static float   smTurnThreshold; // if delta angle is greater than this, turn.
  static float   smStopThreshold; // if delta angle is greater than this, stop before turning.
  static float   smRunSpeed; // how fast to play the run animation at full tilt
  static float   smWalkSpeed; // how fast to play the run animation at full tilt
  static float   smBlendSpeed; // speed at which we change the blend factor
};

class cAS_WalkWithFacing : public cAnimState
{
public:
  cAS_WalkWithFacing();
  virtual ~cAS_WalkWithFacing(){};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();

  virtual bool IsCombat() const {return false;}

protected:
  void    PlayWalkAnims(float faceHeading);

  int32   mPlaybackIndex2;
  int32   mWalkAnimID;
  int32   mWalkAnimID2;
};

class cAS_RunWithProp : public cAS_Running
{
public:
  cAS_RunWithProp() : cAS_Running(ANIM_WALK_HOLD_PROP, ANIM_RUN_HOLD_PROP), mPropID(ID_NONE) {}
  virtual bool Update(float time);
  virtual void Enter();

  virtual tGameObjectID GetCarriedObject() {return mPropID;}

protected:
  virtual bool UpdateActions(float time);

  tGameObjectID mPropID;
};

class cAS_Standard : public cAnimState  // standard animation
{
public:
  cAS_Standard(int32 animID) : mAnimID(animID){};
  virtual ~cAS_Standard(){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
  int32   mAnimID;
};


class cAS_Land : public cAS_Stand  // standard animation
{
public:
  cAS_Land(int32 animID) : cAS_Stand(animID) {};
  virtual ~cAS_Land(){};
  virtual void Enter();
  virtual bool Update(float time);
};

class cAS_HitReact : public cAS_Standard  // standard animation
{
public:
  cAS_HitReact() : cAS_Standard(ANIM_B_IMPACT_FRONT){};
  virtual ~cAS_HitReact(){};
  virtual void Enter();
  virtual bool Update(float time);

  static int32 GetHitAnim(eAnimState prevState, SyCSprite* pSprite, float deltaHeading);
};

class cAS_ThrownByCharacter : public cAS_Standard  // standard animation
{
public:
  cAS_ThrownByCharacter() : cAS_Standard(ANIM_THROWN_BY_CHARACTER){};
  virtual bool Update(float time);
};

class cAS_PickupProp : public cAS_Standard  // standard animation
{
public:
  cAS_PickupProp() : cAS_Standard(ANIM_PICKUP_PROP), mPropID(ID_NONE) {};
  virtual void Enter();
  virtual bool Update(float time);
  virtual bool AcceptTransition();

  virtual tGameObjectID GetCarriedObject() {return mPropID;}

protected:
  virtual bool UpdateActions(float time);


  tGameObjectID mPropID;
};

class cAS_ThrowProp : public cAS_Standard  // standard animation
{
public:
  cAS_ThrowProp() : cAS_Standard(ANIM_THROW_PROP),
                    mPropID(ID_NONE), mTarget(ID_NONE) {};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();

  virtual tGameObjectID GetCarriedObject() {return mPropID;}

protected:
  tGameObjectID mPropID;
  tGameObjectID mTarget;
};

class cAS_ThrowCharacter : public cAS_Standard  // standard animation
{
public:
  cAS_ThrowCharacter() : cAS_Standard(ANIM_THROW_CHARACTER), mThrowee(ID_NONE) {};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();

  static void RegisterTuningVariables();

  virtual tGameObjectID GetCarriedObject() {return mThrowee;}

protected:
  void UpdateConnection();

  static float THROWN_CHARACTER_MIN_SPEED_XZ;
  static float THROWN_CHARACTER_MAX_SPEED_XZ;
  static float THROWN_CHARACTER_MIN_SPEED_Y;
  static float THROWN_CHARACTER_MAX_SPEED_Y;

  tGameObjectID mThrowee;
};

class cAS_PutdownProp : public cAS_Standard  // standard animation
{
public:
  cAS_PutdownProp() : cAS_Standard(ANIM_PUTDOWN_PROP), mPropID(ID_NONE) {};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();

  virtual tGameObjectID GetCarriedObject() {return mPropID;}

protected:
  tGameObjectID mPropID;
};

class cAS_PushProp : public cAnimState 
{
public:
  cAS_PushProp() : cAnimState(), mPropID(ID_NONE), mLastPropLocation(0.0f, 0.0f, 0.0f), mFaceHeading(0.0f), mPushHeading(0.0f), mPushOffset(0.0f) {};
  virtual void Enter();
  virtual bool Update(float time);

  virtual tGameObjectID GetCarriedObject() {return mPropID;}

protected:
  float GetPushAngle(float heading);
  float GetPushOffset(float heading);

  tGameObjectID mPropID;
  SyVect3 mLastPropLocation;
  float mFaceHeading;
  float mPushHeading;
  float mPushOffset;
};

class cAS_Attack : public cAnimState  // standard animation
{
public:                        
  cAS_Attack(int32 animID,eComboType attackIndex) : 
        mAnimID(animID),
        mCastFXHandle(-1),
        mAttackIndex(attackIndex),
        mComboL(false),
        mComboS(false),
        mNPCComboOverride(NUM_COMBOS),
        mLastHandLoc(0.0f, 0.0f, 0.0f),
        mbImpact(false) {};
  virtual ~cAS_Attack(){};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();
  virtual bool AcceptTransition();
  virtual bool IsActive();

  void AttackFrame();

protected:
  eAnimState GetNextCombo_Strong();
  eAnimState GetNextCombo_Light();

  void       ImpactSurface();
  void       PlaySurfaceImpactFX();
  void       GetImpactTestLocation(SyVect3& testLoc);

  int32   mAnimID;
  int32   mCastFXHandle;
  eComboType   mAttackIndex;
  tGameObjectID mTarget;
  float32 mTargetAngle;
  bool    mComboL;
  bool    mComboS;
  eComboType mNPCComboOverride;
  SyVect3         mLastHandLoc;
  bool            mbImpact;
};     

class cAS_Jump : public cAnimState  // standard animation
{
public:
  cAS_Jump(int32 animid) : mAnimID(animid){};
  virtual ~cAS_Jump(){};
  virtual bool AcceptTransition();
  virtual void Enter();
  virtual bool Update(float time);
  static void RegisterTuningVariables();

public:
  static  float      smJumpUp;
  static  float      smJumpSpeed;

  SyVect3            mJumpVel;
  int32              mAnimID;
};

class cAS_InAir : public cAnimState  // standard animation
{
public:
  cAS_InAir(int32 animid) : mRising(false), mFloating(false), mAnimID(animid), mStartHeading(0.0f){};
  virtual ~cAS_InAir(){};
  virtual void Enter();
  virtual bool Update(float time);
  static void RegisterTuningVariables();

  static  float      smJumpControl;
  static  float      smJumpControlMaxAngle;
  static  float      smJumpControlMaxAngleMomentum;
  static  float      smTurnSpeed;

protected:
  bool               mRising;
  bool               mFloating;
  int32              mAnimID;
  float              mStartHeading;
};

class cAS_Death : public cAnimState  // standard animation
{
public:
  cAS_Death() : mKillerID(ID_NONE), mFXHandle(-1), mbOkToDelete(false) {};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();

  virtual void CommonActions() {}; // don't activate anything while dead
  virtual bool CheckForDelete();
  virtual void ExitScope();

  static void RegisterTuningVariables();

protected:
  static float smTuneUnsummomTime;
  static int smTuneMaxCorpses;
  static int smDeadCount;
  static float smOldestCorpseTime;

  tGameObjectID mKillerID;
  int32         mFXHandle;
  bool          mbOkToDelete;
};

class cAS_Block : public cAnimState  // standard animation
{
public:
  cAS_Block(){};
  virtual ~cAS_Block(){};
  virtual void Enter();
  virtual bool Update(float time);
  virtual bool AcceptTransition();
  virtual bool IsActive();
  virtual bool UpdateHitReactionStateChanges();
};

class cAS_RangedAttack : public cAnimState  // standard animation
{
public:                        
  cAS_RangedAttack() : mTarget(ID_NONE), mTargetHeading(0.0f), mTargetPitch(0.0f), mTargetRange(0.0f) {};
  virtual ~cAS_RangedAttack(){};
  virtual void Enter();
  virtual bool Update(float time);
  virtual bool AcceptTransition();
  virtual bool IsActive();
  virtual void RefreshTargeting(float heading, float pitch, float distance);

protected:
  tGameObjectID mTarget;
  float         mTargetHeading;
  float         mTargetPitch;
  float         mTargetRange;
};

class cAS_Override : public cAnimState  // standard animation
{
public:
  cAS_Override() {};
  virtual ~cAS_Override(){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
};

class cAS_Script : public cAnimState  // standard animation
{
public:
  cAS_Script():cAnimState(), mbAllowHitReactions(false) {};
  virtual ~cAS_Script(){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
  bool  mbAllowHitReactions;
};

class cAS_Dodge : public cAnimState  // standard animation
{
public:
  cAS_Dodge(){};
  virtual ~cAS_Dodge(){};
  virtual void Enter();
  virtual bool Update(float time);
  virtual bool IsActive();
};

class cAS_Knocked : public cAnimState  // standard animation
{
public:
  cAS_Knocked(int32 animid) : mAnimID(animid), mKnockdown(true), mGetUpTime(0.0f) {};
  virtual ~cAS_Knocked(){};
  virtual void Enter();
  virtual bool Update(float time);
  virtual bool AcceptTransition();

public:
  int32              mAnimID;
  bool               mKnockdown;
  float              mGetUpTime;
};

class cAS_KnockedInAir : public cAnimState  // standard animation
{
public:
  cAS_KnockedInAir(int32 animid1, int32 animid2) : mAnimID1(animid1), mAnimID2(animid2), mGetUpTime(0.0f){};
  virtual ~cAS_KnockedInAir(){};
  virtual bool AcceptTransition();

  virtual void Enter();
  virtual bool Update(float time);

protected:
  int32              mAnimID1;
  int32              mAnimID2;
  float              mGetUpTime;
};

class cAS_KnockedLand : public cAnimState  // standard animation
{
public:
  cAS_KnockedLand(int32 animid) : mAnimID(animid), mGetUpTime(0.0f){};
  virtual ~cAS_KnockedLand(){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
  int32              mAnimID;
  float              mGetUpTime;
};

class cAS_AttackJump : public cAS_Attack// standard animation
{
public:
  cAS_AttackJump(int32 animid) : cAS_Attack(animid, COMBO_JUMPATTACK), mLastHitTarget(ID_NONE), mStartHeading(0.0f){};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();
  virtual bool AcceptTransition();

  static void RegisterTuningVariables();

protected:

  static float smUpInstant;
  static float smUpButton;
  static float smForward;
  static float smControl;
  static float smDown;

  tGameObjectID mLastHitTarget;
  float         mStartHeading;
};


class cAS_CastSpell : public cAnimState  // standard animation
{
public:                        
  cAS_CastSpell() : mAnimSettingOverride(NUM_ANIM_STATES), mSpellID(0), mTargetID(ID_NONE), mTargetHeading(0.0f), mTargetPitch(0.0f), mTargetRange(0.0f), mCastFXHandle(-1) {};
  virtual void Enter();
  virtual bool Update(float time);
  virtual void Exit();

  virtual bool IsActive();
  virtual cAnimControllerSetting* GetSetting();

  virtual void RefreshTargeting(float heading, float pitch, float distance);

protected:
  int32 GetAnimID();

  void PlayCastFX(const cSpellMaster* pSpell, bool bCastLoopFX = false);

  eAnimState   mAnimSettingOverride;
  tGameID      mSpellID;
  tGameObjectID mTargetID;
  float mTargetHeading;
  float mTargetPitch;
  float mTargetRange;
  int mCastFXHandle;
};     

class cAS_CastSpellLooping : public cAS_CastSpell  // standard animation
{
public:                        
  cAS_CastSpellLooping() : cAS_CastSpell() {};
  virtual void Enter();
  virtual bool Update(float time);

  virtual bool IsActive();
};     

class cAS_Stunned : public cAnimState  // standard animation
{
public:
  cAS_Stunned(){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
};

class cAS_FlipLever : public cAnimState  // standard animation
{
public:
  cAS_FlipLever(){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
  tGameObjectID mLeverID;
};

class cAS_WalkTo : public cAnimState  
{
public:
  cAS_WalkTo(){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
  SyVect3     mDestination;
  float       mHeading;
  float       mSpeed;
};

class cAS_TurnTo : public cAS_Stand  
{
public:
  cAS_TurnTo():cAS_Stand(ANIM_B_STAND_IDLE){};
  virtual void Enter();
  virtual bool Update(float time);

protected:
  SyVect3     mDestination;
  float       mWalkToTurnSpeed;
  float       mWalkToTurnThreshold;
};
//----------------------------------------- Functions Declarations

float GetAngleAdjustment(float delta_angle, float angle_speed);
static inline bool FloatsEqual(float a, float b);
static inline float FramesToSecs(int frame){return ((float)frame)/30.0f;};

//------------------------------------ Static Member Variables (For Tuning Only)

float cAnimCharController::smActionTurnThreshold    = SY_DEG_TO_RAD(10.0f);
float cAnimCharController::smActionDefaultTurnSpeed = SY_DEG_TO_RAD(360.0f);

float cAS_Stand::smTurnThreshold       = SY_DEG_TO_RAD(10.0f);
float cAS_Stand::smTurnSpeedMultiplier = 2.5f;
float cAS_Stand::smTurnSpeed           = 45.0f; // all the way around in a third of a sec...

float cAS_Running::smTurnThreshold = SY_DEG_TO_RAD(10.0f);
float cAS_Running::smTurnSpeed     = SY_DEG_TO_RAD(360 * 3); // all the way around in a third of a sec...
float cAS_Running::smStopThreshold = SY_DEG_TO_RAD(100); 
float cAS_Running::smWalkSpeed     = 0.5f; 
float cAS_Running::smRunSpeed      = 1.0f; 
float cAS_Running::smBlendSpeed    = 5.0f;

float cAS_Jump::smJumpSpeed        = 5.0f; 
float cAS_Jump::smJumpUp           = 6.5f; 
float cAS_InAir::smJumpControl     = 2.0f; 
float cAS_InAir::smJumpControlMaxAngle = 180.0f; 
float cAS_InAir::smJumpControlMaxAngleMomentum = 0.0f; 
float cAS_InAir::smTurnSpeed       = SY_DEG_TO_RAD(360) ;

float cAnimCharController::smImpactBlendMaxBlend = 0.5f; // max percentage of blending
float cAnimCharController::smImpactBlendAttack   = 0.2f;  // amount of time it takes to acheive max blend
float cAnimCharController::smImpactBlendSustain  = 0.2f; // amount of time we sustain max blend
float cAnimCharController::smImpactBlendRelease  = 0.2f; // amount of time it takes to go from max blend to no blend

static const float EPSILON = 0.000001f; // used to see if two float angles or values are close enough to be equal

float l_JumpControlGravity = 6.0f;

const char *l_AnimStatenames[] = 
{
  "Stand",
  "CombatIdle",
  "Run",
  "ComboL",
  "ComboH",
  "ComboLL",
  "ComboLH",                       
  "ComboHL",
  "ComboHH",
  "ComboLLL",
  "ComboLLH",
  "ComboLHL",
  "ComboLHH",
  "ComboHLL",
  "ComboHLH",
  "ComboHHL",
  "ComboHHH",
  "ComboLLLL",
  "RangedAttack",
  "Attack_Jump",
  "Jump",
  "InAir",
  "Land",
  "Death",
  "HitReact",
  "Block",
  "Override",
  "Script",
  "RunJump",
  "RunInAir",
  "RunLand",
  "Dodge",
  "EmoteAnger",
  "Knockback",
  "KnockbackInAir",
  "KnockbackLand",
  "KnockbackGetUp",
  "Knockforward",
  "KnockforwardInAir",
  "KnockforwardLand",
  "KnockforwardGetUp",
  "Fidget",
  "Attack_Jump_Land",
  "CastA",
  "CastB",
  "CastC",
  "CastD",
  "Pickup_Prop",
  "Stand_Carry_Prop",
  "Run_Carry_Prop",
  "Throw_Prop",
  "Throw_Character",
  "Thrown_By_Character",
  "Putdown_Prop",
  "Push_Prop",
  "Stunned",
  "Flip_Lever",
  "Walk_With_Facing",
  "WalkTo",
  "TurnTo"
};
//------------------------------------ Functions Definitions

float AngleDifference(float end, float start )
{
  return AngleNormalize(end-start);
}

float AngleNormalize(float angle)
{
  float result = fmod(angle,SY_DEG_TO_RAD(360.0f));

  // result in range from -180 to +180
  if (result > SY_DEG_TO_RAD(180.0f))
  {
    result -= SY_DEG_TO_RAD(360.0f);
  } 
  else if (result < SY_DEG_TO_RAD(-180.0f))
  {
    result += SY_DEG_TO_RAD(360.0f);
  }

  return result;
}

float GetAngleAdjustment(float delta_angle, float angle_speed)
{
  if (delta_angle >  angle_speed)
  {
    return angle_speed;
  }
  else if (delta_angle < -angle_speed)
  {
    return -angle_speed;
  }
  else return delta_angle;
}

static inline bool FloatsEqual(float a, float b)
{
  float delta = a-b;
  if (delta > -EPSILON && delta < EPSILON)
  {
    return true;
  }
  return false;
}
//------------------------------------ Member Functions Definitions

//------------------------------------ cAnimControllerSys
cAnimControllerSys::cAnimControllerSys()
{
}

cAnimControllerSys::~cAnimControllerSys()
{
  for (int index = mSettings.Begin(); index != mSettings.End();index = mSettings.Next(index))
  {
    delete mSettings(index);
  }
  mSettings.Clear();
}

void
cAnimControllerSys::Init()
{
  Reload();
  RegisterTuningVariables();
}

void
cAnimControllerSys::Reload()
{
  static const int NUM_ANIM_STATENAMES = (sizeof(l_AnimStatenames) / sizeof (l_AnimStatenames[0]));

  const char *l_ContactNodeNames[] = 
  {
    "Head",
    "Chest",
    "Left Foot",
    "Right Foot",
    "Left Hand",
    "Right Hand",
    "Left Shoulder",
    "Right Shoulder",
    "Right Hand Carry",
    "Left Hand Carry",
  };

  //static const float FPS = 30.0f;
  // clear out old settings
  for (int index = mSettings.Begin(); index != mSettings.End();index = mSettings.Next(index))
  {
    delete mSettings(index);
  }
  mSettings.Clear();

  // load up xml file 
	TiXmlDocument doc( "game_assets/design/data/anim.xml" );
	bool loadOkay = doc.LoadFile();

	if ( !loadOkay )
	{
    GAME_ASSERT(ERROR_DESIGN, false, "Unable to open anim.xml file");
    return;
	}
  
	TiXmlNode* node = 0;
  TiXmlNode* data_node;
  int enumVal;
  bool bResult;
  const char* text;
  const char *setName;
  const char *animName;

  node = doc.FirstChild( "dataroot" );
  SyAssertf(node!=NULL,"Unable to find dataroot in animation db file");
	node = node->FirstChild( "Anim" );
	SyAssertf( node!=NULL, "Unable to find Anim node in animation db file" );

  while (node != NULL)
  {
    data_node = node->FirstChild("AnimSet");
    GAME_ASSERT(ERROR_DESIGN, data_node && data_node->FirstChild() && data_node->FirstChild()->Value(), "Missing AnimSet attribute in anim.xml");
    if (!data_node || !data_node->FirstChild() || !data_node->FirstChild()->Value())
    {
      node = node->NextSibling();
      continue;
    }

    cAnimControllerSetting *newsetting = SyNew cAnimControllerSetting; 

    setName = data_node->FirstChild()->Value();
    newsetting->mAnimSet.SetName(setName);

    enumVal = 0;
    data_node = node->FirstChild("Anim");
    GAME_ASSERT(ERROR_DESIGN, data_node && data_node->FirstChild() && data_node->FirstChild()->Value(),"Unable to find Anim attribute in set %s in anim db file", setName);
    animName = data_node->FirstChild()->Value();
    SyAssertf(NUM_ANIM_STATENAMES == NUM_ANIM_STATES,"Forgot to add anim state name?");
    bResult = ChooseEnum(animName,l_AnimStatenames,NUM_ANIM_STATENAMES, enumVal);
    GAME_ASSERT(ERROR_DESIGN, bResult,"Unknown anim state in %s - %s in anim.xml", setName, animName);
    newsetting->mAnim = (eAnimState)enumVal;

    data_node = node->FirstChild("DamageMultiplier");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      GAME_ASSERT(ERROR_DESIGN, data_node!=NULL,"Unable to find DamageMultiplier in %s - %s in anim db file", setName, animName);
      newsetting->mDamageMultiplier = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("StrikethroughMultiplier");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mStrikethroughMultiplier = (float) atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("ActionFrame");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mActionFrame = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("ActionFrameEnd");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mActionFrameEnd = (float)atof(data_node->FirstChild()->Value());
    }
    else
    {
      newsetting->mActionFrameEnd = newsetting->mActionFrame;
    }

    data_node = node->FirstChild("Speed");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mSpeed = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("Range");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mRange = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("ComboInputStart");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mComboInputStart = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("ComboAnimStart");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mComboAnimStart = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("ComboEnd");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mComboEnd = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("NoInterruptStart");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mNoInterruptStart = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("NoInterruptEnd");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mNoInterruptEnd = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("AllowMovementFrame");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mAllowMovementFrame = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("TurnSpeed");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mTurnSpeed = SY_DEG_TO_RAD((float)atof(data_node->FirstChild()->Value()));
    }

    data_node = node->FirstChild("TargetRecoveryTime");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mTargetRecoveryTime = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("IntroBlend");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mIntroBlend = (float)atof(data_node->FirstChild()->Value());
      newsetting->mIntroBlend = SY_MAX(0.0f, newsetting->mIntroBlend);
    }

    data_node = node->FirstChild("Priority");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mHitPriority = atoi(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("Spell");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mSpellID = SyHashResourceID(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("ContactNode");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      enumVal = 0;
      text = data_node->FirstChild()->Value();
      bResult = ChooseEnum(text,l_ContactNodeNames, 10, enumVal);
      GAME_ASSERT(ERROR_DESIGN, bResult,"Unknown contact node type in record %s - %s in anim.xml", setName, animName);
      newsetting->mContactNode = enumVal+1;
    }
    else
    {
      newsetting->mContactNode = CHAR_NODE_RIGHT_HAND_CARRY;
    }

    data_node = node->FirstChild("WeaponFX");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mWeaponFXID = SyHashResourceID(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("WeaponFXStart");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mWeaponFXStart = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("WeaponFXEnd");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mWeaponFXEnd = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("CameraShakeFrame");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mCameraShakeFrame = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("CameraShakeAmount");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mCameraShakeAmount = (float)atof(data_node->FirstChild()->Value());
    }

    data_node = node->FirstChild("CameraShakeTime");
    if (data_node && data_node->FirstChild() && data_node->FirstChild()->Value())
    {
      newsetting->mCameraShakeTime = (float)atof(data_node->FirstChild()->Value());
    }

    GAME_WARN(ERROR_DESIGN, newsetting->mComboInputStart <= 1.0f,"Bad Parameter (ComboInputStart > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mComboInputStart >= 0.0f,"Bad Parameter (ComboInputStart > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mComboAnimStart <= 1.0f,"Bad Parameter (mComboAnimStart > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mComboAnimStart >= 0.0f,"Bad Parameter (mComboAnimStart > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mComboEnd <= 1.0f,"Bad Parameter (ComboEnd > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mComboEnd >= 0.0f,"Bad Parameter (ComboEnd > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mActionFrame <= 1.0f,"Bad Parameter (ActionFrame > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mActionFrame >= 0.0f,"Bad Parameter (ActionFrame > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mActionFrameEnd <= 1.0f,"Bad Parameter (ActionFrameEnd > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mActionFrameEnd >= 0.0f,"Bad Parameter (ActionFrameEnd > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mNoInterruptStart <= 1.0f,"Bad Parameter (NoInterruptStart > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mNoInterruptStart >= 0.0f,"Bad Parameter (NoInterruptStart > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mNoInterruptEnd <= 1.0f,"Bad Parameter (NoInterruptEnd > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mNoInterruptEnd >= 0.0f,"Bad Parameter (NoInterruptEnd > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mAllowMovementFrame <= 1.0f,"Bad Parameter (AllowMovementFrame > 1.0f) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mAllowMovementFrame >= 0.0f,"Bad Parameter (AllowMovementFrame > 0.0f) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, newsetting->mActionFrame <= newsetting->mActionFrameEnd,"Bad Parameter (ActionFrame > ActionFrameEnd) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mActionFrame <= newsetting->mComboEnd,"Bad Parameter (ActionFrameEnd > ComboEnd) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mComboInputStart <= newsetting->mComboEnd,"Bad Parameter (ComboInputStart > ComboEnd) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mComboAnimStart <= newsetting->mComboEnd,"Bad Parameter (ComboAnimStart > ComboEnd) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mComboInputStart <= newsetting->mComboAnimStart,"Bad Parameter (ComboInputStart > ComboAnimStart) for %s - %s in anim.xml", setName, animName);
    GAME_WARN(ERROR_DESIGN, newsetting->mNoInterruptStart <= newsetting->mNoInterruptEnd,"Bad Parameter (NoInterruptStart > NoInterruptEnd) for %s - %s in anim.xml", setName, animName);

    GAME_WARN(ERROR_DESIGN, ID_NONE == newsetting->mWeaponFXID || newsetting->mWeaponFXStart <= newsetting->mWeaponFXEnd, "Bad Parameter (WeaponFXEnd > WeaponFXStart) for %s - %s in anim.xml", setName, animName);

    mSettings.Add(newsetting);
    node = node->NextSibling();
  }
}

cAnimControllerSetting  *
cAnimControllerSys::Fetch(tAnimSetID id,int animstate)
{
  for (int index = 0;index < mSettings.Size();++index)
  {
    if (mSettings(index)->mAnimSet.GetID() == id && mSettings(index)->mAnim == animstate)
    {
      return mSettings(index);
    }
  }
  return NULL;
}

void
cAnimControllerSys::RegisterTuningVariables()
{
  cAnimCharController::RegisterTuningVariables();
  cAS_Stand::RegisterTuningVariables();
  cAS_Running::RegisterTuningVariables();
  cAS_Jump::RegisterTuningVariables();
  cAS_InAir::RegisterTuningVariables();
  cAS_AttackJump::RegisterTuningVariables();
  cAS_ThrowCharacter::RegisterTuningVariables();
  cAS_Death::RegisterTuningVariables();

  gTuningSys.AddFloat(&l_JumpControlGravity,"Jump_ControlGravity");
  
}
//------------------------------------ cAnimControllerInput
cAnimCharControllerInput:: cAnimCharControllerInput() 
{
  Clear();
}

void 
cAnimCharControllerInput::Clear()
{
  mHeadingRequest = 0.0f;
  mSpeedRequest = 0.0f;
  mAttackRequestL = false;
  mAttackRequestS = false;
  mAttackRequestRanged = false;
  mNPCComboOverride = NUM_COMBOS;
  mBlockRequest = false;
  mJumpRequest = false;
  mHitReact = false;
  mDeath = false;
  mHitReactTarget = ID_NONE;
  mHitReactTime = 0.0f;
  mHeight  = 0.0f;
  mYVel = 0.0f;
  mFall = false;
  mActionRequest = false;
  mActionTarget = ID_NONE;
  mTargetHeading = 0.0f;
  mTargetPitch = 0.0f;
  mTargetRange = 20.0f;
  mAnimationOverride = ANIM_HANDLE_NONE; 
  mOverrideCancelEnd = false; 
  mOverrideCancelImmediate = false; 
  mOverrideSetLocation = false; 
  mScriptAnimID = -1;
  mScriptAnimAllowHitReactions = false;
  mDodging = false;
  mEmoteAnger = false;
  mbThrown = false;
  mbKnockback = false;
  mbKnockforward = false;
  mbKnockdown = false;
  mKnockbackVect.X = 0.0f;
  mKnockbackVect.Y = 0.0f;
  mKnockbackVect.Z = 0.0f;
  mKnockbackHeading = 0.0f;
  mCastSpellID = 0;
  mCastAnimSettingOverride = NUM_ANIM_STATES;
  mbKeepFacing = false;
  mFaceHeading = 0.0f;

  mWalkTo = false;
  mWalkToHeading = 0;
  mWalkToDestination(0,0,0);
  mWalkToTurnSpeed = 1.0f;
  mWalkToSpeed = 1.0f;
  mWalkToClear = false;
}

//------------------------------------ cAnimControllerSetting

cAnimControllerSetting::cAnimControllerSetting() :
mAnim(AS_STAND),
mDamageMultiplier(1.0f),
mStrikethroughMultiplier(1.0f),
mActionFrame(0.5f),
mActionFrameEnd(0.5f),
mSpeed(1.0f),
mRange(1.0f),
mComboInputStart(0.0f),
mComboAnimStart(0.5f),
mComboEnd(1.0f),
mNoInterruptStart(0.0f),
mNoInterruptEnd(1.0f),
mAllowMovementFrame(1.0f),
mTurnSpeed(0.0f),
mTargetRecoveryTime(1.0f),
mIntroBlend(0.25f),
mHitPriority(0),
mSpellID(ID_NONE),
mContactNode(0),
mWeaponFXID(ID_NONE),
mWeaponFXStart(0.0f),
mWeaponFXEnd(1.0f),
mCameraShakeFrame(0.0f),
mCameraShakeAmount(0.0f),
mCameraShakeTime(0.0f)
{
}


//------------------------------------ cAnimControllerInterface

cAnimCharControllerInterface:: ~cAnimCharControllerInterface()
{
}

cAnimCharControllerInterface *cAnimCharControllerInterface::Allocate()
{
  return SyNew cAnimCharController;
}

cAnimCharControllerInterface::cAnimCharControllerInterface() 
{
}

bool            
cAnimCharControllerInterface::IsIdle() const
{
  switch(GetAnimState())
  {
    case AS_STAND:
    case AS_STAND_CARRY_PROP:
    case AS_COMBAT_IDLE:
    case AS_RUN:
    case AS_RUN_CARRY_PROP:
    case AS_FIDGET:
      return true;
    default:
      return false;
  }
}

bool            
cAnimCharControllerInterface::IsAttacking() const
{
  switch(GetAnimState())
  {
    case AS_ATTACK_L:
    case AS_ATTACK_H:
    case AS_ATTACK_LL:
    case AS_ATTACK_LH:
    case AS_ATTACK_HL:
    case AS_ATTACK_HH:
    case AS_ATTACK_LLL:
    case AS_ATTACK_LLH:
    case AS_ATTACK_LHL:
    case AS_ATTACK_LHH:
    case AS_ATTACK_HLL:
    case AS_ATTACK_HLH:
    case AS_ATTACK_HHL:
    case AS_ATTACK_HHH:
    case AS_ATTACK_LLLL:
    case AS_ATTACK_RANGED:
    case AS_ATTACK_JUMP:
    case AS_CAST_SPELL_A:
    case AS_CAST_SPELL_B:
    case AS_CAST_SPELL_C:
    case AS_CAST_SPELL_D:
      return true;
    default:
      return false;
  }
}

bool            
cAnimCharControllerInterface::IsCasting()
{
  switch(GetAnimState())
  {
    case AS_CAST_SPELL_A:
    case AS_CAST_SPELL_B:
    case AS_CAST_SPELL_C:
    case AS_CAST_SPELL_D:
      return IsAnimStateActive();
    default:
      return false;
  }
}

bool            
cAnimCharControllerInterface::IsGettingUp()
{
  switch(GetAnimState())
  {
    case AS_KNOCKBACK_LAND:
    case AS_KNOCKFORWARD_LAND:
      return true;
    case AS_KNOCKBACK_GETUP:
    case AS_KNOCKFORWARD_GETUP:
      return IsAnimStateActive();
    default:
      return false;
  }
}

bool            
cAnimCharControllerInterface::IsBlocking()
{
  return GetAnimState() == AS_BLOCK && IsAnimStateActive();
}

bool            
cAnimCharControllerInterface::IsInAir() const
{
  switch(GetAnimState())
  {
    case AS_IN_AIR:
    case AS_RUN_IN_AIR:
    case AS_KNOCKBACK_IN_AIR:
    case AS_KNOCKFORWARD_IN_AIR:
    case AS_ATTACK_JUMP:
    case AS_THROWN_BY_CHARACTER:
      return true;
    default:
      return false;
  }
}

bool            
cAnimCharControllerInterface::IsKnockedInAir() const
{
  switch(GetAnimState())
  {
  case AS_KNOCKBACK_IN_AIR:
  case AS_KNOCKFORWARD_IN_AIR:
  case AS_THROWN_BY_CHARACTER:
    return true;
  default:
    return false;
  }
}

eComboType
cAnimCharControllerInterface::GetComboType()
{
  return GetComboTypeFromAnimState(GetAnimState());
}

eComboType
cAnimCharControllerInterface::GetComboTypeFromAnimState(eAnimState anim)
{
  switch (anim)
  {
    case AS_ATTACK_L: return COMBO_L;
    case AS_ATTACK_H: return COMBO_H;
    case AS_ATTACK_LL: return COMBO_LL;
    case AS_ATTACK_LH: return COMBO_LH;
    case AS_ATTACK_HL: return COMBO_HL;
    case AS_ATTACK_HH: return COMBO_HH;
    case AS_ATTACK_LLL: return COMBO_LLL;
    case AS_ATTACK_LLH: return COMBO_LLH;
    case AS_ATTACK_LHL: return COMBO_LHL;
    case AS_ATTACK_LHH: return COMBO_LHH;
    case AS_ATTACK_HLL: return COMBO_HLL;
    case AS_ATTACK_HLH: return COMBO_HLH;
    case AS_ATTACK_HHL: return COMBO_HHL;
    case AS_ATTACK_HHH: return COMBO_HHH;
    case AS_ATTACK_LLLL: return COMBO_LLLL;
    case AS_ATTACK_RANGED: return COMBO_RANGEDATTACK;
    case AS_ATTACK_JUMP: return COMBO_JUMPATTACK;
    case AS_THROW_CHARACTER: return COMBO_THROWCHARACTER;
    default: return NUM_COMBOS;
  }
}

eAnimState
cAnimCharControllerInterface::GetAnimStateFromComboType(eComboType combo)
{
  switch (combo)
  {
    case COMBO_L: return AS_ATTACK_L;
    case COMBO_H: return AS_ATTACK_H;
    case COMBO_LL: return AS_ATTACK_LL;
    case COMBO_LH: return AS_ATTACK_LH;
    case COMBO_HL: return AS_ATTACK_HL;
    case COMBO_HH: return AS_ATTACK_HH;
    case COMBO_LLL: return AS_ATTACK_LLL;
    case COMBO_LLH: return AS_ATTACK_LLH;
    case COMBO_LHL: return AS_ATTACK_LHL;
    case COMBO_LHH: return AS_ATTACK_LHH;
    case COMBO_HLL: return AS_ATTACK_HLL;
    case COMBO_HLH: return AS_ATTACK_HLH;
    case COMBO_HHL: return AS_ATTACK_HHL;
    case COMBO_HHH: return AS_ATTACK_HHH;
    case COMBO_LLLL: return AS_ATTACK_LLLL;
    case COMBO_RANGEDATTACK: return AS_ATTACK_RANGED;
    case COMBO_JUMPATTACK: return AS_ATTACK_JUMP;
    case COMBO_THROWCHARACTER: return AS_THROW_CHARACTER;
    default: return AS_ATTACK_L;
  }
}

bool
cAnimCharControllerInterface::IsHeavyCombo(eComboType combo)
{
  switch (combo)
  {
    case COMBO_H:
    case COMBO_LH: 
    case COMBO_HH: 
    case COMBO_LLH: 
    case COMBO_LHH: 
    case COMBO_HLH: 
    case COMBO_HHH: 
      return true;

    default:
      return false;
  }
}

bool
cAnimCharControllerInterface::IsLightCombo(eComboType combo)
{
  switch (combo)
  {
    case COMBO_L: 
    case COMBO_LL: 
    case COMBO_HL: 
    case COMBO_LLL:
    case COMBO_LHL:
    case COMBO_HLL:
    case COMBO_HHL:
    case COMBO_LLLL:
       return true;

    default:
      return false;
  }
}


//------------------------------------ cAnimCharController
void
cAnimCharController::RegisterTuningVariables()
{
}


cAnimCharController::cAnimCharController() :
  mOwner(NULL),
  mCurState(AS_STAND),
  mPrevState(AS_STAND),
  mPrevSlot(-1),
  mStateTime(0),
  mCurAbility(-1), // cur ability being performed
  mAnimSetID(NULL),
  mImpactIndex(-1)
{
  mStates[AS_STAND]   = SyNew cAS_Stand(ANIM_B_STAND_IDLE);
  mStates[AS_COMBAT_IDLE]   = SyNew cAS_Stand(ANIM_B_COMBAT_IDLE);
  mStates[AS_RUN] = SyNew cAS_Running;

  mStates[AS_ATTACK_L] = SyNew cAS_Attack(ANIM_ATTACK_L, COMBO_L);
  mStates[AS_ATTACK_H] = SyNew cAS_Attack(ANIM_ATTACK_H, COMBO_H);
  mStates[AS_ATTACK_LL] = SyNew cAS_Attack(ANIM_ATTACK_LL, COMBO_LL);
  mStates[AS_ATTACK_LH] = SyNew cAS_Attack(ANIM_ATTACK_LH, COMBO_LH);
  mStates[AS_ATTACK_HL] = SyNew cAS_Attack(ANIM_ATTACK_HL, COMBO_HL);
  mStates[AS_ATTACK_HH] = SyNew cAS_Attack(ANIM_ATTACK_HH, COMBO_HH);
  mStates[AS_ATTACK_LLL] = SyNew cAS_Attack(ANIM_ATTACK_LLL, COMBO_LLL);
  mStates[AS_ATTACK_LLH] = SyNew cAS_Attack(ANIM_ATTACK_LLH, COMBO_LLH);
  mStates[AS_ATTACK_LHL] = SyNew cAS_Attack(ANIM_ATTACK_LHL, COMBO_LHL);
  mStates[AS_ATTACK_LHH] = SyNew cAS_Attack(ANIM_ATTACK_LHH, COMBO_LHH);
  mStates[AS_ATTACK_HLL] = SyNew cAS_Attack(ANIM_ATTACK_HLL, COMBO_HLL);
  mStates[AS_ATTACK_HLH] = SyNew cAS_Attack(ANIM_ATTACK_HLH, COMBO_HLH);
  mStates[AS_ATTACK_HHL] = SyNew cAS_Attack(ANIM_ATTACK_HHL, COMBO_HHL);
  mStates[AS_ATTACK_HHH] = SyNew cAS_Attack(ANIM_ATTACK_HHH, COMBO_HHH);
  mStates[AS_ATTACK_LLLL] = SyNew cAS_Attack(ANIM_ATTACK_LLLL, COMBO_LLLL);
  mStates[AS_ATTACK_RANGED] = SyNew cAS_RangedAttack;
  mStates[AS_ATTACK_JUMP]   = SyNew cAS_AttackJump(ANIM_ATTACK_JUMP);


  mStates[AS_JUMP] = SyNew cAS_Jump(ANIM_B_JUMP);
  mStates[AS_IN_AIR] = SyNew cAS_InAir(ANIM_B_IN_AIR);
  mStates[AS_LAND] = SyNew cAS_Land(ANIM_B_LAND);
  mStates[AS_DEATH] = SyNew cAS_Death;
  mStates[AS_HITREACT] = SyNew cAS_HitReact();
  mStates[AS_BLOCK] = SyNew cAS_Block;
  mStates[AS_OVERRIDE] = SyNew cAS_Override();
  mStates[AS_SCRIPT] = SyNew cAS_Script();
  mStates[AS_RUN_JUMP] = SyNew cAS_Jump(ANIM_B_RUN_JUMP);
  mStates[AS_RUN_IN_AIR] = SyNew cAS_InAir(ANIM_B_RUN_IN_AIR);
  mStates[AS_RUN_LAND] = SyNew cAS_Land(ANIM_B_RUN_LAND);
  mStates[AS_DODGE] = SyNew cAS_Dodge();
  mStates[AS_EMOTE_ANGER] = SyNew cAS_Standard(ANIM_B_EMOTE_ANGER);
  mStates[AS_KNOCKBACK] = SyNew cAS_Knocked(ANIM_B_KNOCKBACK);
  mStates[AS_KNOCKBACK_IN_AIR] = SyNew cAS_KnockedInAir(ANIM_B_KNOCKBACK_IN_AIR, ANIM_B_KNOCKBACK_IN_AIR2);
  mStates[AS_KNOCKBACK_LAND] = SyNew cAS_KnockedLand(ANIM_B_KNOCKBACK_LAND);
  mStates[AS_KNOCKBACK_GETUP] = SyNew cAS_Standard(ANIM_B_KNOCKBACK_GETUP);

  mStates[AS_KNOCKFORWARD] = SyNew cAS_Knocked(ANIM_B_KNOCKFORWARD);
  mStates[AS_KNOCKFORWARD_IN_AIR] = SyNew cAS_KnockedInAir(ANIM_B_KNOCKFORWARD_IN_AIR, ANIM_B_KNOCKFORWARD_IN_AIR2);
  mStates[AS_KNOCKFORWARD_LAND] = SyNew cAS_KnockedLand(ANIM_B_KNOCKFORWARD_LAND);
  mStates[AS_KNOCKFORWARD_GETUP] = SyNew cAS_Standard(ANIM_B_KNOCKFORWARD_GETUP);
  mStates[AS_FIDGET] = SyNew cAS_Fidget(ANIM_B_STAND_IDLE_BREAK_01,
                                            ANIM_B_STAND_IDLE_BREAK_02,
                                            ANIM_B_STAND_IDLE_BREAK_03);

  mStates[AS_ATTACK_JUMP_LAND] = SyNew cAS_Land(ANIM_ATTACK_JUMP_LAND);
  //mStates[AS_ATTACK_JUMP_LAND] = SyNew cAS_AttackJumpLand(ANIM_ATTACK_JUMP_LAND);

  mStates[AS_CAST_SPELL_A] = SyNew cAS_CastSpell();
  mStates[AS_CAST_SPELL_B] = SyNew cAS_CastSpell();
  mStates[AS_CAST_SPELL_C] = SyNew cAS_CastSpell();
  mStates[AS_CAST_SPELL_D] = SyNew cAS_CastSpellLooping();

  mStates[AS_PICKUP_PROP] = SyNew cAS_PickupProp;
  mStates[AS_STAND_CARRY_PROP] = SyNew cAS_StandWithProp;
  mStates[AS_RUN_CARRY_PROP] = SyNew cAS_RunWithProp;
  mStates[AS_THROW_PROP] = SyNew cAS_ThrowProp;
  mStates[AS_THROW_CHARACTER] = SyNew cAS_ThrowCharacter();
  mStates[AS_THROWN_BY_CHARACTER] = SyNew cAS_ThrownByCharacter();
  mStates[AS_PUTDOWN_PROP] = SyNew cAS_PutdownProp();
  mStates[AS_PUSH_PROP] = SyNew cAS_PushProp();
  mStates[AS_STUNNED] = SyNew cAS_Stunned;
  mStates[AS_FLIP_LEVER] = SyNew cAS_FlipLever;
  mStates[AS_WALK_WITH_FACING] = SyNew cAS_WalkWithFacing;
  mStates[AS_WALKTO] = SyNew cAS_WalkTo;
  mStates[AS_TURNTO] = SyNew cAS_TurnTo;
  
  for (int ii=0;ii<NUM_ANIM_STATES;++ii)
  {
    mStates[ii]->Init(this);
  }
}

cAnimCharController::~cAnimCharController()
{
  for (int ii=0;ii<NUM_ANIM_STATES;++ii)
  {
    delete mStates[ii];
  }
}
           
void
cAnimCharController::Init(cGameObject *owner)
{
  mOwner = owner;
  mAnimSetID = mOwner->GetStats()->GetAnimSet();
  mInput.mHeadingRequest = mOwner->GetHeading();
  mStates[mCurState]->Enter();
  mbCombat = false;
  mCombatTime = 0.0f;
  CheckAnims();
}

void
cAnimCharController::Exit()
{
  mStates[mCurState]->Exit();
}

bool
cAnimCharController::CheckForDelete()
{
  if (mOwner->IsLocal())
  {
    return mStates[mCurState]->CheckForDelete();
  }

  return false;
}

void cAnimCharController::EnterScope()
{
  mStates[mCurState]->EnterScope();
}

void cAnimCharController::ExitScope()
{
  mStates[mCurState]->ExitScope();
}

void            
cAnimCharController::Reset()
{
  mAnimSetID = mOwner->GetStats()->GetAnimSet();
  mStateTime = 0.0f;
  mCurState = AS_STAND;
  mInput.Clear();
  mInput.mHeadingRequest = mOwner->GetHeading();
  mStates[mCurState]->Enter();
}

void                    
cAnimCharController::SetCombat() // something has happened which makes us go into combat anim
{
  mbCombat = true;
  mCombatTime = 0.0f;
}

bool                    
cAnimCharController::GetCombat()
{
  static const float PLAYER_COMBAT_REVERT_TIME  = 3.0f;
  static const float NPC_COMBAT_REVERT_TIME  = 15.0f;

  if (mOwner->GetType() == cGameObject::OBJ_NPC)
  {
    if (mCombatTime > NPC_COMBAT_REVERT_TIME)
    {
      return false;
    }
  }
  else 
  {
    if (mCombatTime > PLAYER_COMBAT_REVERT_TIME)
    {
      return false;
    }
  }

  return mbCombat;
}

// checks to make sure all required animations are present
void
cAnimCharController::CheckAnims()
{
  CheckAnim(ANIM_B_STAND_IDLE);
  CheckAnim(ANIM_B_MOVE_F_WALK);
  CheckAnim(ANIM_B_MOVE_F_RUN);
  CheckAnim(ANIM_B_DEATH);
  CheckAnim(ANIM_B_IMPACT_FRONT);
  CheckAnim(ANIM_B_BLOCK);
  CheckAnim(ANIM_ATTACK_L);
  CheckAnim(ANIM_B_KNOCKFORWARD);
  CheckAnim(ANIM_B_KNOCKFORWARD_IN_AIR);
  CheckAnim(ANIM_B_KNOCKFORWARD_LAND);
  CheckAnim(ANIM_B_KNOCKFORWARD_GETUP);
  CheckAnim(ANIM_B_KNOCKBACK);
  CheckAnim(ANIM_B_KNOCKBACK_IN_AIR);
  CheckAnim(ANIM_B_KNOCKBACK_LAND);
  CheckAnim(ANIM_B_KNOCKBACK_GETUP);

  if (mOwner->GetType()==cGameObject::OBJ_PLAYER)
  {
    CheckAnim(ANIM_B_JUMP);
    CheckAnim(ANIM_B_IN_AIR);
    CheckAnim(ANIM_B_LAND);

    CheckAnim(ANIM_B_RUN_JUMP);
    CheckAnim(ANIM_B_RUN_IN_AIR);
    CheckAnim(ANIM_B_RUN_LAND);

    CheckAnim(ANIM_B_CAST_A);
    CheckAnim(ANIM_ATTACK_H);
    CheckAnim(ANIM_ATTACK_LL);
    CheckAnim(ANIM_ATTACK_LH);
    CheckAnim(ANIM_ATTACK_HL);
    CheckAnim(ANIM_ATTACK_HH);
    CheckAnim(ANIM_ATTACK_LLL);
    CheckAnim(ANIM_ATTACK_LLH);
    CheckAnim(ANIM_ATTACK_LHL);
    CheckAnim(ANIM_ATTACK_LHH);
    CheckAnim(ANIM_ATTACK_HLL);
    CheckAnim(ANIM_ATTACK_HLH);
    CheckAnim(ANIM_ATTACK_HHL);
    CheckAnim(ANIM_ATTACK_HHH);
    CheckAnim(ANIM_ATTACK_JUMP);
//    CheckAnim(ANIM_RANGED_ATTACK);

//    CheckAnim(ANIM_B_TURN_90_L);
//    CheckAnim(ANIM_B_TURN_90_R);
//    CheckAnim(ANIM_B_TURN_180_L);
//    CheckAnim(ANIM_B_TURN_180_R);

    CheckAnim(ANIM_PICKUP_PROP);
    CheckAnim(ANIM_STAND_HOLD_PROP);
    CheckAnim(ANIM_RUN_HOLD_PROP);
    CheckAnim(ANIM_WALK_HOLD_PROP);
    CheckAnim(ANIM_THROW_PROP);
    CheckAnim(ANIM_PUTDOWN_PROP);
    CheckAnim(ANIM_THROW_CHARACTER);

    CheckAnim(ANIM_PUSH_PROP_FWD);
    CheckAnim(ANIM_PUSH_PROP_LEFT);
    CheckAnim(ANIM_PUSH_PROP_RIGHT);
    CheckAnim(ANIM_PULL_PROP_BACK);
    CheckAnim(ANIM_PUSH_PROP_FWDRIGHT);
    CheckAnim(ANIM_PUSH_PROP_FWDLEFT);
    CheckAnim(ANIM_PULL_PROP_BACKRIGHT);
    CheckAnim(ANIM_PULL_PROP_BACKLEFT);
  }
  else if (mOwner->GetType()==cGameObject::OBJ_NPC)
  {
    CheckAnim(ANIM_THROWN_BY_CHARACTER);
  }
}

void
cAnimCharController::CheckAnim(int32 animid)
{
  cGraphicActor *graphic = (cGraphicActor*) mOwner->GetGraphic();
  SyScene *scene = GetOwner()->GetTitan()->GetScene();
  SyCSprite* sprite = (SyCSprite*) scene->GetActorSpritePtr( graphic->GetActorHandle());

  GAME_WARN(ERROR_ART, 
            sprite->HasAnim(animid)!=0,"%s is missing required anim: %d(%s)",
            mOwner->GetName(),
            animid,
            GetAnimName(animid));
}

const char *
cAnimCharController::GetAnimName(int32 animID) const
{
  switch (animID)
  {
    case ANIM_B_STAND_IDLE:
    {
      return "Stand_Idle";
    }
    case ANIM_B_STAND_IDLE_BREAK_01:
    {
      return "Stand_Idle_Break_01";
    }
    case ANIM_B_STAND_IDLE_BREAK_02:
    {
      return "Stand_Idle_Break_02";
    }
    case ANIM_B_STAND_IDLE_BREAK_03:
    {
      return "Stand_Idle_Break_03";
    }
    case ANIM_B_MOVE_F_WALK:
    {
      return "Walk_Fwd";
    }
    case ANIM_B_MOVE_F_RUN:
    {
      return "Walk_Run";
    }
    case ANIM_B_DEATH:
    {
      return "Death";
    }

    case ANIM_B_IMPACT_FRONT:
    {
      return "Stand_TakeDmg_Fwd";
    }
    case ANIM_B_IMPACT_BACK:
    {
      return "Stand_TakeDmg_Bck";
    }
    case ANIM_B_IMPACT_FWD_EXTREME:
    {
      return "Stand_TakeDmg_Fwd_Extreme";
    }
    case ANIM_B_JUMP :
    {
      return "Jump";
    }
    case ANIM_B_IN_AIR:
    {
      return "Jump_In_Air";
    }
    case ANIM_B_LAND:
    {
      return "Jump_Land";
    }
    case ANIM_B_FALLING :
    {
      return "Fall";
    }
    case ANIM_B_LAND_HARD:
    {
      return "Land_Hard";
    }
    case ANIM_B_BLOCK:
    {
      return "Stand_Block";
    }
    case ANIM_B_KNOCKBACK:
    {
      return "Stand_KnockDown_Bck";
    }
    case ANIM_B_KNOCKBACK_IN_AIR :
    {
      return "Air_Knockdown_Bck";
    }
    case ANIM_B_KNOCKBACK_LAND:
    {
      return "Land_Knockdown_Bck";
    }
    case ANIM_B_KNOCKBACK_GETUP:
    {
      return "GetUp_Fwd";
    }

    case ANIM_B_KNOCKFORWARD:
    {
      return "Stand_KnockDown_Fwd";
    }
    case ANIM_B_KNOCKFORWARD_IN_AIR:
    {
      return "Air_KnockDown_Fwd";
    }
    case ANIM_B_KNOCKFORWARD_LAND:
    {
      return "Land_KnockDown_Fwd";
    }
    case ANIM_B_KNOCKFORWARD_GETUP:
    {
      return "GetUp_Fwd";
    }

    case ANIM_B_TURN_90_L :
    {
      return "Stand_Turn90_Lft";
    }
    case ANIM_B_TURN_90_R:
    {
      return "Stand_Turn90_Rgt";
    }
    case ANIM_B_TURN_180_L:
    {
      return "Stand_Turn180Left";
    }

    case ANIM_B_DODGE_F:
    {
      return "Dodge_Fwd";
    }
    case ANIM_B_DODGE_B:
    {
      return "Dodge_Bck";
    }
    case ANIM_B_DODGE_L:
    {
      return "Dodge_Lft";
    }
    case ANIM_B_DODGE_R:
    {
      return "Dodge_Rgt";
    }
    case ANIM_B_CAST_A:
    {
      return "CastA";
    }
    case ANIM_B_CAST_B:
    {
      return "CastB";
    }
    case ANIM_B_CAST_C:
    {
      return "CastC";
    }  
    case ANIM_B_CAST_D:
    {
      return "CastD";
    }  
    case ANIM_B_RUN_JUMP :
    {
      return "Run_Jump";
    }
    case ANIM_B_RUN_IN_AIR:
    {
      return "Run_Jump_In_Air";
    }
    case ANIM_B_RUN_LAND:
    {
      return "Run_Jump_Land";
    }
    case ANIM_B_DODGE_FWD_TAKEDAM:
    {
      return "Dodge_Fwd_Takedam";
    }
    case ANIM_B_COMBAT_IDLE:
    {
      return "Combat_Idle";
    }
    case ANIM_B_COMBAT_IDLE_BREAK_01:
    {
      return "Combat_Idle_Break_01";
    }
    case ANIM_B_COMBAT_IDLE_BREAK_02:
    {
      return "Combat_Idle_Break_02";
    }
    case ANIM_B_COMBAT_IDLE_BREAK_03:
    {
      return "Combat_Idle_Break_03";
    }
    case ANIM_B_TURN_180_R:
    {
      return "Stand_Turn180Right";
    }
    case ANIM_B_RUN_JUMP_R:
    {
      return "Run_JumpRight";
    }
    case ANIM_B_STAND_TAKEDAM_LEFT:
    {
      return "Stand_TakeDam_Left";
    }
    case ANIM_B_STAND_TAKEDAM_RIGHT:
    {
      return "Stand_Takedam_Right";
    }
    case ANIM_B_EMOTE_ANGER:
    {
      return "Emote_Anger";
    }                    
    case ANIM_B_STAND_TAKEDAM_FWD2:
    {
      return "Stand_TakeDam_Fwd2";
    }                    
    case ANIM_B_STAND_TAKEDAM_FWD3:
    {
      return "Stand_TakeDam_Fwd3";
    }                    
    case ANIM_B_STAND_TAKEDAM_FWD4:
    {
      return "Stand_TakeDam_Fwd4";
    }                    
    case ANIM_B_STAND_TAKEDAM_FWD5:
    {
      return "Stand_TakeDam_Fwd5";
    }                    
    case ANIM_B_STAND_TAKEDAM_FWD6:
    {
      return "Stand_TakeDam_Fwd6";
    }                    
    case ANIM_B_COMBAT_IDLE_TO_STAND:
    {
      return "Combat_Idle_To_Stand";
    }                    
    case ANIM_B_STAND_TAKEDAM_BCK2:
    {
      return "Stand_TakeDam_Bck2";
    }                    
    case ANIM_ATTACK_L :
    {
      return "Stand_Attack_L";
    }
    case ANIM_ATTACK_H:
    {
      return "Stand_Attack_H";
    }
    case ANIM_ATTACK_LL :
    {
      return "Stand_Attack_LL";
    }
    case ANIM_ATTACK_LH :
    {
      return "Stand_Attack_LH";
    }
    case ANIM_ATTACK_HL :
    {
      return "Stand_Attack_HL";
    }
    case ANIM_ATTACK_HH :
    {
      return "Stand_Attack_HH";
    }
    case ANIM_ATTACK_LLL :
    {
      return "Stand_Attack_LLL";
    }
    case ANIM_ATTACK_LLH :
    {
      return "Stand_Attack_LLH";
    }
    case ANIM_ATTACK_LHL :
    {
      return "Stand_Attack_LHL";
    }
    case ANIM_ATTACK_LHH :
    {
      return "Stand_Attack_LHH";
    }
    case ANIM_ATTACK_HLL :
    {
      return "Stand_Attack_HLL";
    }
    case ANIM_ATTACK_HLH :
    {
      return "Stand_Attack_HLH";
    }
    case ANIM_ATTACK_HHL:
    {
      return "Stand_Attack_HHL";
    }
    case ANIM_ATTACK_HHH :
    {
      return "Stand_Attack_HHH";
    }
    case ANIM_ATTACK_LLLL :
    {
      return "Stand_Attack_LLLL";
    }
    case ANIM_ATTACK_JUMP:
    {
      return "Stand_Attack_Jump";
    }
    case ANIM_RANGED_ATTACK :
    {
      return "Stand_Ranged_Attack";
    }
    case ANIM_RANGED_ATTACK_JUMP:
    {
      return "Jump_Ranged_Attack";
    }
    case ANIM_PICKUP_PROP:
    {
      return "Pickup_Prop";
    }
    case ANIM_STAND_HOLD_PROP:
    {
      return "Stand_Hold_Prop";
    }
    case ANIM_WALK_HOLD_PROP:
    {
      return "Walk_Hold_Prop";
    }
    case ANIM_THROW_PROP:
    {
      return "Throw_Prop";
    }
    case ANIM_PUTDOWN_PROP:
    {
      return "Putdown_Prop";
    }
    case ANIM_HOLD_PROP_TAKEDMG_FWD:
    {
      return "Hold_Prop_TakeDmg_Fwd";
    }
    case ANIM_HOLD_PROP_TAKEDMG_BACK:
    {
      return "Hold_Prop_TakeDmg_Back";
    }
    case ANIM_HOLD_PROP_TAKEDMG_LEFT:
    {
      return "Hold_Prop_TakeDmg_Left";
    }
    case ANIM_HOLD_PROP_TAKEDMG_RIGHT:
    {
      return "Hold_Prop_TakeDmg_Right";
    }
    case ANIM_THROW_CHARACTER:
    {
      return "Throw_Character";
    }
    case ANIM_THROWN_BY_CHARACTER:
    {
      return "Thrown_By_Character";
    }
    case ANIM_RUN_HOLD_PROP:
    {
      return "Run_Hold_Prop";
    }
    case ANIM_PUSH_PROP_FWD:
    {
      return "Push_Prop_Fwd";
    }
    case ANIM_PUSH_PROP_LEFT:
    {
      return "Push_Prop_Left";
    }
    case ANIM_PUSH_PROP_RIGHT:
    {
      return "Push_Prop_Right";
    }
    case ANIM_PULL_PROP_BACK:
    {
      return "Pull_Prop_Back";
    }
    case ANIM_GRAB_PROP:
    {
      return "Grab_Prop";
    }
    case ANIM_RELEASE_PROP:
    {
      return "Release_Prop";
    }
    case ANIM_PUSH_PROP_FWDRIGHT:
    {
      return "Push_Prop_Fwd_Right";
    }
    case ANIM_PUSH_PROP_FWDLEFT:
    {
      return "Push_Prop_Fwd_Left";
    }
    case ANIM_PULL_PROP_BACKRIGHT:
    {
      return "Pull_Prop_Back_Right";
    }
    case ANIM_PULL_PROP_BACKLEFT:
    {
      return "Pull_Prop_Back_Left";
    }
    case ANIM_WALK_SLAVE:
    {
      return "Walk_Slave";
    }
    case ANIM_COWER:
    {
      return "Cower";
    }
    case ANIM_WALK_BACKPEDAL:
    {
      return "Walk_BackPedal";
    }
    case ANIM_WALK_STRAFE_RIGHT:
    {
      return "Walk_Strafe_Right";
    }
    case ANIM_WALK_STRAFE_LEFT:
    {
      return "Walk_Strafe_Left";
    }

    case ANIM_OVERRIDE :
    {
      return "Override";
    }
    default:
    {
      return "Unknown";
    }
  }
}

int
cAnimCharController::GetContactNode() const
{
  cAnimControllerSetting* setting = mStates[mCurState]->GetSetting();

  if (setting)
  {
    return setting->mContactNode;
  }

  return CHAR_NODE_RIGHT_HAND;
}

bool            
cAnimCharController::IsDodging()
{
  switch(GetAnimState())
  {
  case AS_DODGE:
    return IsAnimStateActive();
  case AS_ATTACK_HHL:
    if (strcmp(static_cast<cStatsCharacter*>(mOwner->GetStats())->GetClass()->mName.GetName(), "Brute")==0)
    {
      return !IsAnimStateActive();
    }
  default:
    return false;
  }
}

void
cAnimCharController::UpdateImpactBlend()
{
  if (mImpactIndex >= 0)
  {
    SyScene *scene = GetOwner()->GetRegistry()->GetTitan()->GetScene();
    cGraphicActor *graphic = (cGraphicActor*) GetOwner()->GetGraphic();
    SyCSprite * sprite = (SyCSprite*)scene->GetActorSpritePtr(graphic->GetActorHandle());
    float blend;
    if (mImpactTime < smImpactBlendAttack)
    {
      blend = (smImpactBlendMaxBlend/smImpactBlendAttack) * mImpactTime;
      sprite->SetPlaybackBlend(mImpactIndex,blend);
    }
    else if (mImpactTime < smImpactBlendAttack + smImpactBlendSustain)
    {
      blend = smImpactBlendMaxBlend;
      sprite->SetPlaybackBlend(mImpactIndex,blend);
    }
    else if (mImpactTime < smImpactBlendAttack + smImpactBlendSustain + smImpactBlendRelease)
    {
      float fadeout = mImpactTime - (smImpactBlendAttack + smImpactBlendSustain);
      blend = smImpactBlendMaxBlend - (smImpactBlendMaxBlend/smImpactBlendRelease) * fadeout;
      sprite->SetPlaybackBlend(mImpactIndex,blend);
    }
    else
    {
      sprite->EraseAnimPlay(mImpactIndex,*scene);
      mImpactIndex = -1;
    }
  }
}

void            
cAnimCharController::RefreshTargeting(float heading, float pitch, float distance)
{
  mStates[mCurState]->RefreshTargeting(heading, pitch, distance);
}

void                    
cAnimCharController::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();

  // debug display

#ifdef _DRAWDEBUGOVERLAY
  cGraphicActor *graphic = static_cast<cGraphicActor*>(mOwner->GetGraphic());

  if (cDebugOverlay::Get()->IsChannelEnabled("Anim"))
  {
    SyScene *scene = mOwner->GetTitan()->GetScene();
    SyCSprite* sprite = static_cast<SyCSprite*>(scene->GetActorSpritePtr(graphic->GetActorHandle()));

    float animTime = SY_CLAMP(GetAnimTime(), 0.0f, 99.999f);
    float pct = SY_CLAMP(GetAnimTime()/GetAnimDuration(), 0.0f, 99.999f);
    float hit = SY_CLAMP(GetActionFrameTime()/GetAnimDuration(), 0.0f, 1.0f);

    if (mOwner->GetType() == cGameObject::OBJ_PLAYER)
    {
      int x, y;
      SyString playerName;

      mOwner->GetTitan()->GetLocalPlayerName(0, &playerName);
      if (playerName.Compare(mOwner->GetName()) == 0)
      {
        x = 25;
        y = 150;
      }
      else
      {
        x = 400;
        y = 150;
      }

      DEBUGOVERLAY_DRAWSCREENTEXT(mOwner->GetID(), "Anim", x, y, ("Player: %s", GetAnimStateName()), cDebugOverlay::WHITE);
      DEBUGOVERLAY_DRAWSCREENTEXT(mOwner->GetID(), "Anim", x, y+25, ("Loc: %.1f, %.1f, %.1f H:%0.f", mOwner->GetLocation().X, mOwner->GetLocation().Y, mOwner->GetLocation().Z, SY_RAD_TO_DEG(mOwner->GetHeading())), cDebugOverlay::WHITE);
      DEBUGOVERLAY_DRAWSCREENTEXT(mOwner->GetID(), "Anim", x, y+50, ("Time:%2.3f Pct:%2.3f",animTime,pct), cDebugOverlay::WHITE);

      cAnimControllerSetting* setting = mStates[mCurState]->GetSetting();
      if (setting)
      {
        DEBUGOVERLAY_DRAWSCREENTEXT(mOwner->GetID(), "Anim", x, y+75, ("ActFrm:%.2f End:%.2f",setting->mActionFrame, setting->mActionFrameEnd), cDebugOverlay::WHITE);
        DEBUGOVERLAY_DRAWSCREENTEXT(mOwner->GetID(), "Anim", x, y+100, ("ComboStart:%.2f Anim:%.2f End:%.2f",setting->mComboInputStart,setting->mComboAnimStart,setting->mComboEnd), cDebugOverlay::WHITE);
        DEBUGOVERLAY_DRAWSCREENTEXT(mOwner->GetID(), "Anim", x, y+125, ("AllowMove:%.2f",setting->mAllowMovementFrame), cDebugOverlay::WHITE);
        DEBUGOVERLAY_DRAWSCREENTEXT(mOwner->GetID(), "Anim", x, y+150, ("NoInput:%.2f - %.2f",setting->mNoInterruptStart, setting->mNoInterruptEnd), cDebugOverlay::WHITE);
      }
    }
    else
    {
      if (mOwner->GetTitan()->GetCameraController()->GetCamera()->GetLocation().DistanceSquared(mOwner->GetLocation()) < 25.0f * 25.0f)
      {
        SyVect3 camDir = mOwner->GetTitan()->GetCameraController()->GetCamera()->GetDir(); 
        SyVect3 offset(sprite->GetBBox().Max-sprite->GetBBox().Min);
        float height = 2.0f;
        offset.Mul(camDir, -1.5f);
        DEBUGOVERLAY_DRAWWORLDTEXT(mOwner->GetID(), "Anim", mOwner->GetLocation()+SyVect3(0.0f, height, 0.0f) + offset, ("Time:%.3f Pct:%.3f Hit:%.2f",animTime, pct, hit), cDebugOverlay::YELLOW);
        offset.Mul(camDir, -2.0f);
        DEBUGOVERLAY_DRAWWORLDTEXT(mOwner->GetID(), "Anim", mOwner->GetLocation()+SyVect3(0.0f, height+0.25f, 0.0f) + offset, (GetAnimStateName()), cDebugOverlay::YELLOW);
      }
    }
  }

  if (cDebugOverlay::Get()->IsChannelEnabled("NamedNode"))
  {
    SyVect3 nodeLoc, xOffset, yOffset, zOffset;
    SyColor32F color;
    for (int i=CHAR_NODE_HEAD; i<CHAR_NODE_LEFT_HAND_CARRY+1; ++i)
    {
      switch (i)
      {
        case CHAR_NODE_HEAD: color = cDebugOverlay::RED; break;
        case CHAR_NODE_CHEST: color = cDebugOverlay::GREEN; break;
        case CHAR_NODE_LEFT_FOOT: color = cDebugOverlay::CYAN; break;
        case CHAR_NODE_RIGHT_FOOT: color = cDebugOverlay::CYAN; break;
        case CHAR_NODE_LEFT_HAND: color = cDebugOverlay::YELLOW; break;
        case CHAR_NODE_RIGHT_HAND: color = cDebugOverlay::YELLOW; break;
        case CHAR_NODE_LEFT_SHOULDER: color = cDebugOverlay::MAGENTA; break;
        case CHAR_NODE_RIGHT_SHOULDER: color = cDebugOverlay::MAGENTA; break;
        case CHAR_NODE_RIGHT_HAND_CARRY: color = cDebugOverlay::ORANGE; break;
        case CHAR_NODE_LEFT_HAND_CARRY: color = cDebugOverlay::ORANGE; break;
        default: color = cDebugOverlay::WHITE; break;
      }
    
      if (CHAR_NODE_CHEST == i)
      {
        xOffset(0.5f, 0.0f, 0.0f);
        yOffset(0.0f, 0.5f, 0.0f);
        zOffset(0.0f, 0.0f, 0.5f);
      }
      else
      {
        xOffset(0.3f, 0.0f, 0.0f);
        yOffset(0.0f, 0.3f, 0.0f);
        zOffset(0.0f, 0.0f, 0.3f);
      }

      if (graphic->GetIdentNodeLocation(i, &nodeLoc))
      {
        DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "NamedNode", nodeLoc+xOffset, nodeLoc-xOffset, color);
        DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "NamedNode", nodeLoc+yOffset, nodeLoc-yOffset, color);
        DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "NamedNode", nodeLoc+zOffset, nodeLoc-zOffset, color);
      }
    }
  }
#endif


  if (input->mAnimationOverride != ANIM_HANDLE_NONE)
  {
    ChangeState(AS_OVERRIDE);
  }
  else if (input->mScriptAnimID > 0)
  {
    ChangeState(AS_SCRIPT);
  }

  if (input->mWalkTo == true)
  {
    ChangeState(AS_TURNTO); // turn to direction we're walking too, walk there, then turn back
  }
  
  mStates[mCurState]->CommonActions();
  if (!mStates[mCurState]->Update(time))
  {
    mStateTime += time;
  }
  else
  {
    mStateTime = 0.0f;
  }

  mCombatTime += time;
  if (mStates[mCurState]->IsCombat())
  {
    SetCombat();
  }

  if (mImpactIndex >=0)
  {
    mImpactTime += time;

    UpdateImpactBlend();
  }
}

const char *            
cAnimCharController::GetAnimStateName() const
{
  return l_AnimStatenames[GetAnimState()];
}

bool                    
cAnimCharController::ChangeState(eAnimState state)
{
  if (!mStates[state]->AcceptTransition())
  {
    return false;
  }

  mStates[mCurState]->Exit();
  mPrevState = mCurState;
  mCurState = state;
  mStateTime = 0.0f;

  if (!mOwner->IsRemote())
  {
    NetworkSendBroadcast(mCurState);
  }

  mStates[mCurState]->Enter();

  return true;
}

float           
cAnimCharController::GetDamageMultiplier(eComboType combo)
{
  SyAssert(combo < NUM_COMBOS);

  if (NUM_COMBOS == combo)
  {
    return 1.0f;
  }

  int state = combo + AS_ATTACK_L; 
  cAnimControllerSetting * setting = mOwner->GetTitan()->GetAnimControllerSys()->Fetch(mAnimSetID,state);
  
  if (setting == NULL)
  {
    return 1.0f;
  }

  return setting->mDamageMultiplier;
}

float           
cAnimCharController::GetStrikethroughMultiplier(eComboType combo)
{
  SyAssert(combo < NUM_COMBOS);

  if (NUM_COMBOS == combo)
  {
    return 1.0f;
  }

  int state = combo + AS_ATTACK_L; 
  cAnimControllerSetting * setting = mOwner->GetTitan()->GetAnimControllerSys()->Fetch(mAnimSetID,state);
  
  if (setting == NULL)
  {
    return 1.0f;
  }

  return setting->mStrikethroughMultiplier;
}

float           
cAnimCharController::GetTargetRecoveryTime(eComboType combo)
{
  SyAssert(combo < NUM_COMBOS);

  if (NUM_COMBOS == combo)
  {
    return 1.0f;
  }
 
  int state = combo + AS_ATTACK_L; 
  cAnimControllerSetting * setting = mOwner->GetTitan()->GetAnimControllerSys()->Fetch(mAnimSetID,state);

  if (setting == NULL)
  {
    return 1.0f;
  }

  return setting->mTargetRecoveryTime;
}

float           
cAnimCharController::GetRange(eComboType combo)
{
  SyAssert(combo < NUM_COMBOS);

  if (NUM_COMBOS == combo)
  {
    return 1.0f;
  }

  int state = combo + AS_ATTACK_L; 
  cAnimControllerSetting * setting = mOwner->GetTitan()->GetAnimControllerSys()->Fetch(mAnimSetID,state);
  
  if (setting == NULL)
  {
    return 1.0f;
  }

  return setting->mRange;
}

void
cAnimCharController::NetworkSendBroadcast(eAnimState state)
{
  cNetAnimStatePacket packet;
  packet.mAnimState = (int)state;
  packet.mTarget = mInput.mTarget;
  packet.mActionTarget = mInput.mActionTarget;
  packet.mTargetHeading = mInput.mTargetHeading;
  packet.mTargetRange = mInput.mTargetRange;
  packet.mScriptAnimID = mInput.mScriptAnimID;
  packet.mScriptAnimAllowHitReactions = mInput.mScriptAnimAllowHitReactions;
  packet.mCastSpellID = mInput.mCastSpellID;
  packet.mCastAnimSettingOverride = mInput.mCastAnimSettingOverride;


  char buf[1024];
  int len = packet.PackBuffer(buf, sizeof(buf));
  mOwner->GetTitan()->GetPeeringNetwork()->ObjectBroadcast(mOwner->GetID(), buf, len);
}                         

void
cAnimCharController::NetworkReceiveBroadcast(const char *state, int statelen)
{
  cNetAnimStatePacket packet;
  packet.UnpackBuffer(state, statelen);

  SyAssertf(mOwner->IsRemote(), "Owner receiving network instructions?"); 

  mInput.mTarget = packet.mTarget;
  mInput.mActionTarget = packet.mActionTarget;
  mInput.mTargetHeading = packet.mTargetHeading;
  mInput.mTargetRange = packet.mTargetRange;
  mInput.mScriptAnimID = packet.mScriptAnimID;
  mInput.mScriptAnimAllowHitReactions = packet.mScriptAnimAllowHitReactions;
  mInput.mCastSpellID = packet.mCastSpellID;
  mInput.mCastAnimSettingOverride = (eAnimState)packet.mCastAnimSettingOverride;

  ChangeState((eAnimState)packet.mAnimState);
}

void
cAnimCharController::PlayImpact(tGameObjectID attacker, float recoveryTime)
{
  SyScene *scene = GetOwner()->GetRegistry()->GetTitan()->GetScene();
  cGraphicActor *graphic = (cGraphicActor*) GetOwner()->GetGraphic();
  SyCSprite * sprite = (SyCSprite*)scene->GetActorSpritePtr(graphic->GetActorHandle());
  if (mImpactIndex >= 0)
  {
    sprite->EraseAnimPlay(mImpactIndex,*scene);
  }

  float heading = GetOwner()->GetHeading();
  float headingTowards = heading;
  cGameObject* pAttacker = GetOwner()->GetRegistry()->Fetch(attacker);

  if (pAttacker)
  {
    headingTowards = GetOwner()->GetHeadingTowards(pAttacker);
  }

  int32 animID = cAS_HitReact::GetHitAnim(mCurState, sprite, AngleDifference(heading, headingTowards));

  if (sprite->HasAnim(animID))
  {
    mImpactIndex = sprite->AppendAnimPlay(animID,0,*scene);
    sprite->SetPlaybackBlend(mImpactIndex,0.0f);

    float speedMult = (float)(static_cast<cStatsCharacter*>(GetOwner()->GetStats())->CalcAttackSpeed())*0.01f;
    float playSpeed = 1.0f;

    if (speedMult > 0.0f)
    {
      playSpeed *= speedMult;
    }

    if (recoveryTime > 0.0f)
    {
      playSpeed *= (sprite->GetAnimDuration(animID, *scene)/recoveryTime);
    }

    sprite->SetPlaySpeed(mImpactIndex, playSpeed, *scene);
  }
  else
  {
    GAME_ASSERT(ERROR_ART, false, "Character missing %s anim", GetAnimName(animID));
  }

  mImpactTime = 0.0f;
}

void
cAnimCharController::ExtractImpact()
{
}

void
cAnimCharController::InsertImpact()
{
  if (mImpactIndex < 0)
  {
    // no impact to resume
    return;
  }
  SyScene *scene = GetOwner()->GetRegistry()->GetTitan()->GetScene();
  cGraphicActor *graphic = (cGraphicActor*) GetOwner()->GetGraphic();
  SyCSprite * sprite = (SyCSprite*)scene->GetActorSpritePtr(graphic->GetActorHandle());
  
  int32 animID = cAS_HitReact::GetHitAnim(mCurState, sprite, 0.0f);
  mImpactIndex = sprite->AppendAnimPlay(animID,0,*scene);
  sprite->SetPlaybackBlend(mImpactIndex,0.0f);
}

void
cAnimCharController::ClearImpact()
{
  mImpactIndex = -1;
}

bool            
cAnimCharController::IsAnimStateActive() 
{
  return mStates[mCurState]->IsActive();
}

int            
cAnimCharController::GetHitPriority() 
{
  if (AS_SCRIPT == mCurState ||
      AS_OVERRIDE == mCurState)
  {
    return 100000;
  }

  cAnimControllerSetting* setting = mStates[mCurState]->GetSetting();

  if (setting)
  {
    return setting->mHitPriority; 
  }

  return 0;
}

float           
cAnimCharController::GetActionFrameTime()
{
  return mStates[mCurState]->GetActionFrame();
};

float           
cAnimCharController::GetAnimDuration()
{
  return mStates[mCurState]->GetDuration();
}

float           
cAnimCharController::GetAnimSpeed()
{
  return mStates[mCurState]->GetSpeed();
}

tGameID           
cAnimCharController::GetComboSpellID(eComboType combo)
{
  eAnimState animState = GetAnimStateFromComboType(combo);

  if (NUM_ANIM_STATES == animState)
  {
    return ID_NONE;
  }

  cAnimControllerSetting* setting = mStates[animState]->GetSetting();

  if (setting)
  {
    return setting->mSpellID;
  }

  return ID_NONE;
}


tGameObjectID
cAnimCharController::GetCarriedObject()
{
  return mStates[mCurState]->GetCarriedObject();
}

int32           
cAnimCharController::GetAnimHandle(tAnimIDValues animID)
{
  SyScene *scene = GetOwner()->GetRegistry()->GetTitan()->GetScene();
  cGraphicActor *graphic = (cGraphicActor*) GetOwner()->GetGraphic();
  SyCSprite* sprite = (SyCSprite*) scene->GetActorSpritePtr( graphic->GetActorHandle());
  return sprite->GetAnimHandle(animID,0);
}


//------------------------------------ cAnimState

cAnimState::cAnimState():
  mOwner(NULL),
  mPlaybackIndex(-1),
  mTurning(false),
  mPlayingTurnAnim(false)
{
}

cGameObject *
cAnimState::GetGameObject()
{
  return mOwner->GetOwner();
}

SyScene *
cAnimState::GetScene()
{
  return mOwner->GetOwner()->GetRegistry()->GetTitan()->GetScene();
}

SyCSprite   *
cAnimState::GetSprite()
{
  cGameObject *obj = GetGameObject();
  cGraphicActor *graphic = (cGraphicActor*) obj->GetGraphic();
  SyActorHandle handle = graphic->GetActorHandle();

  if (SyActorNull != handle)
  {
    return  (SyCSprite*) GetScene()->GetActorSpritePtr( graphic->GetActorHandle());
  }

  return NULL;
}


cAnimCharControllerInput *
cAnimState::GetInput()
{
  return mOwner->GetInput();
}

cAnimControllerSetting*
cAnimState::GetSetting()
{
  return mOwner->GetOwner()->GetTitan()->GetAnimControllerSys()->Fetch(mOwner->GetAnimSet(), mOwner->GetAnimState());
}


bool
cAnimState::PlayAnim(int32 animid, float speedMult, bool bForceShortBlend)
{
  cGameObject* pOwner = GetGameObject();
  SyCSprite* sprite = GetSprite();
  SyScene *scene = GetScene();
  int curIndex;

  if (!sprite->HasAnim(animid))
  {
    if (pOwner->GetType() == cGameObject::OBJ_PLAYER)
    {
      mPlaybackIndex = -1;
      return false;
    }

    animid = ANIM_ATTACK_L;
    if (!sprite->HasAnim(animid))
    {
      animid = ANIM_B_STAND_IDLE;
      if (!sprite->HasAnim(animid))
      {
        // ok this is too much i give up
        mPlaybackIndex = -1;
        return false;
      }
    }
  }

  //mOwner->ClearImpact();
  mBlendIndex = -1;
  if (sprite->GetNumPlay() > 0)
  {
    int impactIndex = mOwner->GetImpactIndex();
    int numAnims = 1; // num anims to save from those currently playing
    // clear out impact anim for current pose
    if (impactIndex >= 0)
    {
      sprite->SetPlaybackBlend(impactIndex,0.0f);
      ++numAnims;
    }

    mBlendIndex = sprite->GetFirstPlay();
    sprite->SetPlaybackToCurrentPose(mBlendIndex,*scene); 

    while (sprite->GetNumPlay() > numAnims)
    {
      curIndex = sprite->GetFirstPlay();
      while (curIndex == mBlendIndex || curIndex==impactIndex)
      {
        curIndex = sprite->GetNextPlay(curIndex);
      }
      sprite->ErasePlay(curIndex,*scene);
    }

    mPlaybackIndex = sprite->PrependAnimPlay(animid,0,*scene);

    cAnimControllerSetting * setting = GetSetting();
    float playSpeed = sprite->GetPlaySpeed(mPlaybackIndex);
    float blendTime = 0.25f;
    if (setting != NULL)
    {
      playSpeed = setting->mSpeed;
      blendTime = setting->mIntroBlend;
    }

    if (bForceShortBlend)
    {
      blendTime = 0.025f;
    }


    sprite->SetPlaybackBlendMode(mBlendIndex, SYANIMBLENDMODE_INVERT_TIMED_INTERP, SY_ROUND(blendTime*1000), *scene);

    sprite->SetPlaySpeed(mPlaybackIndex, playSpeed*speedMult, *scene);

    mAnimDuration = sprite->GetPlayDuration(mPlaybackIndex, playSpeed*speedMult, *scene);

    if (impactIndex >= 0)
    {
      mOwner->UpdateImpactBlend();
    }
  }
  else
  {
    // sprite->Clear();
    sprite->EraseAllPlay(*scene);
    mPlaybackIndex = sprite->PrependAnimPlay(animid,0,*scene);
  }
  return true;

}

float       
cAnimState::TurnTo(float32 angle,float32 turnamount,float32 turnThreshold)
{
  cGameObject *obj = GetGameObject();

  // check for turn...
  float cur_heading = obj->GetHeading();
  float delta_ang = AngleDifference(angle,cur_heading);

  // add a little dead zone to the turn
  if (delta_ang > turnThreshold + EPSILON)
  {
    delta_ang -= turnThreshold;
  }
  else if (delta_ang < -turnThreshold - EPSILON)
  {
    delta_ang += turnThreshold;
  }
  else
  {
    mTurning = false;
    delta_ang = 0.0f;
  }

  if (!mTurning)
  {
    if (!FloatsEqual(delta_ang,0.0f))
    {
      // start turning
      mTurning = true;
    }
  }
  
  if (mTurning)
  {
    cur_heading += GetAngleAdjustment(delta_ang,turnamount);
    cur_heading = AngleNormalize(cur_heading);
    obj->SetHeading(cur_heading);
  }
  return delta_ang;
}


void 
cAnimState::CommonActions() // take potions, pick up items
{
  ActivateObject();
}

bool
cAnimState::UpdateHitReactionStateChanges()
{
  cAnimCharControllerInput *input = GetInput();

  if (input->mbKnockback)
  {
    mOwner->ChangeState(AS_KNOCKBACK);
    return true;
  }

  if (input->mbKnockforward)
  {
    mOwner->ChangeState(AS_KNOCKFORWARD);
    return true;
  }

  if (input->mbThrown)
  {
    input->mbThrown = false;
    mOwner->ChangeState(AS_THROWN_BY_CHARACTER);
    return true;
  }

  if (input->mHitReact)
  {
    input->mHitReact = false;

    cGameObject* pObj = GetGameObject();
    cGameObject* pHitReactTarget = pObj->GetTitan()->GetRegistry()->Fetch(input->mHitReactTarget);

    int defenderPriority = mOwner->GetHitPriority();
    bool bInterrupt = true;

    if (pHitReactTarget)
    {
      cGraphicCharacter* pTargetGraphic = prop_cast<cGraphicCharacter*>(pHitReactTarget->GetGraphic());

      if (pTargetGraphic)
      {
        int attackerPriority = pTargetGraphic->GetAnimController()->GetHitPriority();

        bInterrupt = attackerPriority > defenderPriority || 
                     (pHitReactTarget->GetType() == cGameObject::OBJ_PLAYER && attackerPriority == defenderPriority);
      }
    }

    if (bInterrupt && !mOwner->IsIdle())
    {
      mOwner->ChangeState(AS_HITREACT);
      return true;
    }
    else if (defenderPriority < 20 || mOwner->IsIdle())
    {
      mOwner->PlayImpact(input->mHitReactTarget, input->mHitReactTime);
    }
  }

  if (input->mFall)
  {
    input->mFall = false;
    if (!mOwner->IsInAir() && mOwner->GetAnimState() != AS_JUMP && mOwner->GetAnimState() != AS_RUN_JUMP)
    {
      mOwner->ChangeState(AS_IN_AIR);
      return true;
    }
  }

  // play death last out of all hit reactions, to let enemies get
  // hit through entire combo and only play death afterwards
  if (input->mDeath)
  {
    input->mDeath = false;
    mOwner->ChangeState(AS_DEATH);
    return true;
  }

  if (mOwner->GetAnimState() != AS_STUNNED)
  {
    if (GetGameObject()->GetStats()->QueryFlag("Stunned") || 
        GetGameObject()->GetStats()->QueryFlag("Sleeping"))
    {
      mOwner->ChangeState(AS_STUNNED);
      return true;
    }
  }

  return false;
}

bool cAnimState::UpdateActions(float time)
{
  cGameObject *obj = GetGameObject();

  cAnimCharControllerInput *input = GetInput();
  cAnimControllerSetting* setting = GetSetting();

  float cameraShakeFrame = 0.5f;
  float noInterruptStart = 0.0f;
  float noInterruptEnd = 1.0f;
  float comboEnd = 1.0f;
  float allowMovementFrame = 1.0f;
  float prevTime = mOwner->GetAnimTime();
  float curTime = prevTime+time;

  if (setting)
  {
    cameraShakeFrame = setting->mCameraShakeFrame;
    noInterruptStart = setting->mNoInterruptStart;
    noInterruptEnd = setting->mNoInterruptEnd;
    comboEnd = setting->mComboEnd;
    allowMovementFrame = setting->mAllowMovementFrame;
  }

  cameraShakeFrame *= mAnimDuration;
  noInterruptStart *= mAnimDuration;
  noInterruptEnd *= mAnimDuration;
  comboEnd *= mAnimDuration;
  allowMovementFrame *= mAnimDuration;

  // camera shake
  if (prevTime <= cameraShakeFrame && curTime > cameraShakeFrame &&
      setting && setting->mCameraShakeAmount > 0.0f)
  {
    obj->GetTitan()->GetCameraController()->Shake(0.0f, setting->mCameraShakeAmount, 10.0f, setting->mCameraShakeTime);
  }


  bool bIsIdle = mOwner->IsIdle();
  bool bAllowSameStateInterrupt = false;

  // only allow attack if we're not attacking now, or if we're past the 
  // appropriate spot in out attack animation
  bool bAllowAttack = false;
  eComboType combo = mOwner->GetComboType();

  if (NUM_COMBOS != combo)
  {
    bAllowAttack = bIsIdle || (curTime >= allowMovementFrame && curTime >= comboEnd);
  }
  else
  {
    bAllowAttack = bIsIdle || (curTime >= allowMovementFrame);
  }

  eAnimState newState = NUM_ANIM_STATES;
  eAnimState curState = mOwner->GetAnimState();

  if (bIsIdle || 
      curTime < noInterruptStart ||
      curTime > noInterruptEnd)
  {
    if (input->mDodging)
    {
      bAllowSameStateInterrupt = true;
      newState = AS_DODGE;
    }
    else if (input->mBlockRequest && static_cast<cStatsCharacter*>(obj->GetStats())->GetBlock() >= 0)
    {
      newState = AS_BLOCK;
    }
    else if (input->mJumpRequest)
    {
      if (mOwner->GetAnimState() == AS_RUN && mOwner->GetStateTime() > 0.25f)
      {
        newState = AS_RUN_JUMP;
      }
      else
      {
        newState = AS_JUMP;
      }
    }
    else if (input->mActionRequest)
    {
      cGameObject *pActionTarget = obj->GetRegistry()->Fetch(input->mActionTarget);

      if (!pActionTarget || pActionTarget->GetType() == cGameObject::OBJ_NPC)
      {
        newState = AS_THROW_CHARACTER;
      }
      else if (pActionTarget && !pActionTarget->GetStats()->IsDead())
      {
        cStats::eActivateType activateType = pActionTarget->GetStats()->GetActivateType();
        if (cStats::ACTIVATE_PICKUP == activateType)
        {
          if (pActionTarget->GetType() == cGameObject::OBJ_PROP &&
              static_cast<cStatsCharacter*>(obj->GetStats())->CanLift(pActionTarget))
          {
            newState = AS_PICKUP_PROP;
          }
        }
        else if (cStats::ACTIVATE_PUSH == activateType)
        {
          newState = AS_PUSH_PROP;
        }
        else if (cStats::ACTIVATE_FLIP == activateType)
        {
          newState = AS_FLIP_LEVER;
        }
      }
    }
    else if (input->mEmoteAnger)
    {
      newState = AS_EMOTE_ANGER;
    }
  }

  if (NUM_ANIM_STATES == newState && bAllowAttack)
  {
    if (input->mNPCComboOverride != NUM_COMBOS && 
        obj->GetType() == cGameObject::OBJ_NPC &&
        (input->mAttackRequestL || input->mAttackRequestS))
    {
      newState = mOwner->GetAnimStateFromComboType(input->mNPCComboOverride);
    }
//  BP 3/31 use throw on both attack buttons instead of action button
//    else if (input->mAttackRequestL && input->mAttackRequestS)
//    {
//      newState = AS_THROW_CHARACTER;
//    }
    else if (input->mAttackRequestL)
    {              
      bAllowSameStateInterrupt = true;
      newState = AS_ATTACK_L;
    }
    else if (input->mAttackRequestS)
    {                      
      bAllowSameStateInterrupt = true;
      newState = AS_ATTACK_H;
    }
    else if (input->mAttackRequestRanged)
    { 
      bAllowSameStateInterrupt = true;
      newState = AS_ATTACK_RANGED;
    }
    else if (input->mCastSpellID != ID_NONE && input->mCastAnimSettingOverride > 0)
    {        
      if (AS_CAST_SPELL_B == input->mCastAnimSettingOverride ||
          AS_CAST_SPELL_C == input->mCastAnimSettingOverride ||
          AS_CAST_SPELL_D == input->mCastAnimSettingOverride)
      {
        newState = input->mCastAnimSettingOverride;
      }
      else
      {
        newState = AS_CAST_SPELL_A;
      }
    }
  }

  if (NUM_ANIM_STATES == newState &&
      (bIsIdle || curTime >= allowMovementFrame) &&
      !IsActive() &&
      !FloatsEqual(input->mSpeedRequest, 0.0f))
  {
    if (AS_WALK_WITH_FACING != curState &&
        input->mbKeepFacing)
    {
      newState = AS_WALK_WITH_FACING;
    }
    else if (AS_RUN != curState)
    {
      bool start_running = false;

      if (input->mbKeepFacing)
      {
      }
      else if (!mTurning)
      {
        start_running = true;
      }
      else
      {
        float delta = AngleDifference(input->mHeadingRequest,obj->GetHeading());

        if (SY_FABS(delta) < SY_DEG_TO_RAD(30))
        {
          start_running = true;
        }
        else
        {
          newState = AS_COMBAT_IDLE;
        }
      }

      if (mOwner->IsAttacking() &&
          curTime >= allowMovementFrame &&
          input->mSpeedRequest < 0.75f)
      {
        start_running = false;
      }

      if (start_running)
      {
        newState = AS_RUN;
      }
    }
  }

  if (NUM_ANIM_STATES != newState &&
      (curState != newState || bAllowSameStateInterrupt))
  {
    mOwner->ChangeState(newState);
    return true;
  }

  return false;
}

void
cAnimState::UpdatePivot(float time, bool bTurnExact)
{
  cAnimCharControllerInput* input = mOwner->GetInput();
  cAnimControllerSetting* setting = GetSetting();

  float turnSpeed = mOwner->smActionDefaultTurnSpeed;
  float actionStart = 0.5f;
  float actionEnd = 0.5f;

  if (setting)
  {
    turnSpeed = setting->mTurnSpeed;
    actionStart = setting->mActionFrame;
    actionEnd = setting->mActionFrameEnd;
  }

  if (turnSpeed <= 0.000001f)
  {
    return;
  }

  actionStart *= mAnimDuration;
  actionEnd *= mAnimDuration;
  
  bool bIsComboing = mOwner->IsAttacking() && IsActive();
  float curTime = mOwner->GetAnimTime()+time;

  if (bIsComboing && curTime < actionStart)
  {
    TurnTo(input->mHeadingRequest,time*turnSpeed*3.0f, bTurnExact ? 0.0f : mOwner->smActionTurnThreshold);
  }
  else if (curTime <= actionEnd)
  {
    TurnTo(input->mHeadingRequest,time*turnSpeed, bTurnExact ? 0.0f : mOwner->smActionTurnThreshold);
  }
}

void
cAnimState::ActivateObject()
{
  cAnimCharControllerInput *input = GetInput();
  if (input->mActionRequest == true &&
      ID_NONE != input->mActionTarget)
  {
    cGameObject *obj = GetGameObject();
    if (((cStatsCharacter *)obj->GetStats())->ActivateObject(input->mActionTarget))
    {
      input->mActionRequest = false;
      input->mActionTarget = ID_NONE;
    }
  }
}

void
cAnimState::ClearInput()
{
  cAnimCharControllerInput *input = GetInput();
  float heading = input->mHeadingRequest;
  bool bBlockRequest = input->mBlockRequest;
  bool bDeath = input->mDeath;
  bool bKeepFacing = input->mbKeepFacing;
  float faceHeading = input->mFaceHeading;
  tGameObjectID hitReact = input->mHitReactTarget;
  input->Clear();
  input->mHeadingRequest = heading;
  input->mBlockRequest = bBlockRequest;
  input->mDeath = bDeath;
  input->mbKeepFacing = bKeepFacing;
  input->mFaceHeading = faceHeading;
  input->mHitReactTarget = hitReact;
}


bool
cAnimState::TurnAnimsCompatable(int playing_anim,int next_anim)
{
  switch (playing_anim)
  {
  case ANIM_B_TURN_180_L:
  case ANIM_B_TURN_90_L:
    if (next_anim == ANIM_B_TURN_180_L || next_anim == ANIM_B_TURN_90_L)
    {
      return true;
    }
  case ANIM_B_TURN_180_R:
  case ANIM_B_TURN_90_R:
    if (next_anim == ANIM_B_TURN_180_R || next_anim == ANIM_B_TURN_90_R)
    {
      return true;
    }

  }
  return false;
}
int 
cAnimState::GetTurnAnim(float delta)
{
  SyCSprite* sprite = GetSprite();

  if ( delta > SY_DEG_TO_RAD(135.0f )) 
  {
    if ( sprite->HasAnim(ANIM_B_TURN_180_L))
    {
      return ANIM_B_TURN_180_L;
    }
  }
  if ( delta < SY_DEG_TO_RAD(-135.0f ))
  {
    if ( sprite->HasAnim(ANIM_B_TURN_180_R))
    {
      return ANIM_B_TURN_180_R;
    }
  }
  else if (delta > SY_DEG_TO_RAD(1))
  {
    if ( sprite->HasAnim(ANIM_B_TURN_90_L))
    {
      return ANIM_B_TURN_90_L;
    }
  }
  else if (delta < SY_DEG_TO_RAD(-1))
  {
    if ( sprite->HasAnim(ANIM_B_TURN_90_R))
    {
      return ANIM_B_TURN_90_R;
    }
  }

  return -1;
}

float
cAnimState::GetSpeed()
{
  if (mPlaybackIndex >= 0)
  {
    return GetSprite()->GetPlaySpeed(mPlaybackIndex);
  }
  
  return 1.0f;
}

float
cAnimState::GetActionFrame()
{
  cAnimControllerSetting * setting = GetSetting();
  if (setting == NULL) return 0.0f;
  return setting->mActionFrame * mAnimDuration;
}

//------------------------------------ cAS_Stand

cAS_Stand::cAS_Stand(int32 animID) :
mAnimID(animID)
{
  mTurnSpeed = smTurnSpeed;
}

void 
cAS_Stand::Enter()
{
  mTurnSpeed = smTurnSpeed;
  SyCSprite* sprite = GetSprite();

  float randSpeedAdjust = 1.0f + (GetGameObject()->GetTitan()->RandomFloat()*0.6f - 0.3f);
  if (sprite->HasAnim(mAnimID))
  {
    PlayAnim(mAnimID, randSpeedAdjust);
  }
  else
  {
    PlayAnim(ANIM_B_STAND_IDLE, randSpeedAdjust);
  }
}

bool 
cAS_Stand::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (input->mSpeedRequest > EPSILON && !mTurning && !input->mbKeepFacing)  // start turning
  {
    mOwner->ClearStateTime();
    mTurnTo = input->mHeadingRequest;
    PlayTurnAnim();
  }
  else if (input->mSpeedRequest < EPSILON && !mTurning && !input->mbKeepFacing)
  {
    TurnTo(input->mHeadingRequest,time*mTurnSpeed,smTurnThreshold);

    if (!mTurning)
    {
      TurnTo(input->mHeadingRequest,10000.0f,EPSILON);
      mTurning = false;
    }
  }

  if (mTurning)
  {
    PlayTurnAnim();
    TurnTo(input->mHeadingRequest,time*mTurnSpeed,smTurnThreshold);
  }
  else if (mPlayingTurnAnim)
  {
    // stop playing turn anim
    Enter();
    mPlayingTurnAnim = false;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  if (mAnimID == ANIM_B_COMBAT_IDLE)
  {
    SyCSprite* pSprite = GetSprite();

    if (mPlaybackIndex != -1 &&
        pSprite->GetPlayAnimID(mPlaybackIndex) == ANIM_B_COMBAT_IDLE_TO_STAND)
    {
      if (pSprite->IsPlaybackStopped(mPlaybackIndex))
      {
        mOwner->ChangeState(AS_STAND);
        return true;
      }
    }
    else if (!mTurning &&
             !mOwner->GetCombat())
    {
      if (pSprite->HasAnim(ANIM_B_COMBAT_IDLE_TO_STAND) && 
          mPlaybackIndex != -1 &&
          pSprite->GetPlayAnimID(mPlaybackIndex) != ANIM_B_COMBAT_IDLE_TO_STAND)
      {
        PlayAnim(ANIM_B_COMBAT_IDLE_TO_STAND);
      }
      else
      {
        mOwner->ChangeState(AS_STAND);
        return true;
      }
    }
  }

  static const float FIDGET_TIME = 7.0f;
  if (mOwner->GetStateTime() > FIDGET_TIME)
  {
    if (mAnimID == ANIM_B_STAND_IDLE)
    {
      mOwner->ChangeState(AS_FIDGET);
    }
    return true;
  }

  return false;
}

void
cAS_Stand::PlayTurnAnim()
{
  float delta = TurnTo(mTurnTo,0,smTurnThreshold); // don't actually turn, but determine delta

  SyCSprite* sprite = GetSprite();


  int turn_anim = GetTurnAnim(delta);

  if (turn_anim != -1)
  {
      if (mPlaybackIndex < 0 || sprite->IsPlaybackStopped(mPlaybackIndex) || 
          !TurnAnimsCompatable(sprite->GetPlayAnimID(mPlaybackIndex), turn_anim))
      {
        PlayAnim(turn_anim);
        

        //sprite->SetPlaySpeed(mPlaybackIndex,mTurnSpeedMultiplier,*GetScene());

        mAnimDuration /= smTurnSpeedMultiplier;
        if (delta < SY_DEG_TO_RAD(90) && delta > SY_DEG_TO_RAD(-90))
        {
          mTurnSpeed = SY_DEG_TO_RAD(90)/mAnimDuration;
        }
        else
        {
          mTurnSpeed = delta/mAnimDuration;
        }
        if (mTurnSpeed < 0.0f)
        {
          mTurnSpeed = -mTurnSpeed;
        }
      }
      mPlayingTurnAnim = true;
  }
}

void 
cAS_Stand::RegisterTuningVariables()
{
  gTuningSys.AddFloat(&smTurnSpeedMultiplier,"Stand_TurnSpeed_Multiplier");
}

//------------------------------------ cAS_StandWithProp

void
cAS_StandWithProp::Enter()
{
  mPropID = GetInput()->mActionTarget;

  cAS_Stand::Enter();
}

bool
cAS_StandWithProp::UpdateActions(float time)
{
  cAnimCharControllerInput *input = GetInput();

  if (input->mAttackRequestRanged)
  { 
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_THROW_PROP);
    return true;
  }

  if (input->mActionRequest)
  { 
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_PUTDOWN_PROP);
    return true;
  }

  if (!FloatsEqual(input->mSpeedRequest,0.0f))
  {
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_RUN_CARRY_PROP);
    return true;
  }

  return false;
}

bool 
cAS_StandWithProp::Update(float time)
{
  cGameObject* pProp = GetGameObject()->GetRegistry()->Fetch(mPropID);
  if (!pProp || pProp->GetStats()->IsDead())
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  if (UpdateHitReactionStateChanges())
  {
    pProp->SetCarriedBy(ID_NONE);
    pProp->GetPhysics()->Nudge(); // make sure it falls down
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  UpdatePivot(time);

  return false;
}

//------------------------------------ cAS_Fidget

bool
cAS_Fidget::AcceptTransition()
{
  Titan *titan = mOwner->GetOwner()->GetTitan();
  SyCSprite* sprite = GetSprite();

  int guess = titan->Random(0,2);
  switch(guess)
  {
    case 0:

    if (sprite->HasAnim(mAnimID))
    {
      mPlayingAnimID = mAnimID;
      return true;
    }
    // fallthrough:
    case 1:
    if (sprite->HasAnim(mAnimID2))
    {
      mPlayingAnimID = mAnimID2;
      return true;
    }
    // fallthrough:
    case 2:
    if (sprite->HasAnim(mAnimID3))
    {
      mPlayingAnimID = mAnimID2;
      return true;
    }
    break;
  }
  if (sprite->HasAnim(mAnimID))
  {
    mPlayingAnimID = mAnimID;
    return true;
  }
  if (sprite->HasAnim(mAnimID2))
  {
    mPlayingAnimID = mAnimID2;
    return true;
  }

  return false;
}


void 
cAS_Fidget::Enter()
{
  mTurnSpeed = smTurnSpeed;

  SyAssert(GetSprite()->HasAnim(mPlayingAnimID));

  float randSpeedAdjust = 1.0f + (GetGameObject()->GetTitan()->RandomFloat()*0.8f - 0.4f);

  PlayAnim(mPlayingAnimID, randSpeedAdjust);
}

bool 
cAS_Fidget::Update(float time)
{
  if (cAS_Stand::Update(time))
  {
    return true;
  }

  SyCSprite* sprite = GetSprite();
  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  return false;
}

//------------------------------------ cAS_Running

cAS_Running::cAS_Running(int32 walkAnimID, int32 runAnimID) 
: mWalkAnimID(walkAnimID),
  mRunAnimID(runAnimID)
{
}

void 
cAS_Running::Enter()
{
  SyCSprite* sprite = GetSprite();
  SyScene *scene = GetScene();
  int curIndex;

  mPlaybackIndex = -1;
  mWalkPlaybackIndex = -1;
  mBlendIndex = -1;
  mOwner->ClearImpact();
  if (sprite->GetNumPlay() > 0)
  {
    mBlendIndex = sprite->GetFirstPlay();
    sprite->SetPlaybackToCurrentPose(mBlendIndex,*scene); 

    while (sprite->GetNumPlay()>1)
    {
      curIndex = sprite->GetFirstPlay();
      if (curIndex == mBlendIndex)
      {
        curIndex = sprite->GetNextPlay(curIndex);
      }
      sprite->ErasePlay(curIndex,*scene);
    }

    if (sprite->HasAnim(mWalkAnimID) && sprite->HasAnim(mRunAnimID))
    {
      mPlaybackIndex = sprite->PrependAnimPlay(mRunAnimID,0,*scene);
      mWalkPlaybackIndex     = sprite->PrependAnimPlayWithSync(mWalkAnimID,0,mPlaybackIndex,*scene);
      cAnimCharControllerInput *input = GetInput();     
      float blendValue = input->mSpeedRequest;      

      cPhysicsAnimated *physics = (cPhysicsAnimated*) GetGameObject()->GetPhysics();

      if (physics->IsPushing())
      {
        blendValue = 0.0f; // can only walk while pushing
      }
      sprite->SetPlaybackBlend(mPlaybackIndex,blendValue);
      mCurrentBlend = blendValue;

    }
    else
    {
      GAME_ASSERT(ERROR_ART, false,"Character Missing Run or Walk Animation");
    }

    sprite->SetPlaybackBlendMode(mBlendIndex,SYANIMBLENDMODE_INVERT_TIMED_INTERP,250,*scene);
  }
  else
  {
    if (sprite->HasAnim(mWalkAnimID) && sprite->HasAnim(mRunAnimID))
    {
      mPlaybackIndex         = sprite->PrependAnimPlay(mRunAnimID,0,*scene);
      mWalkPlaybackIndex     = sprite->PrependAnimPlayWithSync(mWalkAnimID,0,mPlaybackIndex,*scene);
      cAnimCharControllerInput *input = GetInput();
      float blendValue = input->mSpeedRequest;
      sprite->SetPlaybackBlend(mPlaybackIndex,blendValue);
    }
    else
    {
      GAME_ASSERT(ERROR_ART, false, "Character Missing Animation");
    }
  }
}

bool 
cAS_Running::Update(float time)
{
  // loop anim...  Is this necessary?
  SyCSprite* sprite = GetSprite();
  SyScene *scene = GetScene();

  cAnimCharControllerInput *input = GetInput();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  // stopping?
  if (input->mSpeedRequest < EPSILON)
  {
    mOwner->ChangeState(mOwner->GetCombat() ? AS_COMBAT_IDLE : AS_STAND);
    return true;
  }

  float blendTarget = input->mSpeedRequest;
  cPhysicsAnimated *physics = (cPhysicsAnimated*) GetGameObject()->GetPhysics();

  if (physics->IsPushing())
  {
    blendTarget = 0.0f; // can only walk while pushing
  }

  float blendAmount = smBlendSpeed *time;

  if (mCurrentBlend + blendAmount < blendTarget  )
  {
    mCurrentBlend += blendAmount;
  }
  else if (mCurrentBlend - blendAmount > blendTarget)
  {
    mCurrentBlend -= blendAmount;
  }
  else
  {
    mCurrentBlend = blendTarget;
  }
  sprite->SetPlaybackBlend(mPlaybackIndex,mCurrentBlend);
  float playspeed = (mCurrentBlend * (smRunSpeed-smWalkSpeed)) + smWalkSpeed;

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = (float)(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcMovementSpeed())*0.01f;
  playspeed = playspeed + (playspeed * (speedMult - 1.0f));

  if (mWalkPlaybackIndex >= 0)
  {
    sprite->SetPlaySpeed(mWalkPlaybackIndex,playspeed,*scene);
  }
  if (mPlaybackIndex >= 0)
  {
    sprite->SetPlaySpeed(mPlaybackIndex,playspeed,*scene);
  }

  // check for turn...
  float delta_ang = TurnTo(input->mHeadingRequest,time*smTurnSpeed,smTurnThreshold);
  if (delta_ang > smStopThreshold)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}

void 
cAS_Running::RegisterTuningVariables()
{
  gTuningSys.AddAngle(&smTurnThreshold,"Run_Turn_Threshold");
  gTuningSys.AddAngle(&smTurnSpeed,"Run_Turn_Speed");
  gTuningSys.AddAngle(&smStopThreshold,"Run_Stop_Threshold");
  gTuningSys.AddFloat(&smRunSpeed,"Run_Speed");
  gTuningSys.AddFloat(&smWalkSpeed,"Walk_Speed");
}

//------------------------------------ cAS_WalkWithFacing

cAS_WalkWithFacing::cAS_WalkWithFacing() 
: mPlaybackIndex2(-1),
  mWalkAnimID(-1),
  mWalkAnimID2(-1)
{
}

void
cAS_WalkWithFacing::PlayWalkAnims(float faceHeading)
{
  cAnimCharControllerInput* input = GetInput();
  SyCSprite* sprite = GetSprite();
  SyScene* scene = GetScene();

  float delta = AngleDifference(input->mHeadingRequest, faceHeading);

  if (!sprite->HasAnim(ANIM_WALK_STRAFE_LEFT) ||
      !sprite->HasAnim(ANIM_WALK_STRAFE_RIGHT) ||
      !sprite->HasAnim(ANIM_WALK_BACKPEDAL))
  {
    return;
  }
  
  int walkAnim1, walkAnim2;

  const float NON_BLEND_AREA = SY_PI*0.1667f;
  const float BLEND_AREA = SY_PI*0.5f - (NON_BLEND_AREA*2.0f);
  float blend = 1.0f;

  if (delta < 0.0f)
  {
    delta = SY_PI*2.0f + delta;
  }

  if (delta < NON_BLEND_AREA || delta >= SY_PI*1.5f+BLEND_AREA+NON_BLEND_AREA)
  {
    walkAnim1 = walkAnim2 = ANIM_B_MOVE_F_WALK;
    blend = 1.0f;
  }
  else if (delta >= NON_BLEND_AREA && delta < BLEND_AREA+NON_BLEND_AREA)
  {
    walkAnim1 = ANIM_B_MOVE_F_WALK;
    walkAnim2 = ANIM_WALK_STRAFE_LEFT;
    blend = 1.0f - (delta-NON_BLEND_AREA)/BLEND_AREA;
  }
  else if (delta >= BLEND_AREA+NON_BLEND_AREA && delta < SY_PI*0.5f+NON_BLEND_AREA)
  {
    walkAnim1 = walkAnim2 = ANIM_WALK_STRAFE_LEFT;
    blend = 1.0f;
  }
  else if (delta >= SY_PI*0.5f+NON_BLEND_AREA && delta < SY_PI*0.5f+BLEND_AREA+NON_BLEND_AREA)
  {
    walkAnim1 = ANIM_WALK_STRAFE_LEFT;
    walkAnim2 = ANIM_WALK_BACKPEDAL;
    blend = 1.0f - (delta-(SY_PI*0.5f+NON_BLEND_AREA))/BLEND_AREA;
  }
  else if (delta >= SY_PI*0.5f+BLEND_AREA+NON_BLEND_AREA && delta < SY_PI+NON_BLEND_AREA)
  {
    walkAnim1 = walkAnim2 = ANIM_WALK_BACKPEDAL;
    blend = 1.0f;
  }
  else if (delta >= SY_PI+NON_BLEND_AREA && delta < SY_PI+BLEND_AREA+NON_BLEND_AREA)
  {
    walkAnim1 = ANIM_WALK_BACKPEDAL;
    walkAnim2 = ANIM_WALK_STRAFE_RIGHT;
    blend = 1.0f - (delta-(SY_PI+NON_BLEND_AREA))/BLEND_AREA;
  }
  else if (delta >= SY_PI+BLEND_AREA+NON_BLEND_AREA && delta < SY_PI*1.5f+NON_BLEND_AREA)
  {
    walkAnim1 = walkAnim2 = ANIM_WALK_STRAFE_RIGHT;
    blend = 1.0f;
  }
  else if (delta >= SY_PI*1.5f+NON_BLEND_AREA && delta < SY_PI*1.5f+BLEND_AREA+NON_BLEND_AREA)
  {
    walkAnim1 = ANIM_WALK_STRAFE_RIGHT;
    walkAnim2 = ANIM_B_MOVE_F_WALK;
    blend = 1.0f - (delta-(SY_PI*1.5f+NON_BLEND_AREA))/BLEND_AREA;
  }
  else
  {
    walkAnim1 = walkAnim2 = ANIM_B_MOVE_F_WALK;
    blend = 1.0f;
  }

  SyAssert((-1 == mPlaybackIndex && -1 == mWalkAnimID) || (-1 != mPlaybackIndex && -1 != mWalkAnimID));
  SyAssert((-1 == mPlaybackIndex2 && -1 == mWalkAnimID2) || (-1 != mPlaybackIndex2 && -1 != mWalkAnimID2));

  if (-1 != mPlaybackIndex && mWalkAnimID != walkAnim1 && mWalkAnimID != walkAnim2)
  {
    sprite->ErasePlay(mPlaybackIndex,*scene);
    mWalkAnimID = mWalkAnimID2;
    mPlaybackIndex = mPlaybackIndex2;
    mPlaybackIndex2 = -1;
    mWalkAnimID2 = -1;

    if (-1 != mPlaybackIndex && mWalkAnimID != walkAnim1 && mWalkAnimID != walkAnim2)
    {
      sprite->ErasePlay(mPlaybackIndex,*scene);
      mWalkAnimID = -1;
      mPlaybackIndex = -1;
    }
  }

  if (-1 != mPlaybackIndex2 && mWalkAnimID2 != walkAnim1 && mWalkAnimID2 != walkAnim2)
  {
    sprite->ErasePlay(mPlaybackIndex2,*scene);
    mWalkAnimID2 = -1;
    mPlaybackIndex2 = -1;
  }

  if ((mWalkAnimID != walkAnim1 && mWalkAnimID == walkAnim2) ||
      (mWalkAnimID2 != walkAnim2 && mWalkAnimID2 == walkAnim1 && mWalkAnimID != walkAnim1)) 
  {
    int swap = walkAnim1;
    walkAnim1 = walkAnim2;
    walkAnim2 = swap;
    blend = 1.0f - blend;
  }

  if ((walkAnim1 == mWalkAnimID || walkAnim1 == mWalkAnimID2) &&
      (walkAnim2 == mWalkAnimID || walkAnim2 == mWalkAnimID2))
  {
    sprite->SetPlaybackBlend(mPlaybackIndex, blend);
    return;
  }

  mBlendIndex = -1;
  mOwner->ClearImpact();
  if (sprite->GetNumPlay() > 0)
  {
    mBlendIndex = sprite->GetFirstPlay();
    sprite->SetPlaybackToCurrentPose(mBlendIndex, *scene); 

    while (sprite->GetNumPlay()>1)
    {
      int curIndex = sprite->GetFirstPlay();
      if (curIndex == mBlendIndex || curIndex == mPlaybackIndex)
      {
        curIndex = sprite->GetNextPlay(curIndex);
      }
      sprite->ErasePlay(curIndex,*scene);
    }
  }

  mWalkAnimID = walkAnim1;
  mPlaybackIndex = sprite->PrependAnimPlay(walkAnim1,0,*scene);

  if (walkAnim2 != walkAnim1)
  {
    mWalkAnimID2 = walkAnim2;
//    mPlaybackIndex2 = sprite->PrependAnimPlayWithSync(walkAnim2,0,mPlaybackIndex,*scene);
    mPlaybackIndex2 = sprite->PrependAnimPlay(walkAnim2,0,*scene);
    if (-1 == mPlaybackIndex2)
    {
      mWalkAnimID2 = -1;
    }
    sprite->SetPlaybackBlend(mPlaybackIndex,blend);
  }
  else
  {
    mWalkAnimID2 = -1;
    mPlaybackIndex2 = -1;
  }

  if (-1 != mBlendIndex)
  {
    sprite->SetPlaybackBlendMode(mBlendIndex,SYANIMBLENDMODE_INVERT_TIMED_INTERP,500,*scene);
  }
  SyAssert((-1 == mPlaybackIndex && -1 == mWalkAnimID) || (-1 != mPlaybackIndex && -1 != mWalkAnimID));
  SyAssert((-1 == mPlaybackIndex2 && -1 == mWalkAnimID2) || (-1 != mPlaybackIndex2 && -1 != mWalkAnimID2));

}

void 
cAS_WalkWithFacing::Enter()
{
  cAnimCharControllerInput* input = GetInput();
  mWalkAnimID = -1;
  mWalkAnimID2 = -1;
  mPlaybackIndex = -1;
  mPlaybackIndex2 = -1;

  PlayWalkAnims(input->mFaceHeading);
}

void 
cAS_WalkWithFacing::Exit()
{
  cAnimCharControllerInput* input = GetInput();
  cGameObject* obj = GetGameObject();

  input->mHeadingRequest = obj->GetHeading();
}

bool 
cAS_WalkWithFacing::Update(float time)
{
  // loop anim...  Is this necessary?
  SyCSprite* sprite = GetSprite();
  cAnimCharControllerInput *input = GetInput();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  // stopping?
  if (input->mSpeedRequest < EPSILON)
  {
    mOwner->ChangeState(mOwner->GetCombat() ? AS_COMBAT_IDLE : AS_STAND);
    return true;
  }

  TurnTo(input->mFaceHeading, 10000.0f, EPSILON);

  PlayWalkAnims(input->mFaceHeading);

  float playspeed = input->mSpeedRequest;

  if (mPlaybackIndex != -1)
  {
    cAnimControllerSetting* setting = GetSetting();

    SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
    float speedMult = (float)(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcMovementSpeed())*0.01f;
    playspeed = playspeed + (playspeed * (speedMult - 1.0f));

    if (setting)
    {
      playspeed *= setting->mSpeed;
    }

    sprite->SetPlaySpeed(mPlaybackIndex, playspeed, *GetScene());
  }

  return false;
}


//------------------------------------ cAS_RunWithProp

void
cAS_RunWithProp::Enter()
{
  mPropID = GetInput()->mActionTarget;

  cAS_Running::Enter();
}

bool
cAS_RunWithProp::UpdateActions(float time)
{
  cAnimCharControllerInput *input = GetInput();

  if (input->mAttackRequestRanged)
  { 
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_THROW_PROP);
    return true;
  }

  if (input->mActionRequest)
  { 
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_PUTDOWN_PROP);
    return true;
  }

  return false;
}

bool 
cAS_RunWithProp::Update(float time)
{
  // loop anim...  Is this necessary?
  SyCSprite* sprite = GetSprite();
  SyScene *scene = GetScene();

  cAnimCharControllerInput *input = GetInput();

  cGameObject* pProp = GetGameObject()->GetRegistry()->Fetch(mPropID);
  if (!pProp || pProp->GetStats()->IsDead())
  {
    mOwner->ChangeState(AS_RUN);
    return true;
  }

  if (UpdateHitReactionStateChanges())
  {
    pProp->SetCarriedBy(ID_NONE);
    pProp->GetPhysics()->Nudge(); // make sure it falls down
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  // stopping?
  if (input->mSpeedRequest < EPSILON)
  {
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_STAND_CARRY_PROP);
    return true;
  }


  SyAssert(prop_cast<cStatsProp*>(pProp->GetStats()));
  float weight = static_cast<cStatsProp*>(pProp->GetStats())->GetMaster()->mWeight;
  weight = SY_CLAMP(weight, 0.0f, 100.0f);

  float blendTarget = 1.0f - weight/100.0f; // play more of run anim if prop is light
  float blendAmount = smBlendSpeed *time;

  if (mCurrentBlend + blendAmount < blendTarget  )
  {
    mCurrentBlend += blendAmount;
  }
  else if (mCurrentBlend - blendAmount > blendTarget)
  {
    mCurrentBlend -= blendAmount;
  }
  else
  {
    mCurrentBlend = blendTarget;
  }

  if (mPlaybackIndex >= 0)
  {
    sprite->SetPlaybackBlend(mPlaybackIndex, mCurrentBlend);
  }

  float playspeed = (input->mSpeedRequest * (smRunSpeed-smWalkSpeed)) + smWalkSpeed;
  cPhysicsAnimated *physics = (cPhysicsAnimated*) GetGameObject()->GetPhysics();

  if (physics->IsPushing())
  {
    playspeed = smWalkSpeed; // can only walk while pushing
  }

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = (float)(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcMovementSpeed())*0.01f;
  playspeed = playspeed + (playspeed * (speedMult - 1.0f));

  if (mWalkPlaybackIndex >= 0)
  {
    sprite->SetPlaySpeed(mWalkPlaybackIndex, playspeed, *scene);
  }
  if (mPlaybackIndex >= 0)
  {
    sprite->SetPlaySpeed(mPlaybackIndex, playspeed, *scene);
  }

  // check for turn...
  float delta_ang = TurnTo(input->mHeadingRequest,time*smTurnSpeed,smTurnThreshold);
  if (delta_ang > smStopThreshold)
  {
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_STAND_CARRY_PROP);
    return true;
  }
  return false;
}

//------------------------------------ cAS_Standard

void 
cAS_Standard::Enter()
{
  ClearInput();
  PlayAnim(mAnimID);
}

bool 
cAS_Standard::Update(float time)
{
  SyCSprite* sprite = GetSprite();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  UpdatePivot(time);

  cAnimControllerSetting* setting = GetSetting();
  float prevTime = mOwner->GetAnimTime();
  float curTime = prevTime + time;
  float noInterruptEnd = setting ? setting->mNoInterruptEnd : 1.0f;
  noInterruptEnd *= mAnimDuration;

  if (curTime <= noInterruptEnd)
  {
    ClearInput();
  }

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex) != 0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}

//------------------------------------ cAS_Land

void 
cAS_Land::Enter()
{
  mTurnSpeed = smTurnSpeed;

  PlayAnim(mAnimID, 1.0f);

  /* plunk */
  SyVect3 v(mOwner->GetOwner()->GetLocation());
  v.Y -= 0.5f;
  SyWaterSystem * pWaterSystem = mOwner->GetOwner()->GetTitan()->GetScene()->GetWaterSystem();
  if (pWaterSystem) 
  {
    pWaterSystem->DemoPlunk( v, 8.00f );
  }
}

bool 
cAS_Land::Update(float time)
{
  SyCSprite* sprite = GetSprite();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  UpdatePivot(time);

  cAnimControllerSetting* setting = GetSetting();
  float prevTime = mOwner->GetAnimTime();
  float curTime = prevTime + time;
  float actionFrame = setting ? setting->mActionFrame : 0.5f;
  actionFrame *= mAnimDuration;

  ClearInput();

  if (prevTime <= actionFrame && curTime > actionFrame)
  {
    if (setting && ID_NONE != setting->mSpellID)
    {
      cGameObject* pObj = GetGameObject();
      const cSpellMaster* pSpell = pObj->GetTitan()->GetDatabaseSys()->GetSpellMaster(setting->mSpellID);
      GAME_ASSERT(ERROR_DESIGN, pSpell != NULL, "Could not find spell master for attack");

      if (pSpell)
      {
        pSpell->CastSpell(pObj, NULL, NULL, COMBO_JUMPATTACK);
      }

//      ImpactSurface();
    }
  }

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex) != 0)
  {
    if (ANIM_ATTACK_JUMP_LAND == mAnimID)
    {
      mOwner->ChangeState(AS_COMBAT_IDLE);
    }
    else
    {
      mOwner->ChangeState(AS_STAND);
    }

    return true;
  }

  return false;
}

//------------------------------------ cAS_HitReact
int32 cAS_HitReact::GetHitAnim(eAnimState prevState, SyCSprite* pSprite, float deltaHeading)
{
  if (AS_BLOCK == prevState)
  {
    return ANIM_B_BLOCK_FWD_TAKEDAM;
  }

  if (AS_STAND_CARRY_PROP == prevState ||
      AS_RUN_CARRY_PROP == prevState)
  {
    if (deltaHeading > SY_PI*0.25f && deltaHeading <= SY_PI*0.75f)
    {
      if (pSprite->HasAnim(ANIM_HOLD_PROP_TAKEDMG_RIGHT))
      {
        return ANIM_HOLD_PROP_TAKEDMG_RIGHT;
      }
    }
    else if (deltaHeading < -SY_PI*0.25f && deltaHeading >= -SY_PI*0.75f)
    {
      if (pSprite->HasAnim(ANIM_HOLD_PROP_TAKEDMG_LEFT))
      {
        return ANIM_HOLD_PROP_TAKEDMG_LEFT;
      }
    }
    else if (SY_FABS(deltaHeading) > SY_PI*0.75f)
    {
      if (pSprite->HasAnim(ANIM_HOLD_PROP_TAKEDMG_BACK))
      {
        return ANIM_HOLD_PROP_TAKEDMG_BACK;
      }
    }

    return ANIM_HOLD_PROP_TAKEDMG_FWD;
  }

  if (SY_FABS(deltaHeading) > SY_PI*0.75f)
  {
    bool bHasAnim1 = pSprite->HasAnim(ANIM_B_IMPACT_BACK) != 0;
    bool bHasAnim2 = pSprite->HasAnim(ANIM_B_STAND_TAKEDAM_BCK2) != 0;

    if (bHasAnim1)
    {
      if (bHasAnim2 && Titan::Get()->Random(0, 1) == 1)
      {
        return ANIM_B_STAND_TAKEDAM_BCK2;
      }
      else
      {
        return ANIM_B_IMPACT_BACK;
      }
    }
    else if (bHasAnim2)
    {
      return ANIM_B_STAND_TAKEDAM_BCK2;
    }
  }

  const int numHitAnims = 6;
  int32 hitAnimIDs[numHitAnims] = { ANIM_B_IMPACT_FRONT,
                                    ANIM_B_STAND_TAKEDAM_FWD2,
                                    ANIM_B_STAND_TAKEDAM_FWD3,
                                    ANIM_B_STAND_TAKEDAM_FWD4,
                                    ANIM_B_STAND_TAKEDAM_FWD5,
                                    ANIM_B_STAND_TAKEDAM_FWD6,
                                  };
  bool hasAnims[numHitAnims];

  int numValidAnims = 0;
  int i;

  for (i=0; i<numHitAnims; ++i)
  {
    hasAnims[i] = pSprite->HasAnim(hitAnimIDs[i]) != 0;

    if (hasAnims[i])
    {
      ++numValidAnims;
    }
  }

  if (numValidAnims > 0)
  {
    int selectHitAnim = Titan::Get()->Random(0, numValidAnims-1);
    numValidAnims = 0;

    for (i=0; i<numHitAnims; ++i)
    {
      if (hasAnims[i])
      {
        if (numValidAnims == selectHitAnim)
        {
          return hitAnimIDs[i];
        }

        ++numValidAnims;
      }
    }
  }

  return ANIM_B_IMPACT_FRONT;
}

void
cAS_HitReact::Enter()
{
  SyCSprite* sprite = GetSprite();
  cAnimCharControllerInput* input = GetInput();

  cGameObject* pObj = GetGameObject();
  float heading = pObj->GetHeading();
  float headingTowards = heading;

  cGameObject* pAttacker = pObj->GetTitan()->GetRegistry()->Fetch(input->mHitReactTarget);

  if (pAttacker)
  {
    headingTowards = pObj->GetHeadingTowards(pAttacker);
  }

  mAnimID = GetHitAnim(mOwner->GetPrevAnimState(), sprite, AngleDifference(heading, headingTowards));

  float oldDuration = sprite->GetAnimDuration(mAnimID, *GetScene());
  float playSpeed = 1.0f;

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = (float)(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed())*0.01f;
  
  if (speedMult > 0.0f)
  {
    playSpeed *= speedMult;
  }

  if (input->mHitReactTime > 0.0f)
  {
    playSpeed *= (oldDuration/input->mHitReactTime);
  }

  PlayAnim(mAnimID, playSpeed, true);

  input->mHitReact = false;
  input->mHitReactTime = 0.0f;
}

bool
cAS_HitReact::Update(float time)
{ 
  SyCSprite* sprite = GetSprite();
  cAnimCharControllerInput* input = GetInput();
  cAnimControllerSetting* setting = GetSetting();

  float noInterruptEnd = 1.0f;

  if (setting)
  {
    noInterruptEnd = setting->mNoInterruptEnd;
  }

  noInterruptEnd *= mAnimDuration;

  if (input->mbKnockback)
  {
    mOwner->ChangeState(AS_KNOCKBACK);
    return true;
  }

  if (input->mbKnockforward)
  {
    mOwner->ChangeState(AS_KNOCKFORWARD);
    return true;
  }

  if (input->mHitReact)
  {
    input->mHitReact = false;

    float oldDuration = sprite->GetAnimDuration(mAnimID, *GetScene());
    float playSpeed = 1.0f;

    SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
    float speedMult = (float)(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed())*0.01f;

    if (speedMult > 0.0f)
    {
      playSpeed *= speedMult;
    }

    if (input->mHitReactTime > 0.0f)
    {
      playSpeed *= (oldDuration/input->mHitReactTime);
    }

    PlayAnim(GetHitAnim(AS_HITREACT, sprite, 0.0f), playSpeed, true);

//    float curTime = mOwner->GetAnimTime()+time;
//    if (curTime > noInterruptEnd)
//    {
//      mOwner->ChangeState(AS_HITREACT);
//      return true;
//    }
//    else
//    {
//      input->mHitReact = false;
//    }
  }

  if (input->mbThrown)
  {
    input->mbThrown = false;
    mOwner->ChangeState(AS_THROWN_BY_CHARACTER);
    return true;
  }

  if (input->mFall)
  {
    input->mFall = false;
    mOwner->ChangeState(AS_IN_AIR);
    return true;
  }

  if (input->mDeath)
  {
    cGameObject* pObj = GetGameObject();
    cGameObject* pKiller = pObj->GetTitan()->GetRegistry()->Fetch(input->mHitReactTarget);
    cGraphicCharacter* pKillerGraphic = pKiller ? prop_cast<cGraphicCharacter*>(pKiller->GetGraphic()) : NULL;
    eComboType killerCombo = pKillerGraphic ? pKillerGraphic->GetAnimController()->GetComboType() : NUM_COMBOS;

    if (pObj->GetType() != cGameObject::OBJ_NPC ||
        !pKillerGraphic || 
        !pKillerGraphic->GetAnimController()->IsAttacking() ||
        !pKillerGraphic->GetAnimController()->IsAnimStateActive() ||
        COMBO_L == killerCombo || COMBO_LL == killerCombo || COMBO_LLL == killerCombo || COMBO_LLLL == killerCombo)
    {
      input->mDeath = false;
      mOwner->ChangeState(AS_DEATH);
      return true;
    }
  }

  if (UpdateActions(time))
  {
    return true;
  }

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex) != 0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}
//------------------------------------ cAS_Attack
bool
cAS_Attack::AcceptTransition()
{
  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(GetGameObject()->GetStats());
  bool bCanAttack = pStats->CalcAttackSpeed() > 0 &&
                    !pStats->QueryFlag("Bound");

  if (!bCanAttack)
  {
    ClearInput();
  }

  return bCanAttack;
}

bool 
cAS_Attack::IsActive()
{
  cAnimControllerSetting * setting = GetSetting();
  float stateTime = mOwner->GetStateTime();
  float attackTime = 0.5f;

  if (setting != NULL)
  {
    attackTime = setting->mActionFrameEnd;
  }

  attackTime *= mAnimDuration;

  if (stateTime <= attackTime)
  {
    return true;
  }
  
  eAnimState nextComboL = GetNextCombo_Light();
  eAnimState nextComboS = GetNextCombo_Strong();

  if ((nextComboL != AS_ATTACK_L && nextComboL != NUM_ANIM_STATES) || 
      (nextComboS != AS_ATTACK_H && nextComboS != NUM_ANIM_STATES))
  {
    return stateTime <= attackTime || mComboL || mComboS;
  }

  return false;
}

void
cAS_Attack::ImpactSurface()
{
  cAnimControllerSetting* setting = GetSetting();

  if (!setting || ID_NONE == setting->mSpellID)
  {
    return;
  }

  cGameObject* pObj = GetGameObject();
  const cSpellMaster* pSpell = pObj->GetTitan()->GetDatabaseSys()->GetSpellMaster(setting->mSpellID);
  SyAssert(pSpell != NULL);

  if (!pSpell || ID_NONE == pSpell->mImpactFXSetID)
  {
    return;
  }

  GetImpactTestLocation(mLastHandLoc);

  mbImpact = true;
}  

void cAS_Attack::GetImpactTestLocation(SyVect3& testLoc)
{
  cAnimControllerSetting* setting = GetSetting();
  cGameObject* pObj = GetGameObject();

  testLoc = pObj->GetLocation();
  testLoc.Y += 1.5f;

  if (!setting || ID_NONE == setting->mSpellID)
  {
    return;
  }
  
  if (static_cast<cInventoryCharacter*>(pObj->GetInventory())->GetEquippedItem(EQUIP_MELEE) != NULL)
  {
    SyMatrix44 mat;
    if (static_cast<cGraphicCharacter*>(pObj->GetGraphic())->GetEquipSlotTransform(EQUIP_MELEE, &mat))
    {
      SyMatrix44 rot;
      SyVect3 offset;
      mat.ConvertToRotate(rot);
      rot.Mul(SyVect3(0.0f, 0.0f, 0.75f), offset);
      mat.GetTranslation(testLoc);
      testLoc += offset;
    }
  }
  else
  {
    SyVect3 contactNodeLoc;
    if (static_cast<cGraphicCharacter*>(pObj->GetGraphic())->GetIdentNodeLocation(setting->mContactNode, &contactNodeLoc))
    {
      testLoc = contactNodeLoc;
    }
  }
}

class cSurfaceImpactFilter : public SySceneFilter
{
public:

  /* Filtering */
  virtual int FilterActor( SyScene& Scene, SyActorHandle ActorHandle );  
};

int cSurfaceImpactFilter :: FilterActor( SyScene& Scene, SyActorHandle ActorHandle )
{
  if (ActorHandle == Self)
  {
    return (1);
  }

  if((Scene.GetActorSpriteType(ActorHandle) == SYSPRITETYPE_CSPRITE))
  {
    int32 userID = Scene.GetActorID(ActorHandle);

    if (-1 == userID || ID_NONE == userID)
    {
      return (0);
    }

    cGameObject* pObj = Titan::Get()->GetRegistry()->Fetch((tGameObjectID)userID);
    if (pObj && pObj->GetType() != cGameObject::OBJ_PROP)
    {
      return (1);
    }
  }

  return(0);
}

class cGameObjectFilter : public SySceneFilter
{
public:

  /* Filtering */
  virtual int FilterActor( SyScene& Scene, SyActorHandle ActorHandle );  
};

int cGameObjectFilter :: FilterActor( SyScene& Scene, SyActorHandle ActorHandle )
{
  if (ActorHandle == Self)
  {
    return (1);
  }

  int32 userID = Scene.GetActorID(ActorHandle);

  if (-1 == userID || ID_NONE == userID)
  {
    return (0);
  }

  cGameObject* pObj = Titan::Get()->GetRegistry()->Fetch((tGameObjectID)userID);
  if (pObj)
  {
    return (1);
  }

  return(0);
}

void
cAS_Attack::PlaySurfaceImpactFX()
{
  if (mbImpact)
  {
    mbImpact = false;

    cAnimControllerSetting* setting = GetSetting();

    if (!setting || ID_NONE == setting->mSpellID)
    {
      return;
    }

    cGameObject* pObj = GetGameObject();
    SyScene* pScene = GetScene();
    SyActorHandle actorHandle = pObj->GetGraphic()->GetActorHandle();

    const cSpellMaster* pSpell = pObj->GetTitan()->GetDatabaseSys()->GetSpellMaster(setting->mSpellID);
    SyAssert(pSpell != NULL);

    if (!pSpell || ID_NONE == pSpell->mImpactFXSetID)
    {
      return;
    }

    const cImpactFXSet* pImpacts = pObj->GetTitan()->GetDatabaseSys()->GetImpactFXSet(pSpell->mImpactFXSetID);
    GAME_ASSERT(ERROR_DESIGN, pImpacts!=NULL, "Could not find impact effect set for spell %s", pSpell->mID.GetName());

    if (!pImpacts)
    {
      return;
    }

    SyVect3 testLoc;
    GetImpactTestLocation(testLoc);

    //
    // Test for collision to play impact effect
    //
    int water = pObj->GetTitan()->GetScene()->GetWaterSystem()->Plunk(testLoc-SyVect3(0.0f, 0.5f, 0.0f), 0.0f);

    SyCollSphere sphere;
    cSurfaceImpactFilter filter;
    SyVect3 start, end;

    SyVect3 myDir;
    myDir.HPR(pObj->GetHeading(), 0.0f, 0.0f);
    myDir.Normalize();

    start = pObj->GetLocation();
    start.Y += 1.5f;
    end = start;
    end.MulAdd(myDir, (start-testLoc).Magnitude());

    sphere.Init(start, end, 0.25f);
    filter.Init(actorHandle);

    if (pScene->Collide(sphere, filter))
    {
      DEBUGOVERLAY_DRAWSPHERE(pObj->GetID(), "Anim", sphere.GetStart(), sphere.GetRadius(), cDebugOverlay::RED);
      DEBUGOVERLAY_DRAWSPHERE(pObj->GetID(), "Anim", sphere.GetEnd(), sphere.GetRadius(), cDebugOverlay::RED);
      pSpell->PlayImpact(pObj->GetTitan(), sphere, filter, water);
    }
    else
    {
      start = end = testLoc;
      end = testLoc;
      start.Y += 0.5f;
      end.Y -= 0.5f;
      sphere.Init(start, end, 0.1f);
      filter.Init(actorHandle);

      if (pScene->Collide(sphere, filter) &&
          start.Distance(sphere.GetHitPoint()) <= 0.75f)
      {
        DEBUGOVERLAY_DRAWSPHERE(pObj->GetID(), "Anim", sphere.GetStart(), sphere.GetRadius(), cDebugOverlay::RED);
        DEBUGOVERLAY_DRAWSPHERE(pObj->GetID(), "Anim", sphere.GetEnd(), sphere.GetRadius(), cDebugOverlay::RED);
        pSpell->PlayImpact(pObj->GetTitan(), sphere, filter, water);
      }
      else
      {
        DEBUGOVERLAY_DRAWSPHERE(pObj->GetID(), "Anim", sphere.GetStart(), sphere.GetRadius(), cDebugOverlay::GREEN);
        DEBUGOVERLAY_DRAWSPHERE(pObj->GetID(), "Anim", sphere.GetEnd(), sphere.GetRadius(), cDebugOverlay::GREEN);
      }
    }
  }
}

void 
cAS_Attack::Enter()
{
  cAnimCharControllerInput *input = GetInput();
  cAnimControllerSetting* setting = GetSetting();
  cGameObject* obj = GetGameObject();

  mTarget = input->mTarget;
  mTargetAngle = input->mTargetHeading;

  input->mAttackRequestL = false;
  input->mAttackRequestS = false;
  input->mNPCComboOverride = NUM_COMBOS;
  mComboL = false;
  mComboS = false;
  mNPCComboOverride = NUM_COMBOS;
  mbImpact = false;

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed() * 0.01f;
  
  PlayAnim(mAnimID, speedMult);

  const cSpellMaster* pSpell = !setting ? NULL : obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(setting->mSpellID);

  mCastFXHandle = -1;

  if (pSpell && ID_NONE != pSpell->mCastStartFXID)
  {
    SyDictionary *pDictionary = obj->GetTitan()->GetScene()->GetDictionary();
    int32         scriptHandle;
    SyActorHandle actorHandle = obj->GetGraphic()->GetActorHandle();

    if(pDictionary->FindTyped( pSpell->mCastStartFXID, SYRESOURCETYPE_FXSCRIPT, scriptHandle))
    {
      mCastFXHandle = obj->GetTitan()->GetScene()->GetFXScriptSystem()->PlayScript(scriptHandle, 1, &actorHandle);
    }
  }
}

void 
cAS_Attack::Exit()
{
  cAnimCharControllerInput *input = GetInput();
  input->mAttackRequestL = false;
  input->mAttackRequestS = false;

  cAnimControllerSetting *setting = GetSetting();
  if (setting && ID_NONE != setting->mWeaponFXID)
  {
    cGraphicCharacter* pGraphic = static_cast<cGraphicCharacter*>(GetGameObject()->GetGraphic());
    pGraphic->RemoveItemEffect(EQUIP_MELEE, setting->mWeaponFXID);
  }

  if (-1 != mCastFXHandle)
  {
    float actionFrame = 0.5f;

    if (setting)
    {
      actionFrame = setting->mActionFrame;
    }

    actionFrame *= mAnimDuration;

    if (mOwner->GetAnimTime() < actionFrame)
    {
      GetScene()->GetFXScriptSystem()->StopPlayback(mCastFXHandle);
      mCastFXHandle = -1;
    }
  }
}

bool 
cAS_Attack::Update(float time)
{
  PlaySurfaceImpactFX();

  SyCSprite* sprite = GetSprite();

  cAnimCharControllerInput *input = GetInput();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  cGameObject *obj = GetGameObject();

  if (mTarget != ID_NONE)
  {
    cGameObject *target = obj->GetRegistry()->Fetch(input->mTarget);
    if (target != NULL)
    {
      cAnimControllerSetting* setting = GetSetting();
      float turnSpeed = setting ? setting->mTurnSpeed : mOwner->smActionDefaultTurnSpeed;
      float heading = obj->GetHeadingTowards(target);
      TurnTo(heading, time*turnSpeed, 0.0f);
    }
  }
  else
  {
    UpdatePivot(time);
  }

  float prevTime = mOwner->GetStateTime();
  float curTime = prevTime + time;
  cAnimControllerSetting * setting = GetSetting();

  float attackTime = 0.5f;
  float comboInputStart = 0.0f;
  float comboAnimStart = 0.8f;
  float comboEnd = 1.0f;
  float weaponFXStart = 0.0f;
  float weaponFXEnd = 1.0f;
  
  if (setting != NULL)
  {
    attackTime = setting->mActionFrame;
    comboInputStart = setting->mComboInputStart;
    comboAnimStart = setting->mComboAnimStart;
    comboEnd = setting->mComboEnd;
    weaponFXStart = setting->mWeaponFXStart;
    weaponFXEnd = setting->mWeaponFXEnd;
  }

  attackTime *= mAnimDuration;
  comboInputStart *= mAnimDuration;
  comboAnimStart *= mAnimDuration;
  comboEnd *= mAnimDuration;
  weaponFXStart *= mAnimDuration;
  weaponFXEnd *= mAnimDuration;

  // play weapon FX during attack if there are any
  if (setting && ID_NONE != setting->mWeaponFXID)
  {
    cGraphicCharacter* pGraphic = static_cast<cGraphicCharacter*>(obj->GetGraphic());

    if (prevTime <= weaponFXStart && curTime > weaponFXStart)
    {
      pGraphic->AddItemEffect(EQUIP_MELEE, setting->mWeaponFXID);
    }
    else if (prevTime <= weaponFXEnd && curTime > weaponFXEnd)
    {
      pGraphic->RemoveItemEffect(EQUIP_MELEE, setting->mWeaponFXID);
    }
  }

  if (prevTime < attackTime && curTime >= attackTime)
  {
    // hit event occurs this time
    AttackFrame();
  }

  if (curTime < comboInputStart)
  {
    // ignore button presses before window
    input->mAttackRequestL = false;
    input->mAttackRequestS = false;
  }
  else
  {
    if (curTime <= comboEnd)
    {
      if (obj->GetType() == cGameObject::OBJ_NPC &&
          (input->mAttackRequestL || input->mAttackRequestS) &&
          input->mNPCComboOverride != NUM_COMBOS)
      {
        mComboL = true;
        mComboS = false;
        mNPCComboOverride = input->mNPCComboOverride;
        input->mNPCComboOverride = NUM_COMBOS;
      }
      if (input->mAttackRequestL)
      {
        mComboL = true;
        mComboS = false;
        input->mAttackRequestL = false;
      }
      else if (input->mAttackRequestS)
      {
        mComboS = true;
        mComboL = false;
        input->mAttackRequestS = false;
      }
    }
  
    if (curTime >= comboAnimStart && (prevTime < comboAnimStart || curTime <= comboEnd))
    {
      eAnimState next_combo = NUM_ANIM_STATES;
      
      if (obj->GetType() == cGameObject::OBJ_NPC &&
          mComboL && mNPCComboOverride != NUM_COMBOS)
      {
        next_combo = mOwner->GetAnimStateFromComboType(mNPCComboOverride);
      }
      else if (mComboL)
      {
        next_combo = GetNextCombo_Light();
      }
      else if (mComboS)
      {
        next_combo = GetNextCombo_Strong();
      }

      if (next_combo != NUM_ANIM_STATES)
      {
        mOwner->ChangeState(next_combo);
        return true;
      }
      else
      {
        mComboS = false;
        mComboL = false;
      }
    }
  }   

  if (UpdateActions(time))
  {
    return true;
  }

  ClearInput();

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex) != 0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}


void
cAS_Attack::AttackFrame()
{
  cAnimControllerSetting *setting = GetSetting();
  cAnimCharControllerInput *input = GetInput();
  cGameObject *obj = GetGameObject();

  GAME_ASSERT(ERROR_DESIGN, setting!=NULL, "Attack animation %s for class %s has no setting in the db, all attacks must be tuned appropriately.", mOwner->GetAnimStateName(), static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->GetClass()->mName.GetName());

  if (setting == NULL || ID_NONE == setting->mSpellID)
  {
    if (input->mTarget != ID_NONE)
    {
      ((cStatsCharacter *)obj->GetStats())->AttackFrame(input->mTarget,mAttackIndex);
    }
  }
  else
  {
    const cSpellMaster* pSpell = obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(setting->mSpellID);
    cGameObject* pTarget = obj->GetTitan()->GetRegistry()->Fetch(input->mTarget);

    GAME_ASSERT(ERROR_DESIGN, pSpell != NULL, "Could not find spell master for attack");

    if (pSpell)
    {
      pSpell->CastSpell(obj, pTarget, NULL, mAttackIndex);
    }

    ImpactSurface();
  }
}


eAnimState 
cAS_Attack::GetNextCombo_Strong()
{
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(GetGameObject()->GetStats());

  switch(mOwner->GetAnimState())
  {
    case AS_ATTACK_L:
    {
      if (pStats->HasAbilityCombo(COMBO_LH) ||
          pStats->HasAbilityCombo(COMBO_LHH) ||
          pStats->HasAbilityCombo(COMBO_LHL))
      {
        return AS_ATTACK_LH;
      }
      else
      {
        return NUM_ANIM_STATES;
      }
    }
    case AS_ATTACK_H:
    {
      if (pStats->HasAbilityCombo(COMBO_HH) ||
          pStats->HasAbilityCombo(COMBO_HHH) ||
          pStats->HasAbilityCombo(COMBO_HHL))
      {
        return AS_ATTACK_HH;
      }
      else
      {
        return NUM_ANIM_STATES;
      }
    }
    case AS_ATTACK_LL:
    {
      return pStats->HasAbilityCombo(COMBO_LLH) ? AS_ATTACK_LLH : NUM_ANIM_STATES;
    }
    case AS_ATTACK_LH:
    {
      return pStats->HasAbilityCombo(COMBO_LHH) ? AS_ATTACK_LHH : NUM_ANIM_STATES;
    }
    case AS_ATTACK_HL:
    {
      return pStats->HasAbilityCombo(COMBO_HLH) ? AS_ATTACK_HLH : NUM_ANIM_STATES;
    }
    case AS_ATTACK_HH:
    {
      return pStats->HasAbilityCombo(COMBO_HHH) ? AS_ATTACK_HHH : NUM_ANIM_STATES;
    }

    case AS_ATTACK_LLLL:
    {
      return AS_ATTACK_H;
    }

    default:
    case AS_ATTACK_HHH:
    case AS_ATTACK_RANGED:
    case AS_ATTACK_LLL:
    case AS_ATTACK_LLH:
    case AS_ATTACK_LHL:
    case AS_ATTACK_LHH:
    case AS_ATTACK_HLL:
    case AS_ATTACK_HLH:
    case AS_ATTACK_HHL:
      return NUM_ANIM_STATES;
  }
}

eAnimState 
cAS_Attack::GetNextCombo_Light()
{
  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(GetGameObject()->GetStats());

  switch(mOwner->GetAnimState())
  {
    case AS_ATTACK_L:
    {
      if (pStats->HasAbilityCombo(COMBO_LL) ||
          pStats->HasAbilityCombo(COMBO_LLH) ||
          pStats->HasAbilityCombo(COMBO_LLL) ||
          pStats->HasAbilityCombo(COMBO_LLLL))
      {
        return AS_ATTACK_LL;
      }
      else
      {
        return NUM_ANIM_STATES;
      }
    }
    case AS_ATTACK_H:
    {
      if (pStats->HasAbilityCombo(COMBO_HL) ||
          pStats->HasAbilityCombo(COMBO_HLH) ||
          pStats->HasAbilityCombo(COMBO_HLL))
      {
        return AS_ATTACK_HL;
      }
      else
      {
        return NUM_ANIM_STATES;
      }
    }
    case AS_ATTACK_LL:
    {
      return pStats->HasAbilityCombo(COMBO_LLL) ? AS_ATTACK_LLL : AS_ATTACK_L;
    }
    case AS_ATTACK_LH:
    {
      return pStats->HasAbilityCombo(COMBO_LHL) ? AS_ATTACK_LHL : NUM_ANIM_STATES;
    }
    case AS_ATTACK_HL:
    {
      return pStats->HasAbilityCombo(COMBO_HLL) ? AS_ATTACK_HLL : NUM_ANIM_STATES;
    }
    case AS_ATTACK_HH:
    {
      return pStats->HasAbilityCombo(COMBO_HHL) ? AS_ATTACK_HHL : NUM_ANIM_STATES;
    }

    case AS_ATTACK_LLL:
    {
      return pStats->HasAbilityCombo(COMBO_LLLL) ? AS_ATTACK_LLLL : AS_ATTACK_L;
    }
      
    case AS_ATTACK_LLLL:
    {
      return AS_ATTACK_L;
    }

    default:
    case AS_ATTACK_RANGED:
    case AS_ATTACK_LLH:
    case AS_ATTACK_LHL:
    case AS_ATTACK_LHH:
    case AS_ATTACK_HLL:
    case AS_ATTACK_HLH:
    case AS_ATTACK_HHL:
    case AS_ATTACK_HHH:
      return NUM_ANIM_STATES;
  }
}

//------------------------------------ cAS_Jump

void 
cAS_Jump::Enter()
{
  cAnimCharControllerInput *input = GetInput();

  input->mJumpRequest = false;

  mJumpVel.Y = smJumpUp;
  if (mOwner->GetAnimState() == AS_RUN_JUMP)
  {
    PlayAnim(mAnimID);
    mJumpVel.X = SY_SIN(input->mHeadingRequest) * smJumpSpeed * input->mSpeedRequest;
    mJumpVel.Z = SY_COS(input->mHeadingRequest) * smJumpSpeed * input->mSpeedRequest;
  }
  else
  {
    PlayAnim(mAnimID);
    mJumpVel.X = 0;
    mJumpVel.Z = 0;
  }
}

bool cAS_Jump::AcceptTransition()
{
  bool bHasAnim = GetSprite()->HasAnim(mAnimID)!=0;

  if (!bHasAnim)
  {
    GetInput()->mJumpRequest = false;
  }

  return bHasAnim;
}

bool 
cAS_Jump::Update(float time)
{
  SyCSprite* sprite = GetSprite();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex)!=0)
  {                              
    if (sprite->HasAnim(mAnimID))
    {
      static_cast<cPhysicsAnimated*>(GetGameObject()->GetPhysics())->Jump(mJumpVel);

      if (mAnimID == ANIM_B_JUMP)
      {
        mOwner->ChangeState(AS_IN_AIR);
      }
      else
      {
        mOwner->ChangeState(AS_RUN_IN_AIR);
      }
    }
    else
    {
      mOwner->ChangeState(AS_STAND);
    }

    return true;
  }

  ClearInput();

  return false;
}

void 
cAS_Jump::RegisterTuningVariables()
{
  gTuningSys.AddFloat(&smJumpUp,"Jump_Up");
  gTuningSys.AddFloat(&smJumpSpeed,"Jump_Speed");
}


//------------------------------------ cAS_InAir

void 
cAS_InAir::Enter()
{
  if (GetSprite()->HasAnim(mAnimID))
  {
    // don't play for npcs, they're not meant to fall?
    PlayAnim(mAnimID);
  }

  mStartHeading = GetGameObject()->GetHeading();
  mFloating = true;
  mRising = true;
}

bool 
cAS_InAir::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();

  if (input->mbKnockback)
  {
    mOwner->ChangeState(AS_KNOCKBACK_IN_AIR);
    return true;
  }

  if (input->mbKnockforward)
  {
    mOwner->ChangeState(AS_KNOCKFORWARD);
    return true;
  }

  if (input->mDeath)
  {
    input->mDeath = false;
    if (!mOwner->ChangeState(AS_KNOCKFORWARD_IN_AIR))
    {
      mOwner->ChangeState(AS_DEATH);
    }
    return true;
  }

  cGameObject *obj = GetGameObject();
  cPhysicsAnimated *physics = prop_cast<cPhysicsAnimated *>(obj->GetPhysics());

  if (input->mbFloating == false)
  {
    mFloating = false;
  }

  if (physics->GetVelocity().Y < 0.0f)
  {
    mRising = false;
  }

  float pushVel = 0.0f;
  if (mRising && !mFloating )
  {
    pushVel = -l_JumpControlGravity;
  }

  // Mario style jump with air control

  SyVect3 impulse(0.0f, 0.0f, 0.0f);
  float angleDelta = AngleDifference(input->mHeadingRequest, mStartHeading);
  float maxMomentumAngleRadians = SY_DEG_TO_RAD(smJumpControlMaxAngle);

  GAME_WARN(ERROR_DESIGN, smJumpControlMaxAngle >= 0.0f && smJumpControlMaxAngle <= 180.0f, "Jump_Control_MaxAngle must be between 0 and 180");

  if (SY_FABS(angleDelta) <= maxMomentumAngleRadians)
  {
    GAME_WARN(ERROR_DESIGN, smJumpControlMaxAngleMomentum >= 0.0f && smJumpControlMaxAngleMomentum <= 1.0f, "Jump_Control_MaxAngle_Momentume must be between 0 and 1");

    // only allow so much movement backward to initial jump direction
    float maxMomentumInterp = 1.0f - (SY_FABS(angleDelta)/maxMomentumAngleRadians);
    maxMomentumInterp = ((1.0f - smJumpControlMaxAngleMomentum)*maxMomentumInterp) + smJumpControlMaxAngleMomentum;

    impulse.X += SY_SIN(input->mHeadingRequest) * smJumpControl * input->mSpeedRequest * maxMomentumInterp; 
    impulse.Z += SY_COS(input->mHeadingRequest) * smJumpControl * input->mSpeedRequest * maxMomentumInterp;
  }

  impulse.Y = pushVel;
  physics->Impulse(impulse);

  // check for turn...
  TurnTo(input->mHeadingRequest,time*smTurnSpeed,0.0f);

  if (input->mHeight <= ON_GROUND_HEIGHT &&
      input->mYVel <= ON_GROUND_VEL)
  {
    if (input->mSpeedRequest < 0.5f)
    {
      mOwner->ChangeState(AS_LAND);
    }
    else
    {
      mOwner->ChangeState(AS_RUN_LAND);
    }
    return true;
  }

  if (input->mAttackRequestL || input->mAttackRequestS)
  {                      
    mOwner->ChangeState(AS_ATTACK_JUMP);
    return true;
  }

  ClearInput();

  return false;
}

void 
cAS_InAir::RegisterTuningVariables()
{
  gTuningSys.AddAngle(&smTurnSpeed,"Jump_TurnSpeed");
  gTuningSys.AddFloat(&smJumpControl,"Jump_Control");
  gTuningSys.AddFloat(&smJumpControlMaxAngle,"Jump_Control_MaxAngle");
  gTuningSys.AddFloat(&smJumpControlMaxAngleMomentum,"Jump_Control_MaxAngle_Momentum");
}


//------------------------------------ cAS_Death

int cAS_Death::smDeadCount = 0;
float cAS_Death::smTuneUnsummomTime = 0.0f;
int cAS_Death::smTuneMaxCorpses = 10;
float cAS_Death::smOldestCorpseTime = 0.0f;

void cAS_Death::RegisterTuningVariables()
{
  gTuningSys.AddFloat(&smTuneUnsummomTime, "Corpse_UnsummonTime");
  gTuningSys.AddInt(&smTuneMaxCorpses, "Corpse_Max");
}

bool cAS_Death::CheckForDelete()
{
  return mbOkToDelete;
}

void cAS_Death::ExitScope()
{
  cGameObject* pOwner = GetGameObject();

  if (pOwner->GetType() == cGameObject::OBJ_NPC)
  {
    cStatsCharacter* pStats = static_cast<cStatsCharacter*>(pOwner->GetStats());

    if (!pStats->IsDead())
    {
      pStats->Die(mKillerID);
    }

    if (!mbOkToDelete)
    {
      mbOkToDelete = true;
      --smDeadCount;
    }

    smOldestCorpseTime = 0.0f;
  }
}

void 
cAS_Death::Enter()
{
  cGameObject* pOwner = GetGameObject();

  mKillerID = GetInput()->mHitReactTarget;
  mbOkToDelete = false;
  mFXHandle = -1;

  if ( GetGameObject()->GetGraphic()->RagdollOn() )
  {
    static_cast<cPhysicsAnimated*>(pOwner->GetPhysics())->Ragdoll(GetInput()->mKnockbackVect);
  }

  PlayAnim(ANIM_B_DEATH);

  if (pOwner->GetType() == cGameObject::OBJ_NPC)
  {
    smDeadCount++;
  }
}

void 
cAS_Death::Exit()
{
  cGameObject* pOwner = GetGameObject();

  if (pOwner->GetType() == cGameObject::OBJ_NPC)
  {
    if (!mbOkToDelete)
    {
      --smDeadCount;
    }
    
    smOldestCorpseTime = 0.0f;
  }
}

bool 
cAS_Death::Update(float time)
{
  cAnimControllerSetting* setting = GetSetting();
  cGameObject* pOwner = GetGameObject();

  float actionStart = 0.05f;
  float prevTime = mOwner->GetAnimTime();
  float curTime = prevTime + time;

  if (setting)
  {
    actionStart = setting->mActionFrame;
  }

  actionStart *= mAnimDuration;

  cStatsCharacter* pStats = static_cast<cStatsCharacter*>(pOwner->GetStats());

  if (curTime >= actionStart)
  {
    if (!pStats->IsDead())
    {
      pStats->Die(mKillerID);
    }
  }

  ClearInput();

  if (pOwner->GetType() == cGameObject::OBJ_NPC && pStats->IsDead())
  {
    SyScene* pScene = pOwner->GetTitan()->GetScene();
    SyActorHandle actorHandle = pOwner->GetGraphic()->GetActorHandle();

    if (SyActorNull == actorHandle || 
        (!pScene->IsActorVisible(actorHandle) && -1 == mFXHandle))
    {
      if (!mbOkToDelete)
      {
        mbOkToDelete = true;
        --smDeadCount;
        smOldestCorpseTime = 0.0f;
      }
    }
    else if (((smDeadCount > smTuneMaxCorpses && curTime >= smOldestCorpseTime && smOldestCorpseTime > 0.000001f) ||
             curTime > smTuneUnsummomTime))
    {
      int32 fxScriptHandle = cImpactFXSet::GetCharacterFXHandle("fx_npc_death", pOwner);
    
      if (-1 != fxScriptHandle)
      {
        if (-1 == mFXHandle)
        {
          mFXHandle = pScene->GetFXScriptSystem()->PlayScript(fxScriptHandle, 1, &actorHandle);
          pOwner->GetGraphic()->SetVisible(false);
        }
        else
        {
          if (pScene->GetFXScriptSystem()->IsComplete(mFXHandle))
          {
            if (!mbOkToDelete)
            {
              mbOkToDelete = true;
              --smDeadCount;
              smOldestCorpseTime = 0.0f;
            }
          }
        }
      }
      else if (!mbOkToDelete)
      {
        SyCamera* pCam = pOwner->GetTitan()->GetCameraController()->GetCamera();

        static const float MAX_CORPSE_DIST_SQR = 50.0f*50.0f;

        if (!pCam || 
            pCam->GetLocation().DistanceSquared(pOwner->GetLocation()) > MAX_CORPSE_DIST_SQR ||
            pCam->GetFrustum().Cull(pOwner->GetLocation(), 2.0f))
        {
          mbOkToDelete = true;
          --smDeadCount;
          smOldestCorpseTime = 0.0f;
        }
      }
    }

    if (!mbOkToDelete &&
        curTime > smOldestCorpseTime)
    {
      smOldestCorpseTime = curTime;
    }
  }

  return false;
}

//------------------------------------ cAS_Block
bool 
cAS_Block::IsActive()
{
  cAnimControllerSetting* setting = GetSetting();

  float actionStart = 0.0f;
  float curTime = mOwner->GetAnimTime();

  if (setting)
  {
    actionStart = setting->mActionFrame;
  }

  actionStart *= mAnimDuration;

  return curTime >= actionStart;
}

bool
cAS_Block::AcceptTransition()
{
  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  bool bCanAttack = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed() > 0;

  if (!bCanAttack)
  {
    GetInput()->mBlockRequest = false;
  }

  return bCanAttack;
}

void 
cAS_Block::Enter()
{
  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed() * 0.01f;

  PlayAnim(ANIM_B_BLOCK, speedMult); 
}

bool
cAS_Block::UpdateHitReactionStateChanges()
{
  cAnimCharControllerInput *input = GetInput();

  if (input->mbKnockback)
  {
    mOwner->ChangeState(AS_KNOCKBACK);
    return true;
  }

  if (input->mbKnockforward)
  {
    mOwner->ChangeState(AS_KNOCKFORWARD);
    return true;
  }

  if (input->mbThrown)
  {
    input->mbThrown = false;
    mOwner->ChangeState(AS_THROWN_BY_CHARACTER);
    return true;
  }

  if (input->mFall)
  {
    input->mFall = false;
    mOwner->ChangeState(AS_IN_AIR);
    return true;
  }

  // play death last out of all hit reactions, to let enemies get
  // hit through entire combo and only play death afterwards
  if (input->mDeath)
  {
    input->mDeath = false;
    mOwner->ChangeState(AS_DEATH);
    return true;
  }

  if (mOwner->GetAnimState() != AS_STUNNED)
  {
    if (GetGameObject()->GetStats()->QueryFlag("Stunned") || 
      GetGameObject()->GetStats()->QueryFlag("Sleeping"))
    {
      mOwner->ChangeState(AS_STUNNED);
      return true;
    }
  }

  SyCSprite* sprite = GetSprite();
  if (input->mHitReact)
  { 
    input->mHitReact = false;
    if (sprite->HasAnim(ANIM_B_BLOCK_FWD_TAKEDAM))
    {
      PlayAnim(ANIM_B_BLOCK_FWD_TAKEDAM, 1.0f, true);
    }
  }
  else
  {
    if (mPlaybackIndex != -1 &&
        sprite->GetPlayAnimID(mPlaybackIndex) == ANIM_B_BLOCK_FWD_TAKEDAM &&
        sprite->IsPlaybackStopped(mPlaybackIndex))
    {
      Enter(); // play block anim again
    }
  }

  return false;
}

bool 
cAS_Block::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();
  cGameObject *obj = GetGameObject();
  cStatsCharacter *stats = (cStatsCharacter *)obj->GetStats();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (!input->mBlockRequest || stats->GetBlock() < 0)
  {
    input->mBlockRequest = false;
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  if (input->mDodging)
  {
    mOwner->ChangeState(AS_DODGE);
    return true;
  }

  UpdatePivot(time);

  ClearInput();

  return false;
}

//------------------------------------ cAS_RangedAttack
bool
cAS_RangedAttack::AcceptTransition()
{
  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  return static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed() > 0;
}

void            
cAS_RangedAttack::RefreshTargeting(float heading, float pitch, float distance)
{
  mTargetHeading = heading;
  mTargetPitch = pitch;
  mTargetRange = distance;
}

bool
cAS_RangedAttack::IsActive()
{
  cAnimControllerSetting * setting = GetSetting();
  float curTime = mOwner->GetStateTime();

  float actionEnd = 0.5f;

  if (setting != NULL)
  {
    actionEnd = setting->mActionFrameEnd;
  }

  actionEnd *= mAnimDuration;

  return curTime > actionEnd;
}

void 
cAS_RangedAttack::Enter()
{
  cAnimCharControllerInput *input = GetInput();

  mTarget = input->mTarget;
  mTargetHeading = input->mTargetHeading;
  mTargetPitch = input->mTargetPitch;
  mTargetRange = input->mTargetRange;

  input->mTarget = ID_NONE;
  input->mAttackRequestRanged = false;

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed() * 0.01f;

  if (GetSprite()->HasAnim(ANIM_RANGED_ATTACK))
  {
    PlayAnim(ANIM_RANGED_ATTACK, speedMult);
  }
  else
  {
    PlayAnim(ANIM_ATTACK_L, speedMult);
  }
}

bool 
cAS_RangedAttack::Update(float time)
{
  SyCSprite* sprite = GetSprite();

  //cAnimCharControllerInput *input = GetInput();
  cAnimControllerSetting * setting = GetSetting();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  cGameObject *obj = GetGameObject();
  cGameObject *target = obj->GetRegistry()->Fetch(mTarget);

  float turnSpeed = setting ? setting->mTurnSpeed : cAS_Running::smTurnSpeed;

  if (target != NULL)
  {
    float heading = obj->GetHeadingTowards(target);
    TurnTo(heading, turnSpeed, 0.0f);  // turn immediately as possible, no tolerance
  }
  else if (obj->GetType() == cGameObject::OBJ_PLAYER)
  {
    UpdatePivot(time, true);
    mTargetHeading = obj->GetHeading();
  }
  else if (turnSpeed > 0.0f)
  {
    TurnTo(mTargetHeading, time*turnSpeed, mOwner->smActionTurnThreshold);
  }

  float prevTime = mOwner->GetStateTime();
  float curTime = prevTime + time;
  float attackTime = 0.5f;
  
  if (setting != NULL)
  {
    attackTime = setting->mActionFrame;
  }

  attackTime *= mAnimDuration;

  if (prevTime < attackTime && curTime >= attackTime)
  {
    // create projectile
    cGameObject *obj = GetGameObject();
    
    if (target != NULL)
    {
      ((cStatsCharacter *)obj->GetStats())->Shoot(target);
    }
    else
    {
      ((cStatsCharacter *)obj->GetStats())->Shoot(obj->GetLocation(),
                                                  mTargetHeading,
                                                  mTargetPitch,
                                                  mTargetRange);
    }
  }

  ClearInput();

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}
//------------------------------------ cAS_Override

void 
cAS_Override::Enter()
{
  SyCSprite* sprite = GetSprite();
  SyScene *scene = GetScene();
  cAnimCharControllerInput *input = GetInput();
  int curIndex;

  mOwner->ClearImpact();
  mBlendIndex = -1;
  if (sprite->GetNumPlay() > 0)
  {
    mBlendIndex = sprite->GetFirstPlay();
    sprite->SetPlaybackToCurrentPose(mBlendIndex,*scene); 

    while (sprite->GetNumPlay()>1)
    {
      curIndex = sprite->GetFirstPlay();
      if (curIndex == mBlendIndex)
      {
        curIndex = sprite->GetNextPlay(curIndex);
      }
      sprite->ErasePlay(curIndex,*scene);
    }

    //mPlaybackIndex = sprite->PrependAnimPlay(animid,0,*scene);
    mPlaybackIndex = sprite->PrependPlay(input->mAnimationOverride,ANIM_OVERRIDE,*scene);
    static const int blendtime = 250;
    sprite->SetPlaybackBlendMode(mBlendIndex,SYANIMBLENDMODE_INVERT_TIMED_INTERP,blendtime,*scene);
    input->mAnimationOverride = ANIM_HANDLE_NONE;
  }
  else
  {
    // sprite->Clear();
    sprite->EraseAllPlay(*scene);
    //mPlaybackIndex = sprite->PrependAnimPlay(animid,0,*scene);
    mPlaybackIndex = sprite->PrependPlay(input->mAnimationOverride,ANIM_OVERRIDE,*scene);
    input->mAnimationOverride = ANIM_HANDLE_NONE;
  }
}


bool 
cAS_Override::Update(float time)
{
  SyCSprite* sprite = GetSprite();
  cAnimCharControllerInput *input = GetInput();

  if (input->mOverrideCancelImmediate)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    input->mOverrideCancelImmediate=false;
    input->mOverrideCancelEnd=false;
    input->mOverrideSetLocation=false;
    return true;
  }

  if (sprite->IsPlaybackStopped(mPlaybackIndex)!=0 && input->mOverrideCancelEnd)
  {
    cPhysicsAnimated *physics = (cPhysicsAnimated*) GetGameObject()->GetPhysics();
    physics->SetLocomotion(cPhysics::LOCO_ANIMATION);

    /*
     * Cut scene has ended - set the actor to the absolute last frame of the
     * cut scene animation.
     */

    if (input->mOverrideSetLocation)
    {
      SyScene *scene = GetScene();
      cGameObject* gameObject = GetGameObject();
      cGraphicCharacter* gameGraphicCharacter = prop_cast<cGraphicCharacter*>(gameObject->GetGraphic());
      SyActorHandle actorHandle = gameGraphicCharacter->GetActorHandle();
      SyCSprite* cSprite = (SyCSprite*) scene->GetActorSpritePtr (actorHandle);
      SyMatrix44 transform;
      SyVect3 Rotation,Scale, Translation;
      cSprite->CalcMotionAbsTransform(*scene,transform);
      transform.ConvertTo(Rotation, Scale, Translation);
      gameObject->SetLocation (Translation);
      gameObject->SetHPR (Rotation);
      input->mOverrideSetLocation = false;
    }
    mOwner->ChangeState(AS_COMBAT_IDLE);
    input->mOverrideCancelImmediate=false;
    input->mOverrideCancelEnd=false;

   return true;
  }

  return false;
}


//------------------------------------ cAS_Script

void 
cAS_Script::Enter()
{
  SyCSprite* sprite = GetSprite();
  SyScene *scene = GetScene();
  cAnimCharControllerInput *input = GetInput();
  int curIndex;

  mOwner->ClearImpact();
  mBlendIndex = -1;
  if (sprite->GetNumPlay() > 0)
  {
    mBlendIndex = sprite->GetFirstPlay();
    sprite->SetPlaybackToCurrentPose(mBlendIndex,*scene); 

    while (sprite->GetNumPlay()>1)
    {
      curIndex = sprite->GetFirstPlay();
      if (curIndex == mBlendIndex)
      {
        curIndex = sprite->GetNextPlay(curIndex);
      }
      sprite->ErasePlay(curIndex,*scene);
    }
  }
  else
  {
    // sprite->Clear();
    sprite->EraseAllPlay(*scene);
  }

  mPlaybackIndex = sprite->PrependAnimPlay(input->mScriptAnimID,0,*scene);
  mAnimDuration = sprite->GetPlayDuration(mPlaybackIndex, 1.0f, *scene);

  //    mPlaybackIndex = sprite->PrependPlay(input->mAnimationOverride,ANIM_OVERRIDE,*scene);
  sprite->SetPlaybackBlendMode(mBlendIndex,SYANIMBLENDMODE_INVERT_TIMED_INTERP,250,*scene);

  if (ANIM_SUMMONED == input->mScriptAnimID)
  {
    cGameObject* pOwner = GetGameObject();
    const cStatsModelData* pModelData = pOwner->GetTitan()->GetDatabaseSys()->GetModelData(static_cast<cStatsCharacter*>(pOwner->GetStats())->GetMaster()->mModelDataID);

    if (pModelData && ID_NONE != pModelData->mSummonFXID)
    {
      int fxScriptHandle = -1;
      SyActorHandle actorHandle = pOwner->GetGraphic()->GetActorHandle();
      if (SyActorNull != actorHandle &&
          scene->GetDictionary()->FindTyped(pModelData->mSummonFXID, SYRESOURCETYPE_FXSCRIPT, fxScriptHandle))
      {
        scene->GetFXScriptSystem()->PlayScript(fxScriptHandle, 1, &actorHandle);
      }
    }
  }

  input->mScriptAnimID = -1;
  mbAllowHitReactions = input->mScriptAnimAllowHitReactions;
  input->mScriptAnimAllowHitReactions = false;
}


bool 
cAS_Script::Update(float time)
{
  SyCSprite* sprite = GetSprite();

  if (mbAllowHitReactions)
  {
    if (UpdateHitReactionStateChanges())
    {
      return true;
    }
  }

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    ClearInput();
    return true;
  }

  return false;
}

//------------------------------------ cAS_Dodge

bool 
cAS_Dodge::IsActive()
{
  cAnimControllerSetting* setting = GetSetting();

  float actionStart = 0.0f;
  float actionEnd = 1.0f;
  float curTime = mOwner->GetAnimTime();

  if (setting)
  {
    actionStart = setting->mActionFrame;
    actionEnd = setting->mActionFrameEnd;
  }

  actionStart *= mAnimDuration;
  actionEnd *= mAnimDuration;

  return curTime >= actionStart && curTime <= actionEnd;
}

void 
cAS_Dodge::Enter()
{  
  cGameObject* obj = GetGameObject();
  cAnimCharControllerInput *input = GetInput();

  float oldHeading = obj->GetHeading();
  float delta = AngleDifference(oldHeading, input->mDodgeDirection);

  int anim_to_play = ANIM_B_DODGE_F;

  input->mDodging = false;

  if (delta < SY_DEG_TO_RAD(-135.0f) || delta > SY_DEG_TO_RAD(135.0f))
  {
    // temp to only use forward anim
//    obj->SetHeading(input->mDodgeDirection);
//    input->mHeadingRequest = input->mDodgeDirection;
    anim_to_play = ANIM_B_DODGE_B;
  }
  else if (delta < SY_DEG_TO_RAD(-45.0f))
  {
//    obj->SetHeading(input->mDodgeDirection);
//    input->mHeadingRequest = input->mDodgeDirection;
    anim_to_play = ANIM_B_DODGE_L;
  }
  else if (delta > SY_DEG_TO_RAD(45.0f))
  {
//    obj->SetHeading(input->mDodgeDirection);
//    input->mHeadingRequest = input->mDodgeDirection;
    anim_to_play = ANIM_B_DODGE_R;
  }

  PlayAnim(anim_to_play);
}

bool 
cAS_Dodge::Update(float time)
{
  SyCSprite* sprite = GetSprite();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  UpdatePivot(time);

  ClearInput();

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex) != 0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}

//------------------------------------ cAS_Knocked

void 
cAS_Knocked::Enter()
{
  PlayAnim(mAnimID);
  cGameObject *obj = GetGameObject();
  cAnimCharControllerInput *input = GetInput();
  obj->SetHeading(input->mKnockbackHeading);

  mKnockdown = input->mbKnockdown;
  mGetUpTime = input->mHitReactTime;

  if (mKnockdown)
  {
    // For Paul - swap out the following Jump() call with the Ragdoll() call below
    static_cast<cPhysicsAnimated*>(GetGameObject()->GetPhysics())->Jump(input->mKnockbackVect);
//    static_cast<cPhysicsAnimated*>(GetGameObject()->GetPhysics())->Ragdoll(input->mKnockbackVect);

    // shake the camera a lot - something just knocked the player off his/her feet
    if ( obj->GetType() == cGameObject::OBJ_PLAYER && !obj->IsRemote() )
    {
      float shakeMag = input->mKnockbackVect.Magnitude()*0.1f;
      shakeMag = SY_CLAMP(shakeMag, 0.1f, 0.8f);

      float shakeTime = mGetUpTime*0.1f;
      shakeTime = SY_CLAMP(shakeTime, 0.05f, 1.0f);
      obj->GetTitan()->GetCameraController()->Shake(0.0f, shakeMag, 10.0f, shakeTime);
    }
  }
  else
  {
    SyVect3 dir(input->mKnockbackVect);
    float mag = dir.NormalizeMagn();
    static_cast<cPhysicsAnimated*>(GetGameObject()->GetPhysics())->Impact(dir, mag);
  }

  ClearInput();
}

bool 
cAS_Knocked::AcceptTransition()
{
  bool bHasAnim = GetSprite()->HasAnim(mAnimID)!=0;

  if (!bHasAnim)
  {
    cAnimCharControllerInput* input = GetInput();
    input->mbKnockback = false;
    input->mbKnockdown = false;
    input->mbKnockforward = false;
    input->mKnockbackVect(0.0f, 0.0f, 0.0f);
  }

  return bHasAnim;
}

bool 
cAS_Knocked::Update(float time)
{
  SyCSprite* sprite = GetSprite();
  cAnimCharControllerInput* input = GetInput();

  // For Paul
  if (static_cast<cPhysicsAnimated*>(GetGameObject()->GetPhysics())->GetLocomotion() == cPhysics::LOCO_RAGDOLL)
  {
    return false;
  }

  if (input->mDeath)
  {
    input->mDeath = false;
    mOwner->ChangeState(AS_DEATH);
    return true;
  }

  if (sprite->IsPlaybackStopped(mPlaybackIndex)!=0)
  {                              
    if (mKnockdown)
    {
      input->mHitReactTime = mGetUpTime;

      if (mAnimID == ANIM_B_KNOCKBACK)
      {
        mOwner->ChangeState(AS_KNOCKBACK_IN_AIR);
      }
      else
      {
        mOwner->ChangeState(AS_KNOCKFORWARD_IN_AIR);
      }
    }
    else
    {
      mOwner->ChangeState(AS_STAND);
    }
      
    return true;
  }

  return false;
}


//------------------------------------ cAS_KnockedInAir

void 
cAS_KnockedInAir::Enter()
{
  mGetUpTime = GetInput()->mHitReactTime;

  SyCSprite* pSprite = GetSprite();

  int32 animID = mAnimID1;

  if (pSprite)
  {
    bool bHas1 = pSprite->HasAnim(mAnimID1) != 0;
    bool bHas2 = pSprite->HasAnim(mAnimID2) != 0;
    if (bHas1 && bHas2)
    {
      if (GetGameObject()->GetTitan()->Random(0, 1) == 0)
      {
        animID = mAnimID2;
      }
    }
    else if (bHas2 && !bHas1)
    {
      animID = mAnimID2;
    }
  }

  PlayAnim(animID);
}

bool 
cAS_KnockedInAir::AcceptTransition()
{
  bool bHasAnim = GetSprite()->HasAnim(mAnimID1) != 0 || GetSprite()->HasAnim(mAnimID2) != 0;

  if (!bHasAnim)
  {
    cAnimCharControllerInput* input = GetInput();
    input->mbKnockback = false;
    input->mbKnockdown = false;
    input->mbKnockforward = false;
    input->mKnockbackVect(0.0f, 0.0f, 0.0f);
  }

  return bHasAnim;
}

bool 
cAS_KnockedInAir::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();

  if (input->mHeight <= ON_GROUND_HEIGHT &&
      input->mYVel <= ON_GROUND_VEL)
  {
    input->mHitReactTime = mGetUpTime;

    if (mAnimID1 == ANIM_B_KNOCKBACK_IN_AIR)
    {
      mOwner->ChangeState(AS_KNOCKBACK_LAND);
    }
    else
    {
      mOwner->ChangeState(AS_KNOCKFORWARD_LAND);
    }
    return true;
  }

  ClearInput();

  return false;
}

//------------------------------------ cAS_KnockedLand

void 
cAS_KnockedLand::Enter()
{
  mGetUpTime = GetInput()->mHitReactTime;

  if ( GetGameObject()->GetGraphic()->RagdollOn() )
  {
    static_cast<cPhysicsAnimated*>(GetGameObject()->GetPhysics())->Ragdoll(GetInput()->mKnockbackVect);
  }

  if (mGetUpTime > 0.0f)
  {
    mGetUpTime += GetGameObject()->GetTitan()->RandomFloat()*0.5f;
  }

  PlayAnim(mAnimID);
}

bool 
cAS_KnockedLand::Update(float time)
{
  SyCSprite* sprite = GetSprite();
  cAnimCharControllerInput* input = GetInput();
  cAnimControllerSetting* setting = GetSetting();
  cGameObject* obj = GetGameObject();

  if (input->mDeath)
  {
    input->mDeath = false;
    mOwner->ChangeState(AS_DEATH);
    return true;
  }

  if (obj->GetType() == cGameObject::OBJ_PLAYER)
  {
    if (input->mAttackRequestL ||
        input->mAttackRequestS ||
        input->mJumpRequest ||
        input->mActionRequest ||
        input->mAttackRequestRanged)
    {
      static const float RECOVERY_REDUCE = 0.05f;

      if (mGetUpTime > RECOVERY_REDUCE)
      {
        mGetUpTime -= RECOVERY_REDUCE;
      }
      else
      {
        mGetUpTime = 0.0f;
      }
    }
  }

  float curTime = mOwner->GetAnimTime()+time;
  float allowMovementFrame = 1.0f;

  if (setting)
  {
    allowMovementFrame = setting->mAllowMovementFrame;
  }

  allowMovementFrame *= mAnimDuration;

  if ((mPlaybackIndex == -1 || sprite->IsPlaybackStopped(mPlaybackIndex)!=0 || curTime >= allowMovementFrame) &&
      curTime > mGetUpTime)
  {
    if (!obj->GetStats()->IsDead())
    {
      if ( GetGameObject()->GetGraphic()->RagdollOff(0) )
      {
        if ( GetGameObject()->GetPhysics()->GetClassID() == cPhysicsAnimated::mCLASSID )
        {
          ((cPhysicsAnimated*)(GetGameObject()->GetPhysics()))->SetLocomotion( cPhysicsAnimated::LOCO_ANIMATION );
        }
        if (mAnimID == ANIM_B_KNOCKBACK_LAND)
        {
          mOwner->ChangeState(AS_KNOCKBACK_GETUP);
        }
        else
        {
          mOwner->ChangeState(AS_KNOCKFORWARD_GETUP);
        }
        return true;
      }
    }
  }

  ClearInput();
  input->mbKeepFacing = false;

  return false;
}

//--------------------------------------------------------------- cAS_AttackJump
float cAS_AttackJump::smUpInstant = 20.0f;
float cAS_AttackJump::smUpButton = 2.5f;
float cAS_AttackJump::smForward = 8.5f;
float cAS_AttackJump::smControl = 0.0f;
float cAS_AttackJump::smDown = -10.0f;

void 
cAS_AttackJump::RegisterTuningVariables()
{
  gTuningSys.AddFloat(&smUpInstant,"JumpAttack_UpInstant");
  gTuningSys.AddFloat(&smUpButton,"JumpAttack_UpButton");
  gTuningSys.AddFloat(&smForward,"JumpAttack_Forward");
  gTuningSys.AddFloat(&smControl,"JumpAttack_Control");
  gTuningSys.AddFloat(&smDown, "JumpAttack_Down");
}

bool cAS_AttackJump::AcceptTransition()
{
  SyCSprite* pSprite = GetSprite();

  return pSprite->HasAnim(ANIM_ATTACK_JUMP) && pSprite->HasAnim(ANIM_ATTACK_JUMP_LAND);
}

void 
cAS_AttackJump::Enter()
{
  cAnimCharControllerInput *input = GetInput();
  cGameObject* obj = GetGameObject();

  mTarget = input->mTarget;
  mTargetAngle = input->mTargetHeading;

  input->mAttackRequestL = false;
  input->mAttackRequestS = false;
  mComboL = false;
  mComboS = false;

  mLastHitTarget = ID_NONE;
  mStartHeading = obj->GetHeading();

  SyAssert(prop_cast<cStatsCharacter*>(obj->GetStats())!=NULL);
  float speedMult = static_cast<cStatsCharacter*>(obj->GetStats())->CalcAttackSpeed() * 0.01f;

  PlayAnim(mAnimID, speedMult);

  cPhysicsAnimated* physics = static_cast<cPhysicsAnimated*>(obj->GetPhysics());
  SyVect3 up(0.0f, smUpInstant, 0.0f);
  physics->Jump(up);
}

void
cAS_AttackJump::Exit()
{
  cGameObject* obj = GetGameObject();
  cAnimControllerSetting *setting = GetSetting();

  if (setting && ID_NONE != setting->mSpellID)
  {
    const cSpellMaster* pSpell = obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(setting->mSpellID);

    if (pSpell)
    {
      pSpell->EndSpellEffects(obj, NULL);
    }
  }
}

bool 
cAS_AttackJump::Update(float time)
{
  cGameObject* obj = GetGameObject();
  cAnimCharControllerInput *input = GetInput();
  
  if (input->mbKnockback)
  {
    mOwner->ChangeState(AS_KNOCKBACK_IN_AIR);
    return true;
  }

  if (input->mbKnockforward)
  {
    mOwner->ChangeState(AS_KNOCKFORWARD_IN_AIR);
    return true;
  }

  if (input->mDeath)
  {
    input->mDeath = false;
    mOwner->ChangeState(AS_KNOCKBACK_IN_AIR);
    return true;
  }

  // upward impulse while player is holding attack button
  cPhysicsAnimated* physics = static_cast<cPhysicsAnimated*>(obj->GetPhysics());
  SyVect3 impulse(0.0f, 0.0f, 0.0f);

  float heading = obj->GetHeading();

  float angleDelta = AngleDifference(input->mHeadingRequest, mStartHeading);
  float maxMomentumAngleRadians = SY_DEG_TO_RAD(cAS_InAir::smJumpControlMaxAngle);

  GAME_WARN(ERROR_DESIGN, cAS_InAir::smJumpControlMaxAngle >= 0.0f && cAS_InAir::smJumpControlMaxAngle <= 180.0f, "Jump_Control_MaxAngle must be between 0 and 180");

  if (SY_FABS(angleDelta) <= maxMomentumAngleRadians)
  {
    GAME_WARN(ERROR_DESIGN, cAS_InAir::smJumpControlMaxAngleMomentum >= 0.0f && cAS_InAir::smJumpControlMaxAngleMomentum <= 1.0f, "Jump_Control_MaxAngle_Momentume must be between 0 and 1");

    // only allow so much movement backward to initial jump direction
    float maxMomentumInterp = 1.0f - (SY_FABS(angleDelta)/maxMomentumAngleRadians);
    maxMomentumInterp = ((1.0f - cAS_InAir::smJumpControlMaxAngleMomentum)*maxMomentumInterp) + cAS_InAir::smJumpControlMaxAngleMomentum;

    impulse.X += SY_SIN(input->mHeadingRequest) * (smForward + (smControl * input->mSpeedRequest * maxMomentumInterp)); 
    impulse.Z += SY_COS(input->mHeadingRequest) * (smForward + (smControl * input->mSpeedRequest * maxMomentumInterp));
  }
  else
  {
    impulse.X += SY_SIN(heading) * smForward; 
    impulse.Z += SY_COS(heading) * smForward;
  }

  if (input->mbFloating)
  {
    impulse.Y += smUpButton;
  }

  impulse.Y += smDown;
  physics->Impulse(impulse);

  cAnimControllerSetting * setting = GetSetting();
  float prevTime = mOwner->GetStateTime();
  float curTime = prevTime + time;
  float attackStartTime = 0.5f;
  float attackEndTime = 0.6f;
  float range = mOwner->GetRange(COMBO_JUMPATTACK);

  if (setting != NULL)
  {
    attackStartTime = setting->mActionFrame;
    attackEndTime = setting->mActionFrameEnd;
    range = setting->mRange;
  }
  
  attackStartTime *= mAnimDuration;
  attackEndTime *= mAnimDuration;

  if (setting && ID_NONE != setting->mSpellID)
  {
    // if we have a spell just cast it once
    if (prevTime < attackStartTime && curTime >= attackStartTime)
    {
      AttackFrame();
    }
  }
  else if (curTime >= attackStartTime && curTime <= attackEndTime)
  {
    // hit event occurs during this window this time (can hit multiple targets)
    cGameObject* pObj = GetGameObject();
    input->mTarget = pObj->GetIntel()->PickAttackTarget(pObj->GetHeading(), range, SY_DEG_TO_RAD(60.0f));

    if (mLastHitTarget != input->mTarget)
    {
      AttackFrame();
      mLastHitTarget = input->mTarget;
    }
  }

  if (input->mHeight <= ON_GROUND_HEIGHT &&
      input->mYVel <= ON_GROUND_VEL)
  {
    mOwner->ChangeState(AS_ATTACK_JUMP_LAND);
    return true;
  }

  return false;
}

//------------------------------------ cAS_CastSpell

int32 cAS_CastSpell::GetAnimID()
{
  switch (mAnimSettingOverride)
  {
    case AS_ATTACK_L:       return ANIM_ATTACK_L;
    case AS_ATTACK_H:       return ANIM_ATTACK_H;
    case AS_ATTACK_LL:      return ANIM_ATTACK_LL;
    case AS_ATTACK_LH:      return ANIM_ATTACK_LH;
    case AS_ATTACK_HL:      return ANIM_ATTACK_HL;
    case AS_ATTACK_HH:      return ANIM_ATTACK_HH;
    case AS_ATTACK_LLL:     return ANIM_ATTACK_LLL;
    case AS_ATTACK_LLH:     return ANIM_ATTACK_LLH;
    case AS_ATTACK_LHL:     return ANIM_ATTACK_LHL;
    case AS_ATTACK_LHH:     return ANIM_ATTACK_LHH;
    case AS_ATTACK_HLL:     return ANIM_ATTACK_HLL;
    case AS_ATTACK_HLH:     return ANIM_ATTACK_HLH;
    case AS_ATTACK_HHL:     return ANIM_ATTACK_HHL;
    case AS_ATTACK_HHH:     return ANIM_ATTACK_HHH;
    case AS_ATTACK_LLLL:    return ANIM_ATTACK_LLLL;
    case AS_ATTACK_RANGED:  return ANIM_RANGED_ATTACK;
    case AS_BLOCK:          return ANIM_B_BLOCK;
    case AS_EMOTE_ANGER:    return ANIM_B_EMOTE_ANGER;
    case AS_CAST_SPELL_A:   return ANIM_B_CAST_A;
    case AS_CAST_SPELL_B:   return ANIM_B_CAST_B;
    case AS_CAST_SPELL_C:   return ANIM_B_CAST_C;
    case AS_CAST_SPELL_D:   return ANIM_B_CAST_D;
    default:
      GAME_ASSERT(ERROR_DESIGN, false, "Unsupported anim for spell cast");
      return ANIM_B_CAST_A;
  }
}

void            
cAS_CastSpell::RefreshTargeting(float heading, float pitch, float distance)
{
  mTargetHeading = heading;
  mTargetPitch = pitch;
  mTargetRange = distance;
}

cAnimControllerSetting*
cAS_CastSpell::GetSetting()
{
  return mOwner->GetOwner()->GetTitan()->GetAnimControllerSys()->Fetch(mOwner->GetAnimSet(), mAnimSettingOverride);
}

bool cAS_CastSpell::IsActive()
{
  cAnimControllerSetting * setting = GetSetting();
  float curTime = mOwner->GetStateTime();

  float actionEnd = 0.5f;

  if (setting != NULL)
  {
    actionEnd = setting->mActionFrameEnd;
  }

  actionEnd *= mAnimDuration;

  cGameObject* obj = GetGameObject();

  if (obj->GetType() == cGameObject::OBJ_NPC)
  {
    const cSpellMaster* pSpell = obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSpellID);
    GAME_ASSERT(ERROR_DESIGN, pSpell!=NULL, "Bad spell master while casting");
    if (pSpell && pSpell->mNPCCastTime > 0.00001f &&
        GetSprite()->HasAnim(ANIM_B_CAST_LOOP))
    {
      actionEnd += pSpell->mNPCCastTime; 
    }
  }

  return curTime <= actionEnd;
}

void 
cAS_CastSpell::Enter()
{
  cAnimCharControllerInput *input = GetInput();
  cGameObject* obj = GetGameObject();
  SyCSprite* sprite = GetSprite();

  mCastFXHandle = -1;
  mAnimSettingOverride = input->mCastAnimSettingOverride;
  mSpellID = input->mCastSpellID;
  mTargetID = input->mTarget;
  mTargetHeading = input->mTargetHeading;
  mTargetPitch = input->mTargetPitch;
  mTargetRange = input->mTargetRange;
  input->mCastAnimSettingOverride = NUM_ANIM_STATES;
  input->mCastSpellID = 0;

  if (NUM_ANIM_STATES == mAnimSettingOverride)
  {
    mAnimSettingOverride = mOwner->GetAnimState();
  }

  const cSpellMaster* pSpell = obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSpellID);
  GAME_ASSERT(ERROR_DESIGN, pSpell!=NULL, "Bad spell master while casting");

  if (obj->GetType() == cGameObject::OBJ_NPC && 
      pSpell && pSpell->mNPCCastTime > 0.00001f &&
      sprite->HasAnim(ANIM_B_CAST_LOOP))
  {
    PlayAnim(ANIM_B_CAST_LOOP);
    PlayCastFX(pSpell, true);
  }
  else
  {
    PlayAnim(GetAnimID());
    PlayCastFX(pSpell);
  }
}

void cAS_CastSpell::PlayCastFX(const cSpellMaster* pSpell, bool bCastLoopFX)
{
  SyScene* pScene = GetScene();

  if (-1 != mCastFXHandle)
  {
    pScene->GetFXScriptSystem()->StopPlayback(mCastFXHandle);
    mCastFXHandle = -1;
  }

  if (pSpell)
  {
    tGameID fxID;

    if (bCastLoopFX)
    {
      fxID = pSpell->mCastLoopFXID;
    }
    else
    {
      fxID = pSpell->mCastStartFXID;
    }

    if (pSpell && ID_NONE != fxID)
    {
      cGameObject* obj = GetGameObject();
      int32         scriptHandle;
      SyActorHandle actorHandle = obj->GetGraphic()->GetActorHandle();

      if(pScene->GetDictionary()->FindTyped(fxID, SYRESOURCETYPE_FXSCRIPT, scriptHandle))
      {
        mCastFXHandle = obj->GetTitan()->GetScene()->GetFXScriptSystem()->PlayScript(scriptHandle, 1, &actorHandle);
      }
    }
  }
}


void 
cAS_CastSpell::Exit()
{
  if (-1 == mCastFXHandle)
  {
    return;
  }

  cAnimControllerSetting* setting = GetSetting();

  float actionFrame = 0.5f;

  if (setting)
  {
    actionFrame = setting->mActionFrame;
  }

  actionFrame *= mAnimDuration;

  if (mOwner->GetAnimTime() < actionFrame)
  {
    GetScene()->GetFXScriptSystem()->StopPlayback(mCastFXHandle);
    mCastFXHandle = -1;
  }
}

bool 
cAS_CastSpell::Update(float time)
{
  SyCSprite* sprite = GetSprite();

  cAnimCharControllerInput *input = GetInput();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  cGameObject *obj = GetGameObject();
  cGameObject *target = NULL;
  SyScene* scene = GetScene();

  if (mTargetID != ID_NONE && mTargetID != obj->GetID())
  {
    target = obj->GetRegistry()->Fetch(mTargetID);

    if (target != NULL)
    {
      cAnimControllerSetting* setting = GetSetting();
      float turnSpeed = setting ? setting->mTurnSpeed : mOwner->smActionDefaultTurnSpeed;
      float heading = obj->GetHeadingTowards(target);
      TurnTo(heading, time*turnSpeed, 0.0f);
    }
  }
  else 
  {
    UpdatePivot(time, true);
  }

  float prevTime = mOwner->GetStateTime();
  float curTime = prevTime + time;

  const cSpellMaster* pSpell = obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSpellID);
  GAME_ASSERT(ERROR_DESIGN, pSpell!=NULL, "Bad spell master while casting");

  if (obj->GetType() == cGameObject::OBJ_NPC &&
      pSpell && pSpell->mNPCCastTime > 0.00001f &&
      sprite->HasAnim(ANIM_B_CAST_LOOP))
  {
    if (sprite->GetPlayAnimID(mPlaybackIndex) == ANIM_B_CAST_LOOP)
    {
      if (curTime >= pSpell->mNPCCastTime)
      {
        sprite->StopPlayback(mPlaybackIndex, *scene);
        PlayAnim(GetAnimID());
        PlayCastFX(pSpell);
      }

      return false;
    }
    else
    {
      prevTime -= pSpell->mNPCCastTime;
      curTime -= pSpell->mNPCCastTime;
    }
  }

  if (UpdateActions(time))
  {
    return true;
  }

  if (input->mCastSpellID != ID_NONE)
  {
    const cSpellMaster* pNextSpell = obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(input->mCastSpellID);
    GAME_ASSERT(ERROR_DESIGN, pNextSpell!=NULL, "Bad spell master while casting");

    if (pNextSpell)
    {
      if (pNextSpell->mParentSpellID != ID_NONE && pNextSpell->mParentSpellID == mSpellID)
      {
        mOwner->ChangeState(AS_CAST_SPELL_A);
        return true;
      }
    }
  }

  cAnimControllerSetting * setting = GetSetting();

  float castTime = 0.5f;

  if (setting != NULL)
  {
    castTime = setting->mActionFrame;
  }

  castTime *= mAnimDuration;

  if (prevTime < castTime && curTime >= castTime)
  {
    // Spell should be cast during this frame
    if (pSpell)
    {
      if (mTargetRange > 0.0f)
      {
        SyVect3 targetLoc(obj->GetLocation());
        targetLoc.X += SY_SIN(mTargetHeading) * mTargetRange;
        targetLoc.Y += SY_SIN(mTargetPitch) * mTargetRange;
        targetLoc.Z += SY_COS(mTargetHeading) * mTargetRange;
        pSpell->CreateSpellDelivery(obj, target, &targetLoc);
      }
      else
      {
        pSpell->CreateSpellDelivery(obj, target);
      }
    }
  }

  ClearInput();

  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex) != 0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}


//------------------------------------ cAS_CastSpellLooping

bool cAS_CastSpellLooping::IsActive()
{
  return true;
}

void 
cAS_CastSpellLooping::Enter()
{
  cAnimCharControllerInput *input = GetInput();
  cGameObject* obj = GetGameObject();

  mAnimSettingOverride = input->mCastAnimSettingOverride;
  mSpellID = input->mCastSpellID;
  mTargetID = input->mTarget;
  mTargetHeading = input->mTargetHeading;
  mTargetRange = input->mTargetRange;
  input->mCastAnimSettingOverride = NUM_ANIM_STATES;
  input->mCastSpellID = 0;

  if (NUM_ANIM_STATES == mAnimSettingOverride)
  {
    mAnimSettingOverride = mOwner->GetAnimState();
  }

  const cSpellMaster* pSpell = obj->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSpellID);
  GAME_ASSERT(ERROR_DESIGN, pSpell!=NULL, "Bad spell master while casting");

  mCastFXHandle = -1;
  if (pSpell && ID_NONE != pSpell->mCastStartFXID)
  {
    SyDictionary *pDictionary = obj->GetTitan()->GetScene()->GetDictionary();
    int32         scriptHandle;
    SyActorHandle actorHandle = obj->GetGraphic()->GetActorHandle();

    if(pDictionary->FindTyped( pSpell->mCastStartFXID, SYRESOURCETYPE_FXSCRIPT, scriptHandle))
    {
      mCastFXHandle = obj->GetTitan()->GetScene()->GetFXScriptSystem()->PlayScript(scriptHandle, 1, &actorHandle);
    }
  }

  PlayAnim(ANIM_B_CAST_D); // looping animation

  if (pSpell)
  {
    mAnimDuration = pSpell->mDeliveryDuration;

    cGameObject *target = NULL;

    if (mTargetID != ID_NONE && mTargetID != obj->GetID())
    {
      target = obj->GetRegistry()->Fetch(mTargetID);
    }

    if (mTargetRange > 0.0f)
    {
      SyVect3 targetLoc(obj->GetLocation());
      targetLoc.X += SY_SIN(mTargetHeading) * mTargetRange;
      targetLoc.Z += SY_COS(mTargetHeading) * mTargetRange;
      pSpell->CreateSpellDelivery(obj, target, &targetLoc);
    }
    else
    {
      pSpell->CreateSpellDelivery(obj, target);
    }
  }
}

bool 
cAS_CastSpellLooping::Update(float time)
{
  //SyCSprite* sprite = GetSprite();

  //cAnimCharControllerInput *input = GetInput();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  cGameObject *obj = GetGameObject();
  cGameObject *target = NULL;
  //SyScene* scene = GetScene();

  if (mTargetID != ID_NONE && mTargetID != obj->GetID())
  {
    target = obj->GetRegistry()->Fetch(mTargetID);

    if (target != NULL)
    {
      cAnimControllerSetting* setting = GetSetting();
      float turnSpeed = setting ? setting->mTurnSpeed : mOwner->smActionDefaultTurnSpeed;
      float heading = obj->GetHeadingTowards(target);
      TurnTo(heading, time*turnSpeed, 0.0f);
    }
  }
  else 
  {
    UpdatePivot(time, true);
  }

  if (UpdateActions(time))
  {
    return true;
  }

  ClearInput();

  float curTime = mOwner->GetStateTime() + time;

  if (curTime > mAnimDuration)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}

//------------------------------------ cAS_PickupProp

bool
cAS_PickupProp::AcceptTransition()
{
  cAnimCharControllerInput* input = GetInput();
  cGameObject* obj = GetGameObject();
 
  cGameObject* pActionTarget = obj->GetRegistry()->Fetch(input->mActionTarget);

  if (pActionTarget && pActionTarget->GetType() == cGameObject::OBJ_PROP)
  {
    SyScene* pScene = GetScene();
    SyCollRay ray;
    cGameObjectFilter filter;
    ray.Init(obj->GetLocation()+SyVect3(0.0f, 1.0f, 0.0f), pActionTarget->GetLocation()+SyVect3(0.0f, 1.0f, 0.0f));
    filter.Init(pActionTarget->GetGraphic()->GetActorHandle());

    if (!pScene->Collide(ray, filter))
    {
      return true;
    }
  }

  input->mActionRequest = false;
  return false;
}

void 
cAS_PickupProp::Enter()
{
  cAnimCharControllerInput* input = GetInput();

  mPropID = input->mActionTarget;
  input->mActionRequest = false;

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed() * 0.01f;

  PlayAnim(mAnimID, speedMult);
}

bool cAS_PickupProp::UpdateActions(float time)
{
  cGameObject *obj = GetGameObject();

  cAnimCharControllerInput *input = GetInput();
  cAnimControllerSetting* setting = GetSetting();

  float noInterruptStart = 0.0f;
  float noInterruptEnd = 1.0f;
  float comboEnd = 1.0f;
  float allowMovementFrame = 1.0f;
  float curTime = mOwner->GetAnimTime()+time;

  if (setting)
  {
    noInterruptStart = setting->mNoInterruptStart;
    noInterruptEnd = setting->mNoInterruptEnd;
    comboEnd = setting->mComboEnd;
    allowMovementFrame = setting->mAllowMovementFrame;
  }

  noInterruptStart *= mAnimDuration;
  noInterruptEnd *= mAnimDuration;
  comboEnd *= mAnimDuration;
  allowMovementFrame *= mAnimDuration;

  bool bIsIdle = mOwner->IsIdle();
  bool bAllowSameStateInterrupt = false;

  // only allow attack if we're not attacking now, or if we're past the 
  // appropriate spot in out attack animation
  bool bAllowAttack = false;
  eComboType combo = mOwner->GetComboType();

  if (NUM_COMBOS != combo)
  {
    bAllowAttack = bIsIdle || (curTime >= allowMovementFrame && curTime >= comboEnd);
  }
  else
  {
    bAllowAttack = bIsIdle || (curTime >= allowMovementFrame);
  }

  eAnimState newState = NUM_ANIM_STATES;
  eAnimState curState = mOwner->GetAnimState();

  if (bIsIdle || 
      curTime < noInterruptStart ||
      curTime > noInterruptEnd)
  {
    if (input->mActionRequest)
    {
      input->mActionTarget = mPropID;
      mOwner->ChangeState(AS_PUTDOWN_PROP);
      return true;
    }
  }

  if (NUM_ANIM_STATES == newState && bAllowAttack)
  {
    if (input->mAttackRequestRanged)
    {                      
      newState = AS_THROW_PROP;
    }
  }

  if (NUM_ANIM_STATES == newState &&
    AS_RUN != curState &&
    (bIsIdle || curTime >= allowMovementFrame) &&
    !IsActive() &&
    !FloatsEqual(input->mSpeedRequest, 0.0f))
  {
    bool start_running = false;

    if (!mTurning)
    {
      start_running = true;
    }
    else
    {
      float delta = AngleDifference(input->mHeadingRequest,obj->GetHeading());

      if (SY_FABS(delta) < SY_DEG_TO_RAD(30))
      {
        start_running = true;
      }
      else
      {
        newState = AS_STAND_CARRY_PROP;
      }
    }

    if (mOwner->IsAttacking() &&
      curTime >= allowMovementFrame &&
      input->mSpeedRequest < 0.75f)
    {
      start_running = false;
    }

    if (start_running)
    {
      newState = AS_RUN_CARRY_PROP;
    }
  }

  if (NUM_ANIM_STATES != newState &&
    (curState != newState || bAllowSameStateInterrupt))
  {
    input->mActionTarget = mPropID;
    mOwner->ChangeState(newState);
    return true;
  }

  return false;
}

bool
cAS_PickupProp::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();

  cGameObject* pObj = GetGameObject();
  cGameObject* pProp = pObj->GetRegistry()->Fetch(mPropID);

  /* apparently one cannot pick up the dead */
  if (!pProp || pProp->GetStats()->IsDead() ||
      (pProp->GetCarriedBy() != ID_NONE && pProp->GetCarriedBy() != pObj->GetID())) // picked up by somebody else
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  if (UpdateHitReactionStateChanges())
  {
    pProp->SetCarriedBy(ID_NONE);
    pProp->GetPhysics()->Nudge(); // make sure it falls down
    return true;
  }

  float prev_time = mOwner->GetStateTime();
  cAnimControllerSetting * setting = GetSetting();

  float pickup_time = 0.35f;
  float noInterruptEnd = 0.8f;

  if (setting != NULL)
  {
    pickup_time = setting->mActionFrame;
    noInterruptEnd = setting->mNoInterruptEnd;
  }

  pickup_time *= mAnimDuration;
  noInterruptEnd *= mAnimDuration;

  float cur_time = prev_time + time;

  if (cur_time < pickup_time)
  {
    float headingTowards = pObj->GetHeadingTowards(pProp);
    float heading = pObj->GetHeading();

    if (SY_FABS(headingTowards-heading) > 0.0001f)
    {
      TurnTo(headingTowards,time*cAS_Running::smTurnSpeed,cAS_Stand::smTurnThreshold);
    }
  }
  else
  {
    UpdatePivot(time);

    if (prev_time < pickup_time && cur_time >= pickup_time)
    {
      // wake up objects around us so that crates stacked on top of us know to fall
      cAreaEffect_Radius* pRadius = SyNew cAreaEffect_Radius();
      pRadius->SetDuration(1.0f);
      pRadius->SetRadius(3.0f);
      pRadius->SetLocation(pProp->GetLocation());
      cGameEffect_Nudge *pNudge = SyNew cGameEffect_Nudge();
      pRadius->AddGameEffect(pNudge);
      cAreaEffectSys::Get()->Add(pRadius);

      pProp->SetCarriedBy(GetGameObject()->GetID());
    }
    else if (cur_time >= noInterruptEnd)
    {
      if (UpdateActions(time))
      {
        return true;
      }
    }
  }

  ClearInput();

  if (-1 == mPlaybackIndex || GetSprite()->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    input->mActionTarget = mPropID;
    mOwner->ChangeState(AS_STAND_CARRY_PROP);
    return true;
  }

  return false;
}

//------------------------------------ cAS_ThrowProp

void 
cAS_ThrowProp::Enter()
{
  cAnimCharControllerInput* input = GetInput();

  mTarget = input->mTarget;
  input->mTarget = ID_NONE;

  mPropID = input->mActionTarget;
  input->mActionRequest = false;
  input->mAttackRequestRanged = false;

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = static_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcAttackSpeed() * 0.01f;

  PlayAnim(mAnimID, speedMult);
}

void 
cAS_ThrowProp::Exit()
{
  cGameObject* pProp = GetGameObject()->GetRegistry()->Fetch(mPropID);
  if (pProp)
  {
    pProp->SetCarriedBy(ID_NONE);
    pProp->GetPhysics()->Nudge(); // make sure it falls down
  }
}

bool
cAS_ThrowProp::Update(float time)
{
  cGameObject *obj = GetGameObject();
  cAnimCharControllerInput *input = GetInput();
  cAnimControllerSetting *setting = GetSetting();

  cGameObject* pProp = obj->GetRegistry()->Fetch(mPropID);
  if (!pProp || pProp->GetStats()->IsDead())
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  cGameObject *target = obj->GetRegistry()->Fetch(mTarget);
  float turnSpeed = setting ? setting->mTurnSpeed : mOwner->smActionDefaultTurnSpeed;

  if (target != NULL)
  {
    float heading = obj->GetHeadingTowards(target);
    TurnTo(heading, time*turnSpeed, 0.0f);
  }
  else if (obj->GetType() == cGameObject::OBJ_PLAYER)
  {
    UpdatePivot(time, true);
    input->mTargetHeading = obj->GetHeading();
  }
  else if (turnSpeed > 0.0f)
  {
    TurnTo(input->mTargetHeading, time*turnSpeed, 0.0f);
  }

  float prev_time = mOwner->GetStateTime();
  float throw_time = 0.23f;

  if (setting != NULL)
  {
    throw_time = setting->mActionFrame;
  }

  throw_time *= mAnimDuration;

  float cur_time = prev_time + time;

  if (prev_time < throw_time && cur_time >= throw_time )
  {
    cGameObject* pProp = obj->GetRegistry()->Fetch(mPropID);

    if (pProp)
    {
      pProp->SetCarriedBy(ID_NONE);

      SyVect3 targetPos;

      if (target != NULL)
      {
        targetPos = target->GetLocation();
      }
      else
      {
        targetPos = obj->GetLocation();
        targetPos.X += SY_SIN(input->mTargetHeading) * input->mTargetRange;
        targetPos.Y += SY_SIN(input->mTargetPitch) * input->mTargetRange;
        targetPos.Z += SY_COS(input->mTargetHeading) * input->mTargetRange;
      }

      static_cast<cPhysicsProp*>(pProp->GetPhysics())->Throw(GetGameObject(), targetPos);
    }
  }

  ClearInput();

  if (-1 == mPlaybackIndex || GetSprite()->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    SyAssert(pProp->GetCarriedBy()==ID_NONE);
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}


//------------------------------------ cAS_ThrowCharacter
float cAS_ThrowCharacter::THROWN_CHARACTER_MIN_SPEED_XZ = 6.0f;
float cAS_ThrowCharacter::THROWN_CHARACTER_MAX_SPEED_XZ = 12.0f;
float cAS_ThrowCharacter::THROWN_CHARACTER_MIN_SPEED_Y = 6.0f;
float cAS_ThrowCharacter::THROWN_CHARACTER_MAX_SPEED_Y = 12.0f;

void 
cAS_ThrowCharacter::RegisterTuningVariables()
{
  gTuningSys.AddFloat(&THROWN_CHARACTER_MIN_SPEED_XZ,"Thrown_Character_MinSpeedXZ");
  gTuningSys.AddFloat(&THROWN_CHARACTER_MAX_SPEED_XZ,"Thrown_Character_MaxSpeedXZ");
  gTuningSys.AddFloat(&THROWN_CHARACTER_MIN_SPEED_Y,"Thrown_Character_MinSpeedY");
  gTuningSys.AddFloat(&THROWN_CHARACTER_MAX_SPEED_Y,"Thrown_Character_MaxSpeedY");
}

void 
cAS_ThrowCharacter::Enter()
{
  mThrowee = ID_NONE;
  cGameObject* obj = GetGameObject();
  tGameObjectID actionTargetID = obj->GetIntel()->PickAttackTarget(COMBO_THROWCHARACTER);
  cGameObject* pActionTarget = obj->GetRegistry()->Fetch(actionTargetID);

  if (pActionTarget && pActionTarget->GetType() == cGameObject::OBJ_NPC)
  {
    int defenderPriority = static_cast<cGraphicCharacter*>(pActionTarget->GetGraphic())->GetAnimController()->GetHitPriority();
    int attackerPriority = mOwner->GetHitPriority();

    if (attackerPriority >= defenderPriority &&
        static_cast<cStatsCharacter*>(obj->GetStats())->CanLift(pActionTarget))
    {
      SyScene* pScene = GetScene();
      SyCollRay ray;
      cSurfaceImpactFilter filter;
      ray.Init(obj->GetLocation()+SyVect3(0.0f, 1.0f, 0.0f), pActionTarget->GetLocation()+SyVect3(0.0f, 1.0f, 0.0f));
      filter.Init(obj->GetGraphic()->GetActorHandle());

      if (!pScene->Collide(ray, filter))
      {
        mThrowee = actionTargetID;
      }
    }
  }

  PlayAnim(mAnimID);
}

void 
cAS_ThrowCharacter::Exit()
{
  cGameObject* pThrownObj = GetGameObject()->GetRegistry()->Fetch(mThrowee);

  if (pThrownObj)
  {
    pThrownObj->GetPhysics()->SetCollideable(true);
  }
}

bool
cAS_ThrowCharacter::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();

  cGameObject* pObj = GetGameObject();
  cGameObject* pThrownObj = pObj->GetRegistry()->Fetch(mThrowee);

  if (pThrownObj && pThrownObj->GetStats()->IsDead())
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  float prevTime = mOwner->GetStateTime();
  cAnimControllerSetting * setting = GetSetting();

  float pickupTime = 0.17f;
  float throwTime = 0.51f;

  if (setting != NULL)
  {
    pickupTime = setting->mActionFrame;
    throwTime = setting->mActionFrameEnd;
  }

  pickupTime *= mAnimDuration;
  throwTime *= mAnimDuration;

  GAME_WARN(ERROR_DESIGN, pickupTime <= throwTime, "Bad action frame timings for throw character anim");

  float curTime = prevTime + time;

  if (curTime < pickupTime)
  {
    if (pThrownObj)
    {
      // while we're picking up the npc, turn towards it
      float headingTowards = pObj->GetHeadingTowards(pThrownObj);
      float heading = pObj->GetHeading();

      if (SY_FABS(headingTowards-heading) > 0.0001f)
      {
        TurnTo(headingTowards, time*SY_PI*2.0f, 0.0f);
      }
    }
    else
    {
      UpdatePivot(time);
    }
  }
  else if (prevTime < pickupTime && curTime >= pickupTime)
  {
    if (ID_NONE == mThrowee || !pThrownObj)
    {
      // failed throw
      mOwner->ChangeState(AS_COMBAT_IDLE);
      ClearInput();
      return true;
    }
    else
    {
      pThrownObj->GetPhysics()->SetCollideable(false);
      SyAssert(prop_cast<cGraphicCharacter*>(pThrownObj->GetGraphic())!=NULL);
      cAnimCharControllerInput* targetInput = static_cast<cGraphicCharacter*>(pThrownObj->GetGraphic())->GetAnimController()->GetInput();
      targetInput->mbThrown = true;
      targetInput->mHitReactTarget = GetGameObject()->GetID();
      targetInput->mHitReactTime = setting ? setting->mTargetRecoveryTime : 1.0f;
    }
  }
  else
  {
    UpdatePivot(time, true);

    if (curTime < throwTime)
    {
      UpdateConnection();
    }
    else if (prevTime < throwTime && curTime >= throwTime)
    {
      int classID = static_cast<cStatsCharacter*>(pObj->GetStats())->GetMaster()->mClass;
      SyAssert(pObj->GetTitan()->GetDatabaseSys()->GetCharacterClass(classID) && pObj->GetTitan()->GetDatabaseSys()->GetCharacterClass(classID)->mStats.mThrowStrength > 0.0f);
      GAME_WARN(ERROR_DESIGN, THROWN_CHARACTER_MAX_SPEED_XZ >= THROWN_CHARACTER_MIN_SPEED_XZ, "Thrown character max xz speed < min xz speed! in tuning.xml");
      GAME_WARN(ERROR_DESIGN, THROWN_CHARACTER_MAX_SPEED_Y >= THROWN_CHARACTER_MIN_SPEED_Y, "Thrown character max y speed < min y speed! in tuning.xml");
      float XZSpeed = ((THROWN_CHARACTER_MAX_SPEED_XZ - THROWN_CHARACTER_MIN_SPEED_XZ)*pObj->GetTitan()->GetDatabaseSys()->GetCharacterClass(classID)->mStats.mThrowStrength*0.01f) + THROWN_CHARACTER_MIN_SPEED_XZ;
      float YSpeed = ((THROWN_CHARACTER_MAX_SPEED_Y - THROWN_CHARACTER_MIN_SPEED_Y)*pObj->GetTitan()->GetDatabaseSys()->GetCharacterClass(classID)->mStats.mThrowStrength*0.01f) + THROWN_CHARACTER_MIN_SPEED_Y;

      if (pThrownObj)
      {
        static_cast<cPhysicsAnimated*>(pThrownObj->GetPhysics())->Throw(pObj->GetID(), input->mHeadingRequest, XZSpeed, YSpeed);
        pThrownObj->GetPhysics()->SetCollideable(true);
      }
    }
  }

  ClearInput();

  if (-1 == mPlaybackIndex || GetSprite()->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    mOwner->ChangeState(AS_COMBAT_IDLE);
    return true;
  }

  return false;
}

void cAS_ThrowCharacter::UpdateConnection()
{
  cGameObject* pThrower = GetGameObject();
  cGameObject* pThrown = pThrower->GetRegistry()->Fetch(mThrowee);

  // get right hand node from thrower from the hierarchy if we can find it

  SyVect3 throwerLoc(pThrower->GetLocation());
  SyVect3 handLoc(throwerLoc);
  SyVect3 thrownLoc(pThrown->GetLocation());
  SyVect3 adjustThrown(0.0f, 0.0f, 0.0f);

  SyVect3 rightHandLoc, leftHandLoc;
  if (static_cast<cGraphicCharacter*>(pThrower->GetGraphic())->GetIdentNodeLocation(CHAR_NODE_RIGHT_HAND_CARRY, &rightHandLoc) &&
      static_cast<cGraphicCharacter*>(pThrower->GetGraphic())->GetIdentNodeLocation(CHAR_NODE_LEFT_HAND_CARRY, &leftHandLoc))
  {
    handLoc = rightHandLoc + leftHandLoc;
    handLoc *= 0.5f;
  }
  else
  {
    GAME_ASSERT(ERROR_ART, false, "Throwing character has no right and/or left hand carry nodes");
    handLoc = throwerLoc;
  }

  SyVect3 hpr(pThrower->GetHPR());
  hpr.X += SY_PI;

  thrownLoc = handLoc;
  pThrown->SetLocation(thrownLoc);
  pThrown->SetHPR(hpr);
}

//------------------------------------ cAS_ThrownByCharacter
bool 
cAS_ThrownByCharacter::Update(float time)
{
  if (UpdateActions(time))
  {
    return true;
  }

  SyCSprite* sprite = GetSprite();
  cGameObject* obj = GetGameObject();
  cAnimCharControllerInput* input = GetInput();

//  if (input->mbThrown)
//  {
//    input->mbThrown = false;
//    mOwner->ChangeState(AS_KNOCKBACK_IN_AIR);
//    return true;
//  }
  
  if (obj->GetPhysics()->IsCollideable() &&
      input->mHeight <= ON_GROUND_HEIGHT &&
      input->mYVel <= ON_GROUND_VEL)
  {
    input->mHitReactTime = 1.2f;
    mOwner->ChangeState(AS_KNOCKBACK_LAND);
    return true;
  }
  
  if (-1 == mPlaybackIndex || sprite->IsPlaybackStopped(mPlaybackIndex) != 0)
  {
    mOwner->ChangeState(AS_KNOCKBACK_IN_AIR);
    return true;
  }

  ClearInput();
  return false;
}

//------------------------------------ cAS_PutdownProp

void 
cAS_PutdownProp::Enter()
{
  cAnimCharControllerInput* input = GetInput();

  mPropID = input->mActionTarget;
  input->mActionRequest = false;

  PlayAnim(mAnimID);
}

void 
cAS_PutdownProp::Exit()
{
  cGameObject* pProp = GetGameObject()->GetRegistry()->Fetch(mPropID);
  if (pProp)
  {
    pProp->SetCarriedBy(ID_NONE);
    pProp->GetPhysics()->Nudge(); // make sure it falls down
  }
}

bool
cAS_PutdownProp::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();

  cGameObject* pProp = GetGameObject()->GetRegistry()->Fetch(mPropID);
  if (!pProp || pProp->GetStats()->IsDead())
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  UpdatePivot(time);

  float prev_time = mOwner->GetStateTime();
  cAnimControllerSetting * setting = GetSetting();

  float putdown_time = 0.2f;

  if (setting != NULL)
  {
    putdown_time = setting->mActionFrame;
  }

  putdown_time *= mAnimDuration;

  float cur_time = prev_time + time;
  
  if (cur_time < putdown_time)
  {
    if (input->mAttackRequestRanged)
    { 
      input->mActionTarget = mPropID;
      mOwner->ChangeState(AS_THROW_PROP);
      return true;
    }
  }
  else if (prev_time < putdown_time && cur_time >= putdown_time )
  {
    pProp->SetCarriedBy(ID_NONE);
    pProp->SetSafeLocation(pProp->GetLocation()); // test for valid world position around us
    // make sure it falls down and away from character
    SyVect3 dir(pProp->GetLocation() - GetGameObject()->GetLocation());
    dir.Y = 0.0f;
    dir.Normalize();
    dir *= 0.25f;
    dir.Y = -0.1f;
    static_cast<cPhysicsProp*>(pProp->GetPhysics())->Impulse(dir, 0.0f);
  }
  else
  {
    if (UpdateActions(time))
    {
      return true;
    }
  }

  ClearInput();
  if (-1 == mPlaybackIndex || GetSprite()->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  return false;
}
//------------------------------------ cAS_PushProp

float 
cAS_PushProp::GetPushAngle(float heading)
{
  while (heading < 0.0f)
  {
    heading += SY_PI*2.0f;
  }

  while (heading > SY_PI*2.0f)
  {
    heading -= SY_PI*2.0f;
  }

  if ((heading >= 0.0f && heading <= (1.0f/8.0f)*SY_PI) ||
      (heading > (15.0f/8.0f)*SY_PI && heading <= SY_PI*2.0f))
  {
    return 0.0f;
  }
  else if (heading > (1.0f/8.0f)*SY_PI && heading <= (3.0f/8.0f)*SY_PI)
  {
    return 0.25f*SY_PI;
  }
  else if (heading > (3.0f/8.0f)*SY_PI && heading <= (5.0f/8.0f)*SY_PI)
  {
    return 0.5f*SY_PI;
  }
  else if (heading > (5.0f/8.0f)*SY_PI && heading <= (7.0f/8.0f)*SY_PI)
  {
    return 0.75f*SY_PI;
  }
  else if (heading > (7.0f/8.0f)*SY_PI && heading <= (9.0f/8.0f)*SY_PI)
  {
    return SY_PI;
  }
  else if (heading > (9.0f/8.0f)*SY_PI && heading <= (11.0f/8.0f)*SY_PI)
  {
    return 1.25f*SY_PI;
  }
  else if (heading > (11.0f/8.0f)*SY_PI && heading <= (13.0f/8.0f)*SY_PI)
  {
    return 1.5f*SY_PI;
  }
  else if (heading > (13.0f/8.0f)*SY_PI && heading <= (15.0f/8.0f)*SY_PI)
  {
    return 1.75f*SY_PI;
  }

  SyAssert(false);
  return 0.0f;
}

float 
cAS_PushProp::GetPushOffset(float heading)
{
  cGameObject* pObj = GetGameObject();
  cGameObject* pProp = pObj->GetTitan()->GetRegistry()->Fetch(mPropID);
  //SyScene* pScene = GetScene();

  if (!pProp || SyActorNull == pProp->GetGraphic()->GetActorHandle())
  {
    return 0.0f;
  }

  SyAssert(prop_cast<cPhysicsProp*>(pProp->GetPhysics())!=NULL);

  SyVect3 dir, hitPoint;
  dir.HPR(heading, 0.0f, 0.0f);

  const float CHARACTER_OFFSET = 0.598f;
  float distToPropCollision = static_cast<cPhysicsProp*>(pProp->GetPhysics())->GetExtentInDirection(pObj->GetLocation(), dir, 1.0f, &hitPoint);
  return pObj->GetLocation().Distance(pProp->GetLocation()) - distToPropCollision + CHARACTER_OFFSET;
}

void 
cAS_PushProp::Enter()
{
  cAnimCharControllerInput* input = GetInput();

  mPropID = input->mActionTarget;
  input->mActionRequest = false;

  cGameObject* pObj = GetGameObject();
  cGameObject* pProp = pObj->GetRegistry()->Fetch(mPropID);

  mFaceHeading = GetPushAngle(pObj->GetHeadingTowards(pProp));
  mPushHeading = 0.0f;
  pObj->SetHeading(mFaceHeading);
  mPushOffset = GetPushOffset(mFaceHeading);
  mLastPropLocation = pProp->GetLocation();

  SyCSprite* sprite = GetSprite();

  PlayAnim(ANIM_PUSH_PROP_FWD);

  if (mPlaybackIndex >= 0)
  {
    sprite->SetPlaySpeed(mPlaybackIndex, 0.0f, *GetScene());
  }
}

bool
cAS_PushProp::Update(float time)
{
  cAnimCharControllerInput *input = GetInput();
  SyCSprite* sprite = GetSprite();

  cGameObject* pObj = GetGameObject();
  cGameObject* pProp = GetGameObject()->GetRegistry()->Fetch(mPropID);

  if (!pProp || pProp->GetStats()->IsDead())
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  if (UpdateHitReactionStateChanges())
  {
    pProp->GetPhysics()->SetVelocity(SyVect3(0.0f, 0.0f, 0.0f));
    return true;
  }

  if (input->mActionRequest)
  {
    input->mActionRequest = false;
    pProp->GetPhysics()->SetVelocity(SyVect3(0.0f, 0.0f, 0.0f));
    mOwner->ChangeState(AS_STAND);
    return true;
  }


  float playspeed = input->mSpeedRequest;

  if (pProp->GetPhysics()->IsPushing())
  {
    playspeed = 0.0f; // can't push if moving something else
  }

// code to allow pulling left right and back
  float curPushHeading = GetPushAngle(AngleDifference(mFaceHeading,input->mHeadingRequest));

  if (playspeed < 0.01f)
  {
    playspeed = 0.0f;
    curPushHeading = 0.0f;
  }

  if (curPushHeading != mPushHeading)
  {
    if (FloatsEqual(0.0f, curPushHeading))
    {
      PlayAnim(ANIM_PUSH_PROP_FWD);
    }
    else if (FloatsEqual(0.25f*SY_PI, curPushHeading))
    {
      PlayAnim(ANIM_PUSH_PROP_FWDRIGHT);
    }
    else if (FloatsEqual(0.5f*SY_PI, curPushHeading))
    {
      PlayAnim(ANIM_PUSH_PROP_RIGHT);
    }
    else if (FloatsEqual(0.75f*SY_PI, curPushHeading))
    {
      PlayAnim(ANIM_PULL_PROP_BACKRIGHT);
    }
    else if (FloatsEqual(SY_PI, curPushHeading))
    {
      PlayAnim(ANIM_PULL_PROP_BACK);
    }
    else if (FloatsEqual(1.25f*SY_PI, curPushHeading))
    {
      PlayAnim(ANIM_PULL_PROP_BACKLEFT);
    }
    else if (FloatsEqual(1.5f*SY_PI, curPushHeading))
    {
      PlayAnim(ANIM_PUSH_PROP_LEFT);
    }
    else if (FloatsEqual(1.75f*SY_PI, curPushHeading))
    {
      PlayAnim(ANIM_PUSH_PROP_FWDLEFT);
    }

    mPushHeading = curPushHeading;
  }

  static const float MAX_PUSH_SPEED = 3.0f;

  SyVect3 displacement(0.0f, 0.0f, 0.0f), newVel, objOffset;

  if (time > 0)
  {
    displacement = pProp->GetLocation()-mLastPropLocation;
  }
  
  mLastPropLocation = pProp->GetLocation();

  newVel.HPR(mFaceHeading-mPushHeading, 0.0f, 0.0f);
  newVel.Normalize();
  newVel *= input->mSpeedRequest*MAX_PUSH_SPEED;

  objOffset.HPR(mFaceHeading, 0.0f, 0.0f);
  objOffset.Normalize();
  objOffset *= -mPushOffset;
  objOffset += displacement;

  pObj->GetPhysics()->SetVelocity(SyVect3(0.0f, -0.1f, 0.0f));
  pObj->SetLocation(pProp->GetLocation()+objOffset);
  pProp->GetPhysics()->SetVelocity(newVel);

  SyMatrix44 transform;
  sprite->CalcMotionDeltaTransform(*GetScene(), transform);

  SyAssert(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())!=NULL);
  float speedMult = (float)(prop_cast<cStatsCharacter*>(GetGameObject()->GetStats())->CalcMovementSpeed())*0.01f;
  playspeed = playspeed + (playspeed * (speedMult - 1.0f));

  if (mPlaybackIndex >= 0)
  {
    sprite->SetPlaySpeed(mPlaybackIndex, playspeed, *GetScene());
  }

  return false;
}

//------------------------------------ cAS_Stunned

void 
cAS_Stunned::Enter()
{
  PlayAnim(ANIM_B_STAND_STUNNED);
}

bool
cAS_Stunned::Update(float time)
{
  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (!GetGameObject()->GetStats()->QueryFlag("Stunned") && 
      !GetGameObject()->GetStats()->QueryFlag("Sleeping"))
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  ClearInput();

  return false;
}

//------------------------------------ cAS_FlipLever

void 
cAS_FlipLever::Enter()
{
  cAnimCharControllerInput* input = GetInput();

  mLeverID = GetInput()->mActionTarget;

  input->mActionRequest = false;

  cGameObject* pObj = GetGameObject();
  cGameObject* pLever = pObj->GetTitan()->GetRegistry()->Fetch(mLeverID);
  cIntelProp* pLeverIntel = prop_cast<cIntelProp*>(pLever->GetIntel());

  int32 animID = ANIM_B_PULL_LEVER;

  if (pLeverIntel && pLeverIntel->IsOn())
  {
    animID = ANIM_B_PUSH_LEVER;
  }

  PlayAnim(animID);
}

bool
cAS_FlipLever::Update(float time)
{
  cGameObject* obj = GetGameObject();
  cAnimControllerSetting* setting = GetSetting();

  if (UpdateHitReactionStateChanges())
  {
    return true;
  }

  if (UpdateActions(time))
  {
    return true;
  }

  float prevTime = mOwner->GetAnimTime();
  float curTime = prevTime + time;
  float actionFrame = 0.3f;

  if (setting)
  {
    actionFrame = setting->mActionFrame;
  }

  actionFrame *= mAnimDuration;

  cGameObject* pLever = obj->GetTitan()->GetRegistry()->Fetch(mLeverID);
  if (!pLever || pLever->GetStats()->IsDead())
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  if (obj->IsLocal())
  {
    if (curTime < actionFrame)
    {

      static const float LEVER_DISTANCE_TOLERANCE = 0.25f;

      SyVect3 offset;
      offset.HPR(pLever->GetHeading(), 0.0f, 0.0f);
      offset.Normalize();
      offset *= 0.5f + obj->GetPhysics()->GetCollisionRadius();
      offset += pLever->GetLocation();

      SyVect3 toLever(offset - obj->GetLocation());
      float distToLever = toLever.NormalizeMagn();
      
      if (distToLever > LEVER_DISTANCE_TOLERANCE)
      {
        static const float LEVER_MOVETO_SPEED = 15.0f;
        
        float moveDist = LEVER_MOVETO_SPEED*time;
        moveDist = SY_MIN(moveDist, distToLever);
        toLever *= moveDist;
        obj->SetLocation(obj->GetLocation()+toLever);
      }

      TurnTo(pLever->GetHeading()+SY_PI, SY_PI*2.0f, 0.00001f);
    }
    else if (prevTime < actionFrame && curTime >= actionFrame)
    {
      pLever->Activate(obj);
    }
  }

  ClearInput();

  if (-1 == mPlaybackIndex || GetSprite()->IsPlaybackStopped(mPlaybackIndex)!=0)
  {
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  return false;
}

//------------------------------------ cAS_WalkTo

void 
cAS_WalkTo::Enter()
{
  SyCSprite* sprite = GetSprite();
  SyScene *scene = GetScene();
  int curIndex;
  cAnimCharControllerInput* input = GetInput();
  mDestination = input->mWalkToDestination;
  mHeading = input->mWalkToHeading;
  mSpeed = input->mWalkToSpeed;

  cGameObject* pObj = GetGameObject();
  pObj->SetHeading(pObj->GetHeadingTowards(mDestination));
  
  input->mWalkTo = false;

  mBlendIndex = -1;
  mOwner->ClearImpact();
  SyRefID WalkAnimID = ANIM_B_MOVE_F_WALK;
  SyRefID RunAnimID = ANIM_B_MOVE_F_RUN;
  
  if (sprite->GetNumPlay() > 0)
  {
    mBlendIndex = sprite->GetFirstPlay();
    sprite->SetPlaybackToCurrentPose(mBlendIndex,*scene); 

    while (sprite->GetNumPlay()>1)
    {
      curIndex = sprite->GetFirstPlay();
      if (curIndex == mBlendIndex)
      {
        curIndex = sprite->GetNextPlay(curIndex);
      }
      sprite->ErasePlay(curIndex,*scene);
    }

    if (sprite->HasAnim(WalkAnimID) && sprite->HasAnim(RunAnimID))
    {
      mPlaybackIndex = sprite->PrependAnimPlay(RunAnimID,0,*scene);
      sprite->PrependAnimPlayWithSync(WalkAnimID,0,mPlaybackIndex,*scene);
      float blendValue = mSpeed;

      cPhysicsAnimated *physics = (cPhysicsAnimated*) GetGameObject()->GetPhysics();

      if (physics->IsPushing())
      {
        blendValue = 0.0f; // can only walk while pushing
      }
      sprite->SetPlaybackBlend(mPlaybackIndex,blendValue);

    }
    else
    {
      GAME_ASSERT(ERROR_ART, false,"Character Missing Run or Walk Animation");
    }

    sprite->SetPlaybackBlendMode(mBlendIndex,SYANIMBLENDMODE_INVERT_TIMED_INTERP,250,*scene);
  }
  else
  {
    if (sprite->HasAnim(WalkAnimID) && sprite->HasAnim(RunAnimID))
    {
      mPlaybackIndex         = sprite->PrependAnimPlay(RunAnimID,0,*scene);
      sprite->PrependAnimPlayWithSync(WalkAnimID,0,mPlaybackIndex,*scene);
      float blendValue = mSpeed;
      sprite->SetPlaybackBlend(mPlaybackIndex,blendValue);
    }
    else
    {
      GAME_ASSERT(ERROR_ART, false,"Character Missing Animation");
    }
  }
  
}


bool
cAS_WalkTo::Update(float time)
{
  cGameObject* pObj = GetGameObject();
  float dist = pObj->GetDistance(mDestination);

  pObj->SetHeading(pObj->GetHeadingTowards(mDestination));

 
  float CLOSE_ENOUGH_DISTANCE = 3.0f * time;

  cAnimCharControllerInput* input = GetInput();
  if (input->mWalkToClear == true)
  {
    input->mWalkToClear = false;
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  if (dist < CLOSE_ENOUGH_DISTANCE )
  {
    pObj->SetLocation(mDestination);
    PlayAnim(ANIM_B_STAND_IDLE);
    mOwner->ChangeState(AS_TURNTO);
    return true;
  }

  return false;
}

//------------------------------------ cAS_TurnTo

void 
cAS_TurnTo::Enter()
{
  mTurnSpeed = smTurnSpeed;

  cGameObject* pObj = GetGameObject();
  cAnimCharControllerInput* input = GetInput();
  mDestination = input->mWalkToDestination;

  float dist = pObj->GetDistance(mDestination);
  float CLOSE_ENOUGH_DISTANCE = 0.3f;
  if (dist < CLOSE_ENOUGH_DISTANCE) 
  {
    pObj->SetLocation(mDestination);
    mTurnTo = input->mWalkToHeading;
  }
  else
  {
    mTurnTo = pObj->GetHeadingTowards(mDestination);
  }
  mWalkToTurnSpeed = input->mWalkToTurnSpeed;
  mWalkToTurnThreshold = SY_DEG_TO_RAD(10.0f);
  
  input->mWalkTo = false;
  mTurning = false;

  PlayAnim(ANIM_B_STAND_IDLE);
}


bool
cAS_TurnTo::Update(float time)
{
  //PlayTurnAnim();  Turn anims broken right now...
  float delta = TurnTo(mTurnTo,time*mWalkToTurnSpeed,0.0f);

  cAnimCharControllerInput* input = GetInput();
  if (input->mWalkToClear == true)
  {
    input->mWalkToClear = false;
    mOwner->ChangeState(AS_STAND);
    return true;
  }

  if (delta == 0.0f)
  {
    cGameObject* pObj = GetGameObject();

    float dist = pObj->GetDistance(mDestination);
    float CLOSE_ENOUGH_DISTANCE = 3.0f * time;//pObj->GetPhysics()->GetVelocity().Magnitude() * time;
    if (dist < CLOSE_ENOUGH_DISTANCE )
    {
      pObj->SetLocation(mDestination);
      mOwner->ChangeState(AS_STAND);
      return true;
    }
    else
    {
      mOwner->ChangeState(AS_WALKTO);
      return true;
    }
  }

  return false;
}

//---------------------------------------------------------------------
// Prop Animation
//---------------------------------------------------------------------

const char *l_PropAnimStatenames[] = 
{
  "Base",
  "Destroy",
  "Activate",
  "Deactivate",
  "Misc"
};

class cAnimPropController : public cAnimPropControllerInterface
{
public:
  cAnimPropController();
  virtual ~cAnimPropController();

  virtual void            Init(cGameObject *owner);
  virtual void            Reset();
  virtual void            Update(float time);
  virtual cGameObject*    GetOwner();
  virtual void            NetworkReceiveBroadcast(const char *state, int statelen);

  virtual PropAnimState   GetAnimState() const;
  virtual const char *    GetAnimStateName() const;

  virtual void            ChangeAnimState(PropAnimState newState);

private:
  bool                    PlayAnim(int32 animid);
  void                    NetworkSendBroadcast(PropAnimState state);

  cGameObject *           mOwner;
  PropAnimState           mCurState;
  int32                   mPlaybackIndex; // for our animation...
};  

cAnimPropController::cAnimPropController()
: mOwner(NULL),
  mCurState(PAS_BASE),
  mPlaybackIndex(-1)
{
}

cAnimPropController::~cAnimPropController()
{

}

void cAnimPropController::Init(cGameObject *owner)
{
  mOwner = owner;
  Reset();
}

void cAnimPropController::Reset()
{
  // just change state w/o network messages
  mCurState = PAS_BASE;
  PlayAnim(PAS_BASE);
}

void cAnimPropController::Update(float time)
{
}

void cAnimPropController::ChangeAnimState(PropAnimState newState)
{
  mCurState = newState;
  PlayAnim(newState);

  if (!mOwner->IsRemote())
  {
    NetworkSendBroadcast(mCurState);
  }
}

cGameObject* cAnimPropController::GetOwner()
{
  return mOwner;
}

void cAnimPropController::NetworkReceiveBroadcast(const char *state, int statelen)
{
  cNetAnimStatePacket pPacket;
  pPacket.UnpackBuffer(state, statelen);

  SyAssertf(mOwner->IsRemote(), "Owner receiving network instructions?"); 
  ChangeAnimState((PropAnimState)pPacket.mAnimState);
}

void cAnimPropController::NetworkSendBroadcast(PropAnimState state)
{
  cNetAnimStatePacket packet;
  packet.mAnimState = (int)state;

  char buf[1024];
  int len = packet.PackBuffer(buf, sizeof(buf));
  mOwner->GetTitan()->GetPeeringNetwork()->ObjectBroadcast(mOwner->GetID(), buf, len);
}                         

cAnimPropController::PropAnimState cAnimPropController::GetAnimState() const
{
  return mCurState;
}

const char* cAnimPropController::GetAnimStateName() const
{
  return l_PropAnimStatenames[mCurState];
}

cAnimPropControllerInterface *cAnimPropControllerInterface::Allocate()
{
  return SyNew cAnimPropController;
}

bool cAnimPropController::PlayAnim(int32 animid)
{
  cGraphicActor *graphic = (cGraphicActor*) mOwner->GetGraphic();
  SyScene *scene = mOwner->GetRegistry()->GetTitan()->GetScene();  
  
  SyAssert(scene->GetActorSpriteType(graphic->GetActorHandle()) == SYSPRITETYPE_CSPRITE);

  SyCSprite* sprite = (SyCSprite*)(scene->GetActorSpritePtr(graphic->GetActorHandle()));

  if (!sprite->HasAnim(animid))
  {
    if (-1 != mPlaybackIndex)
    {
      sprite->ErasePlay(mPlaybackIndex, *scene);
      mPlaybackIndex = -1;
    }

    return false;
  }

  int blendIndex = -1;
  int curIndex;
  if (sprite->GetNumPlay() > 0)
  {
    int numAnims = 1; // num anims to save from those currently playing

    blendIndex = sprite->GetFirstPlay();
    sprite->SetPlaybackToCurrentPose(blendIndex,*scene); 

    while (sprite->GetNumPlay() > numAnims)
    {
      curIndex = sprite->GetFirstPlay();
      while (curIndex == blendIndex)
      {
        curIndex = sprite->GetNextPlay(curIndex);
      }
      sprite->ErasePlay(curIndex,*scene);
    }
  }
  else
  {
    sprite->EraseAllPlay(*scene);
  }

  mPlaybackIndex = sprite->PrependAnimPlay(animid,0,*scene);

  if (-1 != blendIndex)
  {
    sprite->SetPlaybackBlendMode(blendIndex, SYANIMBLENDMODE_INVERT_TIMED_INTERP, 250, *scene);
  }

  return true;
}

// EOF              
