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

//-------------------------------------------------------- Includes
#include "physics.h"
#include "gameobj.h"
#include <stddef.h>
#include "graphic.h"
#include "SyScene.h"
#include "SySceneFilter.h"
#include "Titan.h"
#include "animcontroller.h"
#include "registry.h"
#include "database.h"
#include "script.h"
#include "stats.h"
#include "intel.h"
#include "tuning.h"
#include "spell.h"
#include "TitanPeeringNetwork.h"
#include "cameracontroller.h"
#include "debugoverlay.h"
#include "SyHavok.h"
#include "gameerror.h"

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

/* Floor Collision Filter */

class cFloorFilter : public SySceneFilter
{
public:

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


class cRingFilter : public SySceneFilter
{
public:

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

static const float RUBBER_BAND_TIME = 0.2f;
static const float REST_MIN_VELOCITY = 0.3f;
static const float REST_MIN_FLATNESS = 0.6f;
static const float THROWN_CHARACTER_COLLISION_RADIUS_INCREASE = 0.5f;
const float cPhysics::GRAVITY = -9.8f; // meters/sec
float l_CHARACTER_FRICTION = 10.0f;
int l_THROWN_CHARACTER_DAMAGE = 10;
float l_PROP_KNOCKBACK_REDUCE = 0.25f;
float l_THROWN_CHARACTER_KNOCKBACK_REDUCE = 0.75f;
float l_THROWN_PROP_MIN_SPEED = 25.0f;
float l_THROWN_PROP_MAX_SPEED = 30.0f;
float l_JUMP_GRAVITY = -9.8f;
float l_JUMPATTACK_GRAVITY = -9.8f;
float l_KNOCKDOWN_GRAVITY = -9.8f;
float l_MAX_STEP_SIZE = 0.5f;
float l_FALLING_DAMAGE_THRESHOLD = 5.0f;
float l_FALLING_DAMAGE_HITPOINTS_PER_METER = 1.0f;

//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions

//------------------------------------ cPhysics
cPhysics::cPhysics() : 
  mCollideable(true), 
  mOwner(NULL)
{
  InitPropObject( mCLASSID );
}

void 
cPhysics::SetOwner(cGameObject *owner)
{
  mOwner = owner;
}

int           
cPhysics::InitPropClass()
{

/* Add the class */

  AddClass( mCLASSID, 
            "cPhysics", 
            NULL,       // CRO: abstract base class has no creator
            mCLASSID, 
            0 ); 
  return 0;
}

SyPropObject* 
cPhysics::Creator()
{
  SyAssertf(0,"Trying to object-factory an abstract base class");

  return(NULL);
}

void 
cPhysics::Init()
{
  SetCollideable(mCollideable);
}


const SyVect3 &
cPhysics::GetVelocity() 
{
  static SyVect3 vel(0,0,0);
  return vel;
};

void
cPhysics::SetVelocity(const SyVect3& velocity)
{
  SyAssertf(false, "Calling SetVelocity on a cPhysics subclass that hasn't implemented it");
}

void 
cPhysics::NetworkSetLocation(const SyVect3& location, bool bTeleport)
{
  mOwner->SetLocation(location);
}


void 
cPhysics::NetworkSetHPR(const SyVect3& hpr)
{
  mOwner->SetHPR(hpr);
}

const SyVect3&
cPhysics::GetRenderLocation()
{
  return mOwner->GetLocation();
}

const SyVect3&          
cPhysics::GetRenderHPR()
{
  return mOwner->GetHPR();
}


bool           
cPhysics::SetSafeLocation(const SyVect3 &loc)// finds a safe location near this spot
{
  mOwner->SetLocation(loc);
  return true;
}


void
cPhysics::SetCollideable(bool collide)
{
  mCollideable = collide;

  SyActorHandle handle = mOwner->GetGraphic()->GetActorHandle();

  if (handle != SyActorNull)
  {
    mOwner->GetTitan()->GetScene()->SetActorCollideable(handle, mCollideable);
  }
}

//------------------------------------ cPhysicsStatic

cPhysicsStatic::cPhysicsStatic()
{
  InitPropObject( mCLASSID );
}
int           
cPhysicsStatic::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysics::mCLASSID,
               mCLASSID,
               "cPhysicsStatic", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cPhysicsStatic::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsStatic();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

//------------------------------------ cPhysicsDynamic

cPhysicsDynamic::cPhysicsDynamic() :
mIsMovingCountdown(0),
mVelocity(0.0f, 0.0f, 0.0f),
mLastValidFloorY(0.0f),
mbFalling(false),
mbIsUnderHavokControl(false)
{
  InitPropObject( mCLASSID );
}

int           
cPhysicsDynamic::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysics::mCLASSID,
               mCLASSID,
               "cPhysicsDynamic", 
               Creator, 
               mCLASSID, 
               0 ); 

  AddVect3Property(mCLASSID,PropId_Velocity,SyMemberOffset(cPhysicsDynamic,mVelocity),"mVelocity");

  return 0;
}

void 
cPhysicsDynamic::Init()
{
  cPhysics::Init();
  mbIsUnderHavokControl = false;
  mLastValidFloorY = mOwner->GetLocation().Y;
  mbFalling = false;
}

void 
cPhysicsDynamic::PostInit()
{
  if (mOwner->IsRemote())
  {
    mNetworkUpdateTime = RUBBER_BAND_TIME;
    mRenderLocation = mOwner->GetLocation();
    mRenderHPR = mOwner->GetHPR();
    mRenderOffset(0.0f, 0.0f, 0.0f);
  }
}

void 
cPhysicsDynamic::Reset()
{
  cPhysics::Reset();

  mVelocity(0.0f, 0.0f, 0.0f);
  mLastValidFloorY = mOwner->GetLocation().Y;
  mbFalling = false;

  if (mOwner->IsRemote())
  {
    mNetworkUpdateTime = RUBBER_BAND_TIME;
    mRenderLocation = mOwner->GetLocation();
    mRenderHPR = mOwner->GetHPR();
    mRenderOffset(0.0f, 0.0f, 0.0f);
  }
}

SyPropObject* 
cPhysicsDynamic::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsDynamic();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void cPhysicsDynamic::UpdateRemoteRenderLocation(float time)
{
  if (mOwner->IsRemote())
  {
    mNetworkUpdateTime += time;
    float frac = mNetworkUpdateTime/RUBBER_BAND_TIME;
    if (frac > 1.0f)
    {
      frac = 1.0f;
    }
    frac =  1.0f - frac;
    SyVect3 offset(0.0f,0.0f,0.0f);

    offset.Mul(mRenderOffset,frac);
    mRenderLocation = mOwner->GetLocation()+offset;
    mRenderHPR = mOwner->GetHPR();
  }
}

bool                 
cPhysicsDynamic::Push(float time,SyVect3 &displacement)
{
  if (!mOwner->GetStats()->IsPhysicsPushable())
  {
    return false;
  }
  
  CollisionTestPush(time,displacement);

  SyScene *scene = mOwner->GetRegistry()->GetTitan()->GetScene();
  scene->SetActorLocation(mOwner->GetGraphic()->GetActorHandle(),mOwner->GetLocation());
  mIsMovingCountdown = 4;
  mVelocity.Y -= 0.01f;

  if (mOwner->IsRemote())
  {
    // push render location too...
//BP?    mRenderLocation = mOwner->GetLocation();
//BP?    mRenderLocationOffset = SyVect3(0.0f, 0,0);

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

  return true;
}

void cPhysicsDynamic::StartFallingDamageCheck()
{
  if (!mbFalling)
  {
    mLastValidFloorY = mOwner->GetLocation().Y;
    mbFalling = true;
  }
}

void cPhysicsDynamic::StopFallingDamageCheck()
{
  if (mbFalling)
  {
    if (mOwner->IsLocal())
    {
      float heightDelta = mLastValidFloorY - mOwner->GetLocation().Y;

      GAME_ASSERT(ERROR_DESIGN, l_FALLING_DAMAGE_THRESHOLD > 0.0f, "FallingDamage_Threshold in tuning.xml must be positive value - %f", l_FALLING_DAMAGE_THRESHOLD);
      GAME_ASSERT(ERROR_DESIGN, l_FALLING_DAMAGE_HITPOINTS_PER_METER >= 0.0f, "FallingDamage_HitPointsPerMeter in tuning.xml must >= 0 - %f", l_FALLING_DAMAGE_HITPOINTS_PER_METER);
      if (heightDelta >= l_FALLING_DAMAGE_THRESHOLD)
      {
        // apply damage to object because we hit the ground
        float amt = heightDelta * l_FALLING_DAMAGE_HITPOINTS_PER_METER;
        cDamagePacket selfDamagePacket;
        selfDamagePacket.SetTotal(SY_ROUND(amt), "Falling Damage", "Physics");
        selfDamagePacket.mAttackerID = ID_NONE;
        selfDamagePacket.mAgentID = mOwner->GetID();
        selfDamagePacket.mDefenderID = mOwner->GetID();
        mOwner->GetStats()->ApplyDamage(&selfDamagePacket);
      }
    }

    mbFalling = false;
  }
}

bool
cPhysicsDynamic::IsJustBouncingAround()
{
#ifdef HAVOK_ENABLED
  if (!mOwner->GetRigidBody()) return false;
  SyScene *scene = mOwner->GetTitan()->GetScene();
  SyHavok *havok = scene->GetCollideDev()->GetHavok();
  if ((!havok->IsActive( mOwner->GetRigidBody() )) || SyHavok::IsKeyframed( mOwner->GetRigidBody()))
  {
    mbIsUnderHavokControl = false;
  }
  else
  {
    mbIsUnderHavokControl = true;
  }
  return mbIsUnderHavokControl;
#else
  return false;
#endif
}

void cPhysicsDynamic::SetCollideable(bool collide)
{
  cPhysics::SetCollideable( collide );
#ifdef HAVOK_ENABLED
  if (!mOwner->GetRigidBody()) return;
  SyScene *scene = mOwner->GetTitan()->GetScene();
  SyHavok *havok = scene->GetCollideDev()->GetHavok();
  havok->SetPropCollideable( mOwner->GetRigidBody(), collide );
#endif
}

int cPhysicsDynamic::SetKeyframed( SyVect3& loc, SyVect3& hpr)
{
#ifdef HAVOK_ENABLED
  if (mOwner->GetRigidBody()) 
  {
    SyScene *scene = mOwner->GetTitan()->GetScene();
    SyHavok *havok = scene->GetCollideDev()->GetHavok();
    return havok->KeyframeRigidBody( mOwner->GetRigidBody(), loc, hpr );
  }
#endif
  return 0;
}

#ifdef HAVOK_ENABLED
void cPhysicsDynamic::UpdateHavok(float time)
{
  SyVect3 loc, lastLoc(mOwner->GetLocation());

  if (mOwner->IsRemote())
  {
    SyVect3 displacement(mVelocity);
    displacement *= time;
    mVelocity.Y += GRAVITY * time;
    mRenderLocation += displacement;
    mRenderOffset.Sub(mRenderLocation, lastLoc);
    UpdateRemoteRenderLocation(time);
    loc = mRenderLocation;
  }
  else
  {
    SyScene *scene = mOwner->GetTitan()->GetScene();
    SyHavok *havok = scene->GetCollideDev()->GetHavok();

    SyVect3 nowLoc;
    havok->GetPosition( mOwner->GetRigidBody(), nowLoc );

    SyMatrix44 m;
    havok->GetMatrix( mOwner->GetRigidBody(), m );
    SyVect3 hpr, scale;
    m.ConvertTo( hpr, scale, loc );

    mVelocity.Sub( nowLoc, lastLoc );
    mOwner->SetLocation( loc );
    mOwner->SetHPR( hpr );
  }

  SyWaterSystem * pWaterSystem = mOwner->GetTitan()->GetScene()->GetWaterSystem();
  if (pWaterSystem)
  {
    loc.Y -= 1.0f;
    pWaterSystem->DemoPlunk( loc, 0.75f );
  }    
}
#endif

void         
cPhysicsDynamic::Update(float time)
{
//  DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", mOwner->GetLocation()+mRenderOffset, 1.0f, cDebugOverlay::WHITE);
  DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", mRenderLocation, 1.0f, cDebugOverlay::YELLOW);
  DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", mOwner->GetLocation(), 0.5f, cDebugOverlay::CYAN);

#ifdef HAVOK_ENABLED
  if (mOwner->GetRigidBody())
  {
    SyHavok* pHavok = mOwner->GetTitan()->GetScene()->GetCollideDev()->GetHavok();
    SyVect3 rbLoc;
    pHavok->GetPosition(mOwner->GetRigidBody(), rbLoc);
    DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", rbLoc, 0.5f, cDebugOverlay::GRAY);
  }
#endif

#ifdef HAVOK_ENABLED
  bool bWasUnderHavok = mbIsUnderHavokControl;

  if (IsJustBouncingAround())
  {
    mIsMovingCountdown = 4;
    UpdateHavok(time);
  }
  else if (bWasUnderHavok)
  {
    SyVect3 myLoc(mOwner->GetLocation()), myHPR(mOwner->GetHPR());
    mRenderOffset.Sub(mRenderLocation, myLoc);
    SetKeyframed(myLoc, myHPR);
    mIsMovingCountdown = 0;
  }
  else 
#endif
  if (IsMoving())
  {
    SyVect3 displacement;
    float accel = GRAVITY * time;    

    mVelocity.Y += accel;

    displacement.MulAdd(mVelocity,time);

    CollisionTest(time,displacement);
  }

  if (!mbIsUnderHavokControl)
  {
    UpdateRemoteRenderLocation(time);
  }
}

void         
cPhysicsDynamic::UpdateOutOfScope(float time)
{
#ifdef HAVOK_ENABLED
  if (IsJustBouncingAround())
  {
    UpdateHavok(time);
  }
#endif

  UpdateRemoteRenderLocation(time);
}

void                 
cPhysicsDynamic::SetVelocity(const SyVect3 &velocity)
{
  mVelocity = velocity;
  mIsMovingCountdown = 4;
}


void                 
cPhysicsDynamic::CollisionTest(float time,SyVect3 &displacement)
{
  SyVect3       Start, End;
  SyCollSphere  CollSphere;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  //cStatsProp *stats = (cStatsProjectile *) mOwner->GetStats();
  // TODO: get push collision data from object
  float OBJ_RADIUS = 0.3f;
  float OBJ_HEIGHT = OBJ_RADIUS;

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  Start.Y += OBJ_HEIGHT;
  End       = Start + displacement;

  
  CollSphere.Init( Start, End,OBJ_RADIUS);
  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ))
  {
    // dot product
    SyVect3 normal = CollSphere.GetHitNormal();

    float dot = mVelocity ^ normal; 
    float BOUNCE_FACTOR = 1.4f; // 1.0 = inelastic, 2.0 = perfectly elastic
    SyVect3 restitution;
    restitution.Mul(normal,(BOUNCE_FACTOR * dot));
    mVelocity -= restitution; 

    // finish out motion?

    End = CollSphere.GetHitPoint();
    
    if (normal.Y > REST_MIN_FLATNESS)
    {
      float XZSpeed = mVelocity.X * mVelocity.X + mVelocity.Z * mVelocity.Z;
      if (XZSpeed < REST_MIN_VELOCITY)
      {
        // you could be coming to rest on a player or another moving object; make sure that's not
        // the case...
        SyActorHandle handle = Filter.GetActor();
        if (handle != 0)
        {
          cGameObject *obj = mOwner->GetRegistry()->FetchByActorHandle(handle);
          if (obj == NULL || (!obj->GetPhysics()->IsMoving() && mIsMovingCountdown > 0))
          {
            mIsMovingCountdown--;
          }
          // otherwise, we've landed on a player or npc; don't quit moving.
          // in fact, let's bounce you up a little
          mVelocity.Y = GRAVITY * 0.3f;
        }
        else if (mIsMovingCountdown > 0)
        {
          mIsMovingCountdown--;
        }
      }
    }

    // friction
    mVelocity *= 0.8f;
    End.Y -= OBJ_HEIGHT;
    mOwner->SetLocation(End);
    return;
    
  }

  /* Retrieve the result of the collision/skitters */

  End = CollSphere.GetHitPoint();
  End.Y -= OBJ_HEIGHT;
  mOwner->SetLocation(End);

  return;
}

// sgc hack: todo: rewrite physics significantly.
// after collision data comes from object, we should be able to get rid of this.
void                 
cPhysicsDynamic::CollisionTestPush(float time,SyVect3 &displacement)
{
  SyVect3       Start, End;
  SyCollSphere  CollSphere;
  SyVect3        Floor, FloorNormal;
  SyCollSurfType FloorSurfaceType;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  //cStatsProp *stats = (cStatsProjectile *) mOwner->GetStats();
  // TODO: get push collision data from object
  float PROP_HEIGHT = 0.5f;
  float PROP_RADIUS = 0.2f;

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  Start.Y += PROP_HEIGHT;
  End       = Start + displacement;


  CollSphere.Init( Start, End,PROP_RADIUS);
  /* Collide with the world */

  scene->Collide( CollSphere, Filter );

  /* Retrieve the result of the collision/skitters */

  End = CollSphere.GetHitPoint();

  if(CalcFloor( End, *scene, Floor, FloorNormal, FloorSurfaceType,PROP_RADIUS,PROP_HEIGHT ))
  {
    /* Is the object touching the floor - move the object up to the floor level?  */
    if ((End.Y - Floor.Y) < PROP_HEIGHT && mVelocity.Y <= 0.0f)
    {
      End.Y = Floor.Y + PROP_HEIGHT;    
      mVelocity.Y = 0.0f;  
    }       
  }

  End.Y -= PROP_HEIGHT;
  mOwner->SetLocation(End);

  return;
}


// we own this object, and a peer is making a request
bool                
cPhysicsDynamic::NetworkReceiveMessage(const char *packet,int size) 
{
  cNetPacket::eNetPacketType type;

  SyAssertf(mOwner->IsLocal(),"Network Message on remote object?");

  SyPack::Unpack8u(packet, &type);   

  switch (type)
  {
  case cNetPacket::NET_PUSH_REQUEST:
    {
      // just the fact that we exist means the item hasn't been picked up by anyone else...

      // well if two critters are pushing it, it might be a problem...not sure how to resolve this
      // lets just go ahead and move it for now...
      cNetPushRequestPacket request;
      request.UnpackBuffer(packet, size);

      mOwner->SetLocation(request.mLocation);
      mIsMovingCountdown = 4;
      return true;
    }
    break;
  case cNetPacket::NET_KNOCKBACK:
    {
      cNetKnockbackPacket request;
      request.UnpackBuffer(packet, size);
      Knockback(request.mLocation,request.mXZAmount,request.mYAmount,request.mbKnockdown,request.mKnockdownTime);
      return true;
    }
    break;
  default:
    SyAssertf(0,"Unknown packet type");
    break;
  }

  return false;
}

void 
cPhysicsDynamic::NetworkSetLocation(const SyVect3 &location, bool bTeleport)
{  
  mNetworkUpdateTime = 0.0f;

  if (bTeleport)
  {
    mRenderLocation = location;
    mRenderOffset(0.0f, 0.0f, 0.0f);
  }
  else
  {
    mRenderOffset.Sub(mRenderLocation,location);
  }

  mOwner->SetLocation(location);
}

void cPhysicsDynamic::NetworkSetVelocity(const SyVect3 &velocity)
{
  mVelocity = velocity;

  if (mVelocity.MagnitudeSquared() > 0.0001f)
  {
    mIsMovingCountdown = 4;
  }
}

/*******************************/
/* CalcFoor                    */
/*******************************/

int cPhysicsDynamic:: CalcFloor( 
                                SyVect3&        Point, 
                                SyScene&        Scene, 
                                SyVect3&        Floor, 
                                SyVect3&        FloorNormal,
                                SyCollSurfType& FloorSurfaceType,
                                float radius,
                                float height)
{
  cFloorFilter        Filter;
  SyCollSphere        CollSphere;
  SyVect3             Start, End;

  /* Initialize the filter */

  cGraphicActor *graphic = (cGraphicActor*) mOwner->GetGraphic();
  SyActorHandle handle = graphic->GetActorHandle();
  Filter.Init( handle );

  /* Initialize the collision */

  Start     = Point;
  End       = Point;
  End.Y    -= radius;

  CollSphere.Init( Start, End, radius );

  /* Perform the floor collision */

  if(Scene.Collide( CollSphere, Filter ))
  {
    Floor     = CollSphere.GetHitPoint();
    Floor.Y  -= radius;

    FloorNormal      = CollSphere.GetHitNormal();
    FloorSurfaceType = CollSphere.GetHitSurfaceType();

    float MIN_VELOCITY = 0.1f;
    if (mVelocity.Magnitude() < MIN_VELOCITY)
    {
      SyActorHandle handle = Filter.GetActor();
      if (handle != 0)
      {
        cGameObject *obj = mOwner->GetRegistry()->FetchByActorHandle(handle);
        if (obj == NULL || (!obj->GetPhysics()->IsMoving() && mIsMovingCountdown > 0))
        {
          mIsMovingCountdown--;
        }
      }
      else if (mIsMovingCountdown > 0)
      {
        mIsMovingCountdown--;
      }
    }
    return(1);
  }

  Floor = Point;
  FloorNormal( 0.0f, 0.0f, 0.0f );
  FloorSurfaceType = SYCOLLSURFTYPE_NONE;

  return(0);
}
//------------------------------------ cPhysicsProp

cPhysicsProp::cPhysicsProp()
: mThrowerID(ID_NONE),
  mLastHitID(ID_NONE),
  mDamageAmount(0)
{
  InitPropObject( mCLASSID );
}

int           
cPhysicsProp::InitPropClass()
{
  /* Add the class */

  AddSubClass( mCLASSID, 
    cPhysicsDynamic::mCLASSID,
    mCLASSID,
    "cPhysicsProp", 
    Creator, 
    mCLASSID, 
    0 ); 
  return 0;
}

SyPropObject* 
cPhysicsProp::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsProp();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void cPhysicsProp::PostInit() 
{
  cPhysicsDynamic::PostInit();

#ifdef HAVOK_ENABLED
  SyHavok::SetUserData(mOwner->GetRigidBody(), (void*)mOwner->GetID());
#endif
}

void cPhysicsProp::Reset()
{
  cPhysicsDynamic::Reset();
  mThrowerID = ID_NONE;
  mLastHitID = ID_NONE;
}

float 
cPhysicsProp::GetExtentInDirection(const SyVect3& loc,
                                   const SyVect3& dir,
                                   float width,
                                   SyVect3* pHitPoint)
{
  SyScene* pScene = mOwner->GetTitan()->GetScene();
  SyActorHandle actorHandle = mOwner->GetGraphic()->GetActorHandle();

  if (SyActorNull != actorHandle)
  {
    int32 spriteHandle = pScene->GetActorSprite(actorHandle);
    SySprite* pPropSprite = pScene->Sprite(spriteHandle);

    if (pPropSprite)
    {
      SyCollSphere sphere;
      SySceneFilter filter;
      SyVect3 start, end, castDir;
      SyMatrix44 propMat;

      pScene->GetActorTransform(actorHandle, propMat);

      start = end = loc;
      castDir = dir;
      castDir.Y = 0.0f;
      castDir.Normalize();
      start.MulAdd(castDir, -20.0f);
      end.MulAdd(castDir, 20.0f);
      start.Y = end.Y = loc.Y + 0.25f;

      sphere.Init(start, end, width*0.5f);
      filter.Init();

      pScene->GetCollideDev()->SetWorld(propMat);    

      bool bCollided = pPropSprite->Collide(sphere, filter, *pScene, *pScene->GetCollideDev()) != 0;

      if (bCollided)
      {
        if (pHitPoint)
        {
          *pHitPoint = sphere.GetHitPoint();
        }

        return loc.Distance(sphere.GetHitPoint());
      }
    }
  }

  return loc.Distance(mOwner->GetLocation()) - GetCollisionRadius();
}

void
cPhysicsProp::Update(float time)
{
  if (ID_NONE != mThrowerID)
  {
    static const float SPIN_SPEED_H = SY_DEG_TO_RAD(360.0f) * 1.0f; // revs/sec
    static const float SPIN_SPEED_P = -SY_DEG_TO_RAD(360.0f) * 0.3f; // revs/sec
    static const float SPIN_SPEED_R = SY_DEG_TO_RAD(360.0f) * 0.1f; // revs/sec
    
    float heading = mOwner->GetHeading();
    heading += SPIN_SPEED_H * time;
    mOwner->SetHeading(heading);

    float pitch = mOwner->GetPitch();
    pitch += SPIN_SPEED_P * time;
    mOwner->SetPitch(pitch);

    float roll = mOwner->GetRoll();
    roll += SPIN_SPEED_R * time;
    mOwner->SetRoll(roll);

    SyVect3 loc = mOwner->GetLocation();
    SyVect3 hpr = mOwner->GetHPR();
    cPhysicsDynamic::SetKeyframed( loc, hpr  );
    //mOwner->SetHPR( hpr );
    //mOwner->SetLocation( loc );
  }
  cPhysicsDynamic::Update(time);
}


void
cPhysicsProp::Throw(cGameObject* pThrower, const SyVect3& targetPos)
{
  static const float THROWN_PROP_GRAVITY_MULT = 1.0f;

  SyAssert(prop_cast<cStatsProp*>(mOwner->GetStats())!=NULL &&
           static_cast<cStatsProp*>(mOwner->GetStats())->IsLiftable() &&
           prop_cast<cStatsCharacter*>(pThrower->GetStats())!=NULL);

  SyAssert(pThrower);
  mThrowerID = pThrower->GetID();
  float headingTowards = mOwner->GetHeadingTowards(targetPos);

  SyVect3 delta(targetPos - mOwner->GetLocation());
  SyVect3 vel(0.0f, 0.0f, 0.0f);

  float delta_height = delta.Y+0.5f;
  delta.Y = 0.0f;
  float distance = delta.Magnitude();

  int classID = static_cast<cStatsCharacter*>(pThrower->GetStats())->GetMaster()->mClass;
  SyAssert(pThrower->GetTitan()->GetDatabaseSys()->GetCharacterClass(classID) && pThrower->GetTitan()->GetDatabaseSys()->GetCharacterClass(classID)->mStats.mThrowStrength > 0.0f);
  SyAssertf(l_THROWN_PROP_MAX_SPEED >= l_THROWN_PROP_MIN_SPEED, "Thrown character max speed < min! in tuning.xml");

  float speed = ((l_THROWN_PROP_MAX_SPEED - l_THROWN_PROP_MIN_SPEED)*pThrower->GetTitan()->GetDatabaseSys()->GetCharacterClass(classID)->mStats.mThrowStrength*0.01f) + l_THROWN_PROP_MIN_SPEED;

  if (distance > 0.0f && speed > 0.0f)
  {
    float target_time = distance/speed; 
    vel.Y = delta_height/target_time - target_time * THROWN_PROP_GRAVITY_MULT * GRAVITY * 0.5f;
  }
  else
  {
    vel.Y = THROWN_PROP_GRAVITY_MULT * GRAVITY * 0.5f;
  }

  vel.X = SY_SIN(headingTowards) * speed;
  vel.Z = SY_COS(headingTowards) * speed;
  SetVelocity(vel);

  cDamagePacket targetDamage;
  targetDamage.mAgentID = mOwner->GetID();
  pThrower->GetStats()->CalcRangedAttack(&targetDamage);
  mDamageAmount = targetDamage.GetTotal();

  float mult = static_cast<cStatsProp*>(mOwner->GetStats())->GetMaster()->mThrownDamageMultiplier;
  mDamageAmount = SY_ROUND(mDamageAmount*mult);

  mLastHitID = ID_NONE;
}

#ifdef HAVOK_ENABLED
void
cPhysicsProp::CollisionTest(float time,SyVect3 &displacement)
{
  SyVect3 originalvelocity = mVelocity;
  if (displacement.Magnitude() < 0.00001f)
  {
    return;
  }

  SyVect3        Start, End;
  SyCollSphere   CollSphere;
  SyVect3        Floor, FloorNormal;
  SyCollSurfType FloorSurfaceType;
  int32          Skitter, MaxSkitters = 2;
  SyVect3        AntiSkitter(0.0f, 0.0f, 0.0f), restitution;
  float          dot, mag;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  //cStatsProp *stats = (cStatsProjectile *) mOwner->GetStats();
  // TODO: get push collision data from object
  float PROP_HEIGHT = 1.0f;
  float PROP_RADIUS = 1.0f;

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  Start.Y += PROP_HEIGHT;
  End      = Start + displacement;


  CollSphere.Init( Start, End,PROP_RADIUS);
  /* Collide with the world */

  bool bCollided = false;

  if (scene->Collide( CollSphere, Filter ))
  {
    bCollided = true;

    if (ID_NONE != mThrowerID)
    {
      SyActorHandle hitHandle = Filter.GetActor();

      if (hitHandle != 0)
      {
        cGameObject *pHit = mOwner->GetRegistry()->FetchByActorHandle(hitHandle);

        if (pHit && (pHit == mOwner || pHit->GetID() == mThrowerID))
        {
          bCollided = false;
        }
        else if (pHit)
        {
          ApplyThrowDamage(pHit);
        }
      }
    }
  }

  if (bCollided)
  {
    int damage = static_cast<cStatsProp*>(mOwner->GetStats())->GetMaster()->mMaxHealth;
    int numThrows = static_cast<cStatsProp*>(mOwner->GetStats())->GetMaster()->mNumThrows;

    if (ID_NONE == mThrowerID || damage <= 0)
    {
      Skitter = 0;
      while(Skitter < MaxSkitters)
      {
        // when we hit something, slow down our velocity
        static const float BOUNCE_FACTOR = 1.0f; // 1.0 = inelastic, 2.0 = perfectly elastic
        dot = mVelocity ^ CollSphere.GetHitNormal(); 
        restitution.Mul(CollSphere.GetHitNormal(),(BOUNCE_FACTOR * dot));
        //mVelocity -= restitution; 
        CollSphere.Skitter( AntiSkitter );
        if(!scene->Collide( CollSphere, Filter ))
        {
          break;
        }
        Skitter++;
      }
    }

    End = CollSphere.GetHitPoint();

    if (ID_NONE != mThrowerID)
    {
      mThrowerID = ID_NONE;

      mbIsUnderHavokControl = true;

      SyHavok *pHavok = scene->GetCollideDev()->GetHavok();
      if (SyHavok::PlantRigidBody((void*)mOwner->GetRigidBody(), CollSphere.GetHitPoint()))
      {
        pHavok->GetPosition(mOwner->GetRigidBody(), End);
      }

      if (damage > 0 && mOwner->IsLocal())
      {
        // apply damage to prop because we hit something
        cDamagePacket selfDamagePacket;
        if (numThrows > 0)
        {
          selfDamagePacket.SetTotal(SY_MAX(damage/numThrows, 1), "Thrown Prop Collision", "Physics");
        }
        else
        {
          selfDamagePacket.SetTotal(damage+1, "Thrown Prop Collision", "Physics");
        }

        selfDamagePacket.mAttackerID = mOwner->GetID();
        selfDamagePacket.mAgentID = mOwner->GetID();
        selfDamagePacket.mDefenderID = mOwner->GetID();
        mOwner->GetStats()->ApplyDamage(&selfDamagePacket);
      }
    }

    if (!mbIsUnderHavokControl)
    {
      mag = mVelocity.NormalizeMagn();
      mVelocity *= SY_MAX(mag - (l_CHARACTER_FRICTION*time), 0.0f);

      if (CollSphere.GetHitNormal().Y > REST_MIN_FLATNESS)
      {
        float XZSpeed = mVelocity.X * mVelocity.X + mVelocity.Z * mVelocity.Z;
        if (XZSpeed < REST_MIN_VELOCITY)
        {
          // you could be coming to rest on a player or another moving object; make sure that's not
          // the case...
          SyActorHandle handle = Filter.GetActor();
          if (handle != 0)
          {
            cGameObject *obj = mOwner->GetRegistry()->FetchByActorHandle(handle);
            if (obj == NULL || (!obj->GetPhysics()->IsMoving() && mIsMovingCountdown > 0))
            {
              mIsMovingCountdown--;
            }
          }
          else if (mIsMovingCountdown > 0)
          {
            mIsMovingCountdown--;
          }
        }
      }
    }
  }

  //  DEBUGOVERLAY_DRAWSPHERE(0, "Physics", Start+SyVect3(0.0f, 2.0f, 0.0f), 0.5f, cDebugOverlay::GREEN);
  //  DEBUGOVERLAY_DRAWSPHERE(0, "Physics", End+SyVect3(0.0f, 2.0f, 0.0f), 0.5f, bCollided?cDebugOverlay::RED:cDebugOverlay::YELLOW);
  //  DEBUGOVERLAY_DRAWLINE(0, "Physics", Start+SyVect3(0.0f, 2.0f, 0.0f), Start+mVelocity+SyVect3(0.0f, 2.0f, 0.0f), cDebugOverlay::CYAN);


  if(CalcFloor( End, *scene, Floor, FloorNormal, FloorSurfaceType,PROP_RADIUS,PROP_HEIGHT ))
  {
    /* Is the object touching the floor - move the object up to the floor level?  */
    if ((End.Y - Floor.Y) < PROP_HEIGHT && mVelocity.Y <= 0.0f)
    {
      End.Y = Floor.Y - 2.0f;
      mVelocity.Y = 0.0f;
    }

    StopFallingDamageCheck();
  }
  else
  {
    StartFallingDamageCheck();
  }

  //SyVect3 lastLoc = mOwner->GetLocation();
  End.Y -= PROP_HEIGHT;
  mOwner->SetLocation(End);

  if (mbIsUnderHavokControl)
  {
    SyScene *scene = mOwner->GetTitan()->GetScene();
    SyHavok *havok = scene->GetCollideDev()->GetHavok();
    originalvelocity.ClampMagnitude(5.0f);
    havok->LaunchRigidBody(mOwner->GetRigidBody(), End, 0.2f, originalvelocity, mOwner->GetHPR() );
  }
  
  return;
}

void cPhysicsProp::Nudge()
{
  if (mOwner->GetRigidBody())
  {
    mbIsUnderHavokControl = true;
    mOwner->GetTitan()->GetScene()->GetCollideDev()->GetHavok()->Nudge( mOwner->GetRigidBody() );
  }
}

#else

void cPhysicsProp::Nudge()
{
}

void                 
cPhysicsProp::CollisionTest(float time,SyVect3 &displacement)
{
  if (displacement.Magnitude() < 0.00001f)
  {
    return;
  }

  SyVect3        Start, End;
  SyCollSphere   CollSphere;
  SyVect3        Floor, FloorNormal;
  SyCollSurfType FloorSurfaceType;
  int32          Skitter, MaxSkitters = 2;
  SyVect3        AntiSkitter(0.0f, 0.0f, 0.0f), restitution;
  float          dot, mag;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  //cStatsProp *stats = (cStatsProjectile *) mOwner->GetStats();
  // TODO: get push collision data from object
  float PROP_HEIGHT = 0.2f;
  float PROP_RADIUS = 0.2f;

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  Start.Y += PROP_HEIGHT;
  End      = Start + displacement;

  
  CollSphere.Init( Start, End,PROP_RADIUS);
  /* Collide with the world */

  bool bCollided = false;

  if (scene->Collide( CollSphere, Filter ))
  {
    bCollided = true;

    if (ID_NONE != mThrowerID)
    {
      SyActorHandle hitHandle = Filter.GetActor();

      if (hitHandle != 0)
      {
        cGameObject *pHit = mOwner->GetRegistry()->FetchByActorHandle(hitHandle);

        if (pHit && 
            (pHit == mOwner || pHit->GetID() == mThrowerID))
        {
          bCollided = false;
        }
        else if (pHit)
        {
          // damage target
          cDamagePacket targetDamage;
          targetDamage.mAgentID = mOwner->GetID();
          targetDamage.mbBlocked = false;
          targetDamage.mbRanged = true;
          targetDamage.mAttackerID = mThrowerID;
          targetDamage.mDefenderID = pHit->GetID();
          targetDamage.mPacketType = cRulePacket::CALC_DAMAGE_RAW;
          targetDamage.SetTotal(mDamageAmount, "Thrown Prop Attack", "Physics");
          pHit->GetStats()->CalcRangedDefend(&targetDamage);
        }
      }
    }
  }

  if (bCollided)
  {
    if (ID_NONE == mThrowerID)
    {
      Skitter = 0;
      while(Skitter < MaxSkitters)
      {
        // when we hit something, slow down our velocity
        static const float BOUNCE_FACTOR = 1.0f; // 1.0 = inelastic, 2.0 = perfectly elastic
        dot = mVelocity ^ CollSphere.GetHitNormal(); 
        restitution.Mul(CollSphere.GetHitNormal(),(BOUNCE_FACTOR * dot));
        mVelocity -= restitution; 
        CollSphere.Skitter( AntiSkitter );
        if(!scene->Collide( CollSphere, Filter ))
        {
          break;
        }
        Skitter++;
      }
    }

    if (ID_NONE != mThrowerID)
    {
      mThrowerID = ID_NONE; 

      int maxHealth = static_cast<cStatsProp*>(mOwner->GetStats())->GetMaster()->mMaxHealth;

      if (maxHealth > 0 && mOwner->IsLocal())
      {
        // apply damage to prop because we hit something
        cDamagePacket selfDamagePacket;
        selfDamagePacket.SetTotal(mDamageAmount, "Thrown Prop Collision", "Physics");
        selfDamagePacket.mAgentID = mOwner->GetID();
        selfDamagePacket.mAttackerID = mOwner->GetID();
        selfDamagePacket.mDefenderID = mOwner->GetID();
        mOwner->GetStats()->ApplyDamage(&selfDamagePacket);
      }
      else
      {
        mOwner->SetHPR(SyVect3(0.0f, 0.0f, 0.0f));
      }
    }
    
    mag = mVelocity.NormalizeMagn();
    mVelocity *= SY_MAX(mag - (l_CHARACTER_FRICTION*time), 0.0f);

    End = CollSphere.GetHitPoint();

    if (CollSphere.GetHitNormal().Y > REST_MIN_FLATNESS)
    {
      float XZSpeed = mVelocity.X * mVelocity.X + mVelocity.Z * mVelocity.Z;
      if (XZSpeed < REST_MIN_VELOCITY)
      {
        // you could be coming to rest on a player or another moving object; make sure that's not
        // the case...
        SyActorHandle handle = Filter.GetActor();
        if (handle != 0)
        {
          cGameObject *obj = mOwner->GetRegistry()->FetchByActorHandle(handle);
          if (obj == NULL || (!obj->GetPhysics()->IsMoving() && mIsMovingCountdown > 0))
          {
            mIsMovingCountdown--;
          }
        }
        else if (mIsMovingCountdown > 0)
        {
          mIsMovingCountdown--;
        }
      }
    }
  }

//  DEBUGOVERLAY_DRAWSPHERE(0, "Physics", Start+SyVect3(0.0f, 2.0f, 0.0f), 0.5f, cDebugOverlay::GREEN);
//  DEBUGOVERLAY_DRAWSPHERE(0, "Physics", End+SyVect3(0.0f, 2.0f, 0.0f), 0.5f, bCollided?cDebugOverlay::RED:cDebugOverlay::YELLOW);
//  DEBUGOVERLAY_DRAWLINE(0, "Physics", Start+SyVect3(0.0f, 2.0f, 0.0f), Start+mVelocity+SyVect3(0.0f, 2.0f, 0.0f), cDebugOverlay::CYAN);


  if(CalcFloor( End, *scene, Floor, FloorNormal, FloorSurfaceType,PROP_RADIUS,PROP_HEIGHT ))
  {
    /* Is the object touching the floor - move the object up to the floor level?  */
    if ((End.Y - Floor.Y) < PROP_HEIGHT && mVelocity.Y <= 0.0f)
    {
      End.Y = Floor.Y + PROP_HEIGHT;    
      mVelocity.Y = 0.0f;  
    }       
  }

  End.Y -= PROP_HEIGHT;
  mOwner->SetLocation(End);

  return;
}
#endif

void cPhysicsProp::ApplyThrowDamage(cGameObject* pHit)
{
  if (pHit && pHit->GetID() != mLastHitID && pHit->GetID() != mThrowerID)
  {
    // damage target
    cDamagePacket targetDamage;
    targetDamage.mbBlocked = false;
    targetDamage.mbRanged = true;
    targetDamage.mAttackerID = mThrowerID;
    targetDamage.mAgentID = mOwner->GetID();
    targetDamage.mDefenderID = pHit->GetID();
    targetDamage.mPacketType = cRulePacket::CALC_DAMAGE_RAW;
    targetDamage.SetTotal(mDamageAmount, "Thrown Prop Attack", "Physics");
    pHit->GetStats()->CalcRangedDefend(&targetDamage);

    tGameID spellID = static_cast<cStatsProp*>(mOwner->GetStats())->GetMaster()->mThrowSpellID;
    if (ID_NONE != spellID)
    {
      const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(spellID);
      GAME_ASSERT(ERROR_DESIGN, pSpell!=NULL, "Could not find ThrowSpell for prop master '%s'", static_cast<cStatsProp*>(mOwner->GetStats())->GetMaster()->mID.GetName());

      if (pSpell)
      {
        pSpell->CastSpell(mOwner, pHit);
      }
    }
    else
    {
      const cImpactFXSet* pImpactFXSet = mOwner->GetTitan()->GetDatabaseSys()->GetImpactFXSet(SyHashResourceID("Default"));
      if (pImpactFXSet)
      {
        pImpactFXSet->PlayImpact(pHit, true);
      }
    }

    mLastHitID = pHit->GetID();
  }
}

void cPhysicsProp::Impulse(const SyVect3& force, float rotSpeed)
{
#ifdef HAVOK_ENABLED
  if (mOwner->GetRigidBody())
  {
    SyVect3 point(mOwner->GetLocation()), com(0.0f, 0.0f, 0.0f);
    mbIsUnderHavokControl = true;

    SyHavok* pHavok = mOwner->GetTitan()->GetScene()->GetCollideDev()->GetHavok();
    pHavok->GetCenterOfMass(mOwner->GetRigidBody(), com);

    SyMatrix44 transform;
    pHavok->GetMatrix(mOwner->GetRigidBody(), transform);

    transform.Mul(com, point);

    pHavok->LaunchRigidBody(mOwner->GetRigidBody(), 
                            point, 
                            rotSpeed, 
                            force, 
                            mOwner->GetHPR(), 
                            1);
  }
#endif
}

void cPhysicsProp::Knockback(const SyVect3 &source_loc,
                             float XZamount,
                             float Yamount,
                             bool bKnockdown,
                             float getUpTime)
{
#ifdef HAVOK_ENABLED
  if (mOwner->GetRigidBody())
  {
    if (mOwner->IsRemote())
    {
      cNetKnockbackPacket packet;
      packet.mLocation = source_loc;
      packet.mXZAmount = XZamount;
      packet.mYAmount = Yamount;
      packet.mbKnockdown = bKnockdown;
      packet.mKnockdownTime = getUpTime;
      char buf[1024];
      int len = packet.PackBuffer(buf, sizeof(buf));

      mOwner->GetTitan()->GetPeeringNetwork()->ObjectMessage(mOwner->GetID(), buf, len);

      return;
    }

    SyVect3 rbLoc = mOwner->GetLocation();
    mOwner->GetTitan()->GetScene()->GetCollideDev()->GetHavok()->GetPosition(mOwner->GetRigidBody(), rbLoc);

    SyVect3 impulse(source_loc-rbLoc);
    impulse.Y = 0.0f;
    impulse.Normalize();
    impulse *= XZamount*l_PROP_KNOCKBACK_REDUCE;
    impulse.Y = Yamount*l_PROP_KNOCKBACK_REDUCE;
    Impulse(impulse, 0.1f);
  }
#endif
}

bool cPhysicsProp::SetSafeLocation(const SyVect3 &loc)
{
  mIsMovingCountdown = 4;

#ifdef HAVOK_ENABLED
  SyHavok* pHavok = mOwner->GetTitan()->GetScene()->GetCollideDev()->GetHavok();
  if (pHavok && pHavok->PlantRigidBody((void*)mOwner->GetRigidBody(), loc) != 0)
  {
    SyVect3 safeLoc;
    pHavok->GetPosition(mOwner->GetRigidBody(), safeLoc);
    mOwner->SetLocation(safeLoc);
    return true;
  }
#endif

  return cPhysics::SetSafeLocation(loc);
}

//------------------------------------ cPhysicsAnimated


cPhysicsAnimated::cPhysicsAnimated() :
  cPhysicsDynamic(),
  mCollisionHeight(1.0f),
  mCollisionRadius(0.8f),
  mHeight(1.8f),               // The height of the character in meters
  mFloorCollRadius(0.25f),     // Radius of collision sphere used during floor/ceiling checks
  mMinStepSize(0.1f),              // Minimum distance to step up or down
  mMaxStepSize(l_MAX_STEP_SIZE),              // Maximum distance to step up or down
  mPushing(false),
  mPushingTarget(NULL),
  mThrowerID(ID_NONE),
  mFloorPropID(ID_NONE),
  mLastFloorPropLocation(0.0f, 0.0f, 0.0f),
  mImpactDir(0.0f, 0.0f, 0.0f),
  mImpactMag(0.0f)
#ifdef HAVOK_ENABLED
  ,mpPhantom(NULL)
#endif
{
  InitPropObject( mCLASSID );
}

cPhysicsAnimated::~cPhysicsAnimated()
{
#ifdef HAVOK_ENABLED
  if (mpPhantom)
  {
    SyHavok::RemovePhantom(mpPhantom);
    mpPhantom = NULL;
  }
#endif
}

int           
cPhysicsAnimated::InitPropClass()
{            
/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysicsDynamic::mCLASSID,
               mCLASSID,
               "cPhysicsAnimated", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cPhysicsAnimated::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsAnimated();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void 
cPhysicsAnimated::Reset()
{
  cPhysicsDynamic::Reset();

  mImpactMag = 0.0f;
  mImpactDir(0.0f, 0.0f, 0.0f);
  mThrowerID = ID_NONE;
  mFloorPropID = ID_NONE;

  SetLocomotion( cPhysicsAnimated::LOCO_ANIMATION );
}


#ifdef HAVOK_ENABLED
void HavokCharacterCollisionCallback(void* id1, void* id2)
{
  cGameObject* pObj1 = Titan::Get()->GetRegistry()->Fetch((tGameObjectID)id1);
  cGameObject* pObj2 = Titan::Get()->GetRegistry()->Fetch((tGameObjectID)id2);

  if (pObj1 && pObj2)
  {
    GAME_ASSERT(ERROR_CODE, pObj1->GetType() == cGameObject::OBJ_NPC || pObj1->GetType() == cGameObject::OBJ_PLAYER, "Bad object in havok character phantom collision callback");
    if (pObj2->GetType() == cGameObject::OBJ_PROP)
    {
      SyVect3 vel(pObj2->GetPhysics()->GetVelocity());
      float speed = vel.NormalizeMagn();

      if (speed >= static_cast<cStatsProp*>(pObj2->GetStats())->GetMaster()->mSpeedThreshold)
      {
        static const float MIN_HIT_ANGLE = -SY_COS(SY_DEG_TO_RAD(30.0f));
        SyVect3 toHit(pObj1->GetLocation()-pObj2->GetLocation());
        toHit.Normalize();

        if ((vel^toHit) >= MIN_HIT_ANGLE)
        {
          static_cast<cPhysicsProp*>(pObj2->GetPhysics())->ApplyThrowDamage(pObj1);
        }
      }
    }
  }
}
#endif

void
cPhysicsAnimated::Init()
{
  cPhysicsDynamic::Init();
  meLocomotion = LOCO_ANIMATION;

  // store separate radius in object in case it changes from anim or AI?
  mCollisionRadius = ((cStatsCharacter *) mOwner->GetStats())->GetMaster()->mCollisionRadius;

#ifdef HAVOK_ENABLED
  if (!mpPhantom)
  {
    SyHavok::AddCharacterPhantom(&mpPhantom, mOwner->GetLocation(), mCollisionRadius, mCollisionHeight);
    SyHavok::SetUserData(mpPhantom, (void*)mOwner->GetID());
    SyHavok::SetOwnerCollisionCallback(mpPhantom, HavokCharacterCollisionCallback);
  }
#endif
}

void 
cPhysicsAnimated::Nudge()
{
  // if a character is standing still on a box that got destroyed, increase our velocity
  // by gravity to make sure we fall down
  if (mVelocity.MagnitudeSquared() < 0.00001f)
  {
    mVelocity.Y += GRAVITY;
  }
}

/* called to step the simulation */
void 
cPhysicsAnimated::Update(float time)
{
  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);

  bool bNeedCollisionTest = true;
  SyVect3 displacement(0,0,0);

//  DEBUGOVERLAY_DRAWLINE(0, "Physics", mOwner->GetLocation()+SyVect3(0.0f, 2.0f, 0.0f), mOwner->GetLocation()+SyVect3(0.0f, 2.0f, 0.0f)+mVelocity, cDebugOverlay::CYAN);
  switch (meLocomotion)
  {
    case LOCO_ANIMATION:
      displacement = graphic->GetDisplacement();
      if (displacement.MagnitudeSquared() < 0.00001f &&
          mVelocity.MagnitudeSquared() < 0.00001f &&
          mImpactMag < 0.00001f)
      {
        bNeedCollisionTest = false;
      }
      else
      {
        displacement.Y += mVelocity.Y * time + 0.5f * time * time * GRAVITY;
        mVelocity.Y += GRAVITY * time;

        if (mImpactMag > 0.0f)
        {
          SyVect3 impact;
          impact.Mul(mImpactDir,mImpactMag*time);
          displacement += impact;
        }
      }

      if (ID_NONE != mFloorPropID)
      {
        bNeedCollisionTest = true;
        cGameObject* pFloorProp = mOwner->GetRegistry()->Fetch(mFloorPropID);

        if (pFloorProp)
        {
          displacement += pFloorProp->GetLocation()-mLastFloorPropLocation;
        }
        else
        {
          mFloorPropID = ID_NONE;
        }
      }
      break;

    case LOCO_JUMP:
      {
        SyVect3 velocity = mVelocity;
        velocity.X += mJumpStartVel.X;
        velocity.Z += mJumpStartVel.Z;
        velocity += mJumpPushVel;
        displacement.MulAdd(velocity,time);

        if (graphic->GetAnimController()->IsKnockedInAir())
        {
          mVelocity.Y += l_KNOCKDOWN_GRAVITY * time;
        }
        else if (graphic->GetAnimController()->GetAnimState() == AS_ATTACK_JUMP)
        {
          mVelocity.Y += l_JUMPATTACK_GRAVITY * time;
        }
        else
        {
          mVelocity.Y += l_JUMP_GRAVITY * time;
        }

        if (mImpactMag > 0.0f)
        {
          SyVect3 impact;
          impact.Mul(mImpactDir,mImpactMag*time);
          displacement += impact;
        }
      }
      break;

#ifdef HAVOK_ENABLED
    case LOCO_RAGDOLL:
      {       
        bNeedCollisionTest = false;  // havok completely handles collision
        SyScene *scene = mOwner->GetTitan()->GetScene();
        SyActorHandle actorHandle = graphic->GetActorHandle();
        SyCSprite* cSprite = (SyCSprite*) scene->GetActorSpritePtr (actorHandle);
        SyVect3 vLoc;
        SyHavok::GetLocation( cSprite->GetHavokRagdollInstance(), vLoc );

        if (mOwner->IsRemote())
        {
          mRenderLocation = vLoc;
          mRenderOffset.Sub(mRenderLocation, mOwner->GetLocation());
        }
        else
        {
          mOwner->SetLocation( vLoc );
        }
      }

      break;
#endif

    case LOCO_CUTSCENE:
      {
        SyScene *scene = mOwner->GetTitan()->GetScene();
        SyActorHandle actorHandle = graphic->GetActorHandle();
        SyCSprite* cSprite = (SyCSprite*) scene->GetActorSpritePtr (actorHandle);
        SyMatrix44 transform;
        SyVect3 Rotation,Scale, Translation;
        cSprite->CalcMotionAbsTransform(*scene,transform);
        transform.ConvertTo(Rotation, Scale, Translation);
        mOwner->SetLocation (Translation);
        mOwner->SetHPR (Rotation);
        return;
      }

    default:
      break;
  }

  DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", mOwner->GetLocation()+mRenderOffset, 1.0f, cDebugOverlay::WHITE);
  DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", mRenderLocation, 1.0f, cDebugOverlay::YELLOW);
  DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", mOwner->GetLocation(), 0.5f, cDebugOverlay::CYAN);

  if (bNeedCollisionTest)
  {
    CollisionTest(time, displacement);

#ifdef HAVOK_ENABLED
    if (mpPhantom)
    {
      SyHavok::SetPosition(mpPhantom, mOwner->GetLocation());
    }
#endif
  }

  if (LOCO_RAGDOLL != meLocomotion)
  {
    UpdateRemoteRenderLocation(time);
  }
}

bool
cPhysicsAnimated::PushTarget(float time, SyVect3 &destination, SyCollSphere &CollSphere)
{
  SyVect3 contact = CollSphere.GetHitPoint();
  SyVect3 remainingDisplacement = destination - contact;

  float distance = remainingDisplacement.Magnitude();
  SyVect3 pushVect;
  SyVect3 targetLoc = mPushingTarget->GetLocation();
  SyVect3 hitNormal = targetLoc - contact;
  hitNormal.Y = 0.0f; // don't push characters over us
  hitNormal.Normalize();
  pushVect.Mul(hitNormal, distance);

  // todo: scale push vect for mass differential
  // todo: Check to see if we're pushing out of the path of this object (may be able to push less)

  return prop_cast<cPhysicsDynamic*>(mPushingTarget->GetPhysics())->Push(time,pushVect);
}

void 
cPhysicsAnimated::_CollisionTestWorld(float time, SyVect3 &Start, SyVect3 &End)
{
  SyCollSphere   CollSphere, reverseSphere;
  SySceneFilter  Filter;
  int32          Skitter, MaxSkitters = 4;
  SyVect3        AntiSkitter(0.0f, 0.0f, 0.0f), up(0.0f, 1.0f, 0.0f);
  SyVect3        restitution, unskitteredHitNormal, AIHitNormal, adjustStart;
  float          dot;

  cGraphicCharacter* graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);
  SyActorHandle      handle = graphic->GetActorHandle();
  SyScene*           scene = mOwner->GetTitan()->GetScene();
  
  SyActorHandle pushingHandle;
  cGameObject* pHitObj;
  bool bKnockedInAir = graphic->GetAnimController()->IsKnockedInAir();

  Filter.Init( handle);
  CollSphere.Init( Start, End, mCollisionRadius );

  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ) != 0)
  {
    /* Skitter off of surfaces */
    Skitter = 0;
    AIHitNormal = CollSphere.GetHitNormal();

    while(Skitter < MaxSkitters)
    {
      DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", CollSphere.GetHitPoint(), 0.2f, cDebugOverlay::WHITE);

      pushingHandle = Filter.GetActor();
      if (pushingHandle != SyActorNull)
      {
        pHitObj = mOwner->GetRegistry()->FetchByActorHandle(pushingHandle);
      }
      else
      {
        pHitObj = NULL;
      }

      unskitteredHitNormal = CollSphere.GetHitNormal();
      CollSphere.Skitter( AntiSkitter );

//      DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", CollSphere.GetStart(), CollSphere.GetRadius(), cDebugOverlay::GREEN);
//      DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "Physics", CollSphere.GetEnd(), CollSphere.GetRadius(), cDebugOverlay::RED);
//      DEBUGOVERLAY_DRAWLINE(mOwner->GetID(), "Physics", CollSphere.GetHitPoint(), CollSphere.GetHitPoint()+CollSphere.GetHitNormal(), cDebugOverlay::WHITE);

      if (LOCO_JUMP == meLocomotion)
      {
        if (CollSphere.GetEnd().Y > End.Y)
        {
          End(CollSphere.GetEnd().X, End.Y, CollSphere.GetEnd().Z);
          CollSphere.Init(Start, End, mCollisionRadius);
        }
      }
/*
      if (CollSphere.GetEnd().Y < Start.Y || CollSphere.GetEnd().Y < End.Y) 
      {
        if (LOCO_JUMP == meLocomotion)
        {
          CollSphere.Init(Start, End, mCollisionRadius);
        }
        else
        {
          CollSphere.Init(Start, SyVect3(CollSphere.GetEnd().X, End.Y, CollSphere.GetEnd().Z), mCollisionRadius);
        }
      }
*/
      if (LOCO_JUMP == meLocomotion && 
          (!pHitObj || (pHitObj->GetType() != cGameObject::OBJ_NPC && pHitObj->GetType() != cGameObject::OBJ_PLAYER)))
      {
        if (bKnockedInAir)
        {
          dot = mVelocity ^ (-up);
          mVelocity = -up;
          mVelocity *= SY_MAX(dot, 0.01f);

          if (mImpactMag > 0.0f)
          {
            mImpactDir *= mImpactMag;
            dot = mImpactDir ^ (-up);
            mImpactDir = -up;
            mImpactDir *= dot;
            mImpactMag = mImpactDir.NormalizeMagn();
          }

          meLocomotion = LOCO_ANIMATION;
          graphic->GetAnimController()->GetInput()->mFall = false;

          // apply thrown damage
          if (mThrowerID != ID_NONE)
          {
            if (mOwner->IsLocal())
            {
              cDamagePacket damage;
              damage.SetTotal(l_THROWN_CHARACTER_DAMAGE, "Thrown Character Attack", "Physics");
              damage.mAttackerID = mThrowerID;
              damage.mAgentID = mThrowerID;
              damage.mDefenderID = mOwner->GetID();
              mOwner->GetStats()->ApplyDamage(&damage);
            }

            mThrowerID = ID_NONE;
            mCollisionRadius -= THROWN_CHARACTER_COLLISION_RADIUS_INCREASE;
          }
        }

        adjustStart = Start;
        adjustStart.MulAdd(unskitteredHitNormal, mCollisionRadius+0.001f);
        End = CollSphere.GetEnd();
        CollSphere.Init(adjustStart, End, mCollisionRadius);
      }

      if(!scene->Collide( CollSphere, Filter ))
      {
        reverseSphere.Init(CollSphere.GetEnd(), CollSphere.GetStart(), mCollisionRadius);
        if (scene->Collide(reverseSphere, Filter))
        {
          CollSphere = reverseSphere;
        }

        break;
      }

      Skitter++;
    }

    if (LOCO_JUMP == meLocomotion && Skitter >= MaxSkitters)
    {
      reverseSphere.Init(CollSphere.GetHitPoint(), CollSphere.GetStart(), mCollisionRadius);
      if (scene->Collide(reverseSphere, Filter))
      {
        CollSphere = reverseSphere;
      }
    }

    // if we hit a wall
    if (AIHitNormal.Y < 0.5f && 
        AIHitNormal.MagnitudeSquared() > 0.0001f &&
        (!pHitObj || pHitObj->GetType() == cGameObject::OBJ_PROP))
    {
      mOwner->GetIntel()->OnWallCollision(AIHitNormal);
    }

    if (pHitObj && meLocomotion == LOCO_ANIMATION)
    {
      mPushingTarget = pHitObj;

      // tell target we've collided (once)
      if (static_cast<cStatsCharacter*>(mOwner->GetStats())->CanLift(mPushingTarget))
      {
        cScript* script = mPushingTarget->GetScript();
        if (script != NULL)
        {
          script->Run( mOwner, SE_COLLIDE );
        }

        SyAssertf(mPushingTarget != mOwner,"Pushing self???"); 

        if (mPushingTarget->GetType() == cGameObject::OBJ_NPC ||
            (mPushingTarget->GetType() == cGameObject::OBJ_PROP && mPushingTarget->GetID() != mFloorPropID))
        {
          static const int MAX_RECURSION_DEPTH = 4;
          mRecursionDepth++;
          if (PushTarget(time,End,CollSphere) && mRecursionDepth < MAX_RECURSION_DEPTH)
          {
            _CollisionTestWorld(time,Start,End);
            mPushing = true;
            return;
          }
        }
      }
    }
    else if (pHitObj && meLocomotion == LOCO_JUMP)
    {
      // Characters flying through the air will knock down other characters
      if (bKnockedInAir &&
          pHitObj->GetID() != mThrowerID &&
          pHitObj->GetType() != cGameObject::OBJ_PLAYER)
      {
        SyVect3 xzVel(mJumpStartVel.X, 0.0f, mJumpStartVel.Z);
        float xzAmt = xzVel.Magnitude() * l_THROWN_CHARACTER_KNOCKBACK_REDUCE;
        float yAmt = mJumpStartVel.Y * l_THROWN_CHARACTER_KNOCKBACK_REDUCE;

        if (pHitObj->IsLocal() && (xzAmt > 1.0f || yAmt > 1.0f))
        {
          cDamagePacket damage;
          damage.SetTotal(l_THROWN_CHARACTER_DAMAGE, "Thrown Character Attack", "Physics");
          damage.mAttackerID = mOwner->GetID();
          damage.mAgentID = mOwner->GetID();
          damage.mDefenderID = pHitObj->GetID();
          pHitObj->GetStats()->ApplyDamage(&damage);
          pHitObj->GetPhysics()->Knockback(Start, xzAmt, yAmt, true, 0.0f);

          const cImpactFXSet* pImpactFXSet = mOwner->GetTitan()->GetDatabaseSys()->GetImpactFXSet(SyHashResourceID("Default"));
          if (pImpactFXSet)
          {
            pImpactFXSet->PlayImpact(pHitObj, true);
          }
        }
      }
      else if (pHitObj->GetType() == cGameObject::OBJ_NPC ||
               pHitObj->GetType() == cGameObject::OBJ_PLAYER)
      {
        // ignore character collisions while jumping, or if we've been thrown,
        // we hit the thrower and ignore the collision
        return; 
      }
    }
  }
  else
  {
    mPushing=false;
  }

  /* Retrieve the result of the collision/skitters */

  End = CollSphere.GetHitPoint();
}

void 
cPhysicsAnimated::CollisionTest(float time, SyVect3 &displacement)
{
  SyVect3        Floor, FloorNormal;
  SyCollSurfType FloorSurfaceType;
  SyVect3        Start, End;

  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);

  cAnimCharControllerInput *input = graphic->GetAnimInput();

/* Perform a main-sphere collision test */
  SyScene *scene = mOwner->GetTitan()->GetScene();


  SyVect3 location = mOwner->GetLocation();
  Start = location;

  Start.Y +=  mCollisionHeight;
  End       = Start + displacement;

  mRecursionDepth = 0;
  _CollisionTestWorld(time,Start,End);

  /* Perform a collision against the floor - where is it, feet touching it? */
  if (mImpactMag > 0.0f)
  {
    mImpactMag -= l_CHARACTER_FRICTION * time;
  }

  input->mHeight = 20.0f;
  tGameObjectID floorPropID = ID_NONE;
  bool bFoundFloor = CalcFloor( End, *scene, Floor, FloorNormal, FloorSurfaceType, floorPropID) != 0;

  /* Is the player touching the floor?  */

  if (bFoundFloor &&
      (End.Y - Floor.Y) <= mCollisionHeight &&
      mVelocity.Y <= 0.0f)
  {
    // apply thrown damage
    if (mThrowerID != ID_NONE)
    {
      if (mOwner->IsLocal())
      {
        cDamagePacket damage;
        damage.mAttackerID = mThrowerID;
        damage.mAgentID = mThrowerID;
        damage.mDefenderID = mOwner->GetID();
        damage.SetTotal(l_THROWN_CHARACTER_DAMAGE, "Thrown Character Attack", "Physics");
        mOwner->GetStats()->ApplyDamage(&damage);
      }

      mThrowerID = ID_NONE;
    }

    if (LOCO_JUMP == meLocomotion)
    {
      mImpactMag = 0.0f;
    }

    /* Snap the player to floor level */
    meLocomotion = LOCO_ANIMATION;
    End.Y = Floor.Y + mCollisionHeight;    
    input->mHeight = 0.0f;
    input->mFall = false;
    mVelocity(0.0f, 0.0f, 0.0f); 
    StopFallingDamageCheck();
  }       
  else if ((!bFoundFloor || ((End.Y - Floor.Y) > (mCollisionHeight+mMaxStepSize))) &&
           meLocomotion != LOCO_JUMP &&
           mVelocity.Y < 0.0f)
  {
    // player falling?
    mThrowerID = ID_NONE;
    meLocomotion = LOCO_JUMP;
    input->mFall = true;
    mJumpStartVel(0.0f,0.0f,0.0f);
    mJumpPushVel(0.0f,0.0f,0.0f);
    StartFallingDamageCheck();
  }

  if (bFoundFloor)
  {
    if (graphic->GetActorHandle() != SyActorNull)
    {
      SySprite* pSprite = scene->GetActorSpritePtr(graphic->GetActorHandle());
      SyAssert(pSprite && pSprite->GetType() == SYSPRITETYPE_CSPRITE);
      if (pSprite && pSprite->GetType() == SYSPRITETYPE_CSPRITE)
      {
        static_cast<SyCSprite*>(pSprite)->SetSurfaceType(FloorSurfaceType);
      }
    }
  }

  cGameObject* pFloorProp = mOwner->GetRegistry()->Fetch(floorPropID);

  if (bFoundFloor && pFloorProp && LOCO_ANIMATION == meLocomotion)
  {
    mFloorPropID = floorPropID;
    mLastFloorPropLocation = pFloorProp->GetLocation();
  }
  else if (ID_NONE != mFloorPropID)
  {
    mFloorPropID = ID_NONE;
    mVelocity.Y -= 0.01f;
  }

  input->mYVel   = mVelocity.Y;
  /* Update the environment flags */


  /* Compute the location,  */

   
  End.Y -= mCollisionHeight;

  // make sure we're not at the end of a camera tether

  // todo: stop animation
  // todo: what to do about Y distance?
  Start.Y -= mCollisionHeight;
  mOwner->GetTitan()->GetCameraController()->HitTether(mOwner->GetID(),Start,End,1.0f);
  
  mOwner->SetLocation(End);


  /* Set the surface type */

  return;
}

void 
cPhysicsAnimated::Jump(const SyVect3 &force)
{
  meLocomotion = LOCO_JUMP;
  mJumpStartVel = force;
  mVelocity.Y = force.Y;
  mJumpPushVel(0.0f,0.0f,0.0f);
  StartFallingDamageCheck();
}

void 
cPhysicsAnimated::Ragdoll(const SyVect3 &force)
{
#ifdef HAVOK_ENABLED
  // For Paul
  meLocomotion = LOCO_RAGDOLL;

  // remember force to apply to ragdoll
  //mJumpStartVel = force;
  mVelocity.Y = mJumpStartVel.Y;
  mJumpPushVel(0.0f,0.0f,0.0f);
  mOwner->GetGraphic()->SetStartRagdollVector( mJumpStartVel );
#endif
//todo, test for falling damage in ragdoll  StartFallingDamageCheck();
}

void 
cPhysicsAnimated::Impulse(const SyVect3 &force)
{
  mJumpPushVel = force;
}

void 
cPhysicsAnimated::Impact(const SyVect3 &dir,float mag)
{
  if (!mOwner->GetStats()->QueryFlag("Knockback Immunity"))
  {
    if (mImpactMag > 0.0f)
    {
      mImpactDir.Mul(mImpactDir,mImpactMag);
      SyVect3 newvect;
      newvect.Mul(dir,mag);
      mImpactDir += newvect; 

      mImpactMag = mImpactDir.NormalizeMagn();

      static const float MAX_IMPACT_MAG = 100.0f;
      if (mImpactMag > MAX_IMPACT_MAG)
      {
        mImpactMag = MAX_IMPACT_MAG;
      }
    }
    else
    {
      mImpactMag = mag;
      mImpactDir = dir;
    }
  }
}

bool           
cPhysicsAnimated::SetSafeLocation(const SyVect3 &loc)// finds a safe location near this spot
{
  SyVect3 testLoc(loc);

  if (CheckLocation(testLoc))
  {
    // that was too easy.
    mOwner->SetLocation(testLoc);
    mOwner->GetGraphic()->Prerender();
    return true;
  }
  // ok, search around a couple times
  Titan *titan = mOwner->GetTitan();

  float angle_offset = (float)titan->Random(0,360); // randomize starting location to keep exploits out

  angle_offset = SY_DEG_TO_RAD(angle_offset);
  float ANGLE_INC = SY_DEG_TO_RAD(60.0f);
  float radius = 1.5f * mCollisionRadius;
  for (int ii=0;ii<3;++ii)
  {
    for (float angle = SY_DEG_TO_RAD(-180.0f);angle < SY_DEG_TO_RAD(180.0f); angle += ANGLE_INC)
    {
      float angle_adj = AngleNormalize(angle + angle_offset);
      SyVect3 attempt(loc);

      attempt.X += SY_SIN(angle_adj) * radius;
      attempt.Z += SY_COS(angle_adj) * radius;

      if (CheckLocation(attempt))
      {
        if (mOwner->IsRemote())
        {
          mRenderLocation = attempt;
          mRenderOffset(0.0f, 0.0f, 0.0f);
        }

        mOwner->SetLocation(attempt);        
        mOwner->GetGraphic()->Prerender();
        return true;
      }
    }

    radius += mCollisionRadius;
    angle_offset += ANGLE_INC/3;
  }
  return false;
}

bool 
cPhysicsAnimated::CheckLocation(SyVect3 &loc)
{
  SyCollSphere   CollSphere;
  SySceneFilter  Filter;

  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);

/* Perform a main-sphere collision test */

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();

  Filter.Init( handle);
  SyVect3 Start = loc;

  Start.Y +=  mCollisionHeight;
  SyVect3 End       = Start;
  Start.Y += 10.0f;

  CollSphere.Init( Start, End, mCollisionRadius );

  /* Collide with the world */

  const float MAX_SAFE_HEIGHT = 1.0f;

  if (scene->Collide( CollSphere, Filter ))
  {
    if (End.Distance(CollSphere.GetHitPoint()) < MAX_SAFE_HEIGHT)
    {
      loc = CollSphere.GetHitPoint();
      loc.Y -= mCollisionHeight;
      return true;
    }
    
    return false;
  }

  return true;
}


/*******************************/
/* CalcFoor                    */
/*******************************/
  
int cPhysicsAnimated::CalcFloor(SyVect3&        Point, 
                                SyScene&        Scene, 
                                SyVect3&        Floor, 
                                SyVect3&        FloorNormal,
                                SyCollSurfType& FloorSurfaceType,
                                tGameObjectID&  floorPropID)
{
  cFloorFilter        Filter;
  SyCollSphere        CollSphere;
  SyVect3             Start, End;

  /* Initialize the filter */

  cGraphicActor *graphic = (cGraphicActor*) mOwner->GetGraphic();
  SyActorHandle handle = graphic->GetActorHandle();
  Filter.Init( handle );

  /* Initialize the collision */

  Start     = Point;
  End       = Point;
  Start.Y   += 1.25f;
  End.Y     -= 7.5f;

  floorPropID = ID_NONE;

  CollSphere.Init( Start, End, mFloorCollRadius );

  /* Perform the floor collision */

  if(Scene.Collide( CollSphere, Filter ))
  {
    Floor     = CollSphere.GetHitPoint();
    Floor.Y  -= mFloorCollRadius;

    FloorNormal      = CollSphere.GetHitNormal();
    FloorSurfaceType = CollSphere.GetHitSurfaceType();

    if (Filter.GetActor() != 0)
    {
      cGameObject* pHitObj = mOwner->GetTitan()->GetRegistry()->FetchByActorHandle(Filter.GetActor());

      if (pHitObj && pHitObj->GetType() == cGameObject::OBJ_PROP)
      {
        floorPropID = pHitObj->GetID();
      }
    }


    return(1);
  }

  Floor = Point;
  FloorNormal( 0.0f, 0.0f, 0.0f );
  FloorSurfaceType = SYCOLLSURFTYPE_NONE;

  return(0);
}



void                
cPhysicsAnimated::Knockback(const SyVect3 &source_loc,
                            float XZamount, float Yamount,
                            bool bKnockdown,
                            float getUpTime)
{
  if (mOwner->IsRemote())
  {
    cNetKnockbackPacket packet;
    packet.mLocation = source_loc;
    packet.mXZAmount = XZamount;
    packet.mYAmount = Yamount;
    packet.mbKnockdown = bKnockdown;
    packet.mKnockdownTime = getUpTime;
    char buf[1024];
    int len = packet.PackBuffer(buf, sizeof(buf));

    mOwner->GetTitan()->GetPeeringNetwork()->ObjectMessage(mOwner->GetID(), buf, len);

    return;
  }
  // figure out angle to attacker
  
  float towards = mOwner->GetHeading();

  SyVect3 delta(source_loc-mOwner->GetLocation());
  if (delta.MagnitudeSquared() < 0.001f)
  {
    towards += SY_PI;
  }
  else
  {
    towards = SY_ATAN2(delta.X,delta.Z);
  }

  // figure out facing
  float difference = AngleDifference(mOwner->GetHeading(),towards);

  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());

  cAnimCharControllerInput *input = graphic->GetAnimInput();

  if (input == NULL)
  {
    return;
  }

  if (mOwner->GetStats()->QueryFlag("Knockback Immunity"))
  {
    XZamount = 0.0f;
  }

  if (mOwner->GetStats()->QueryFlag("Knockdown Immunity"))
  {
    Yamount = 0.0f;
    bKnockdown = false;
  }
 
  if (SY_FABS(XZamount) < 0.00001f && SY_FABS(Yamount) < 0.00001f)
  {
    return;
  }

  if (difference < SY_DEG_TO_RAD(90) && difference > SY_DEG_TO_RAD(-90))
  {
    input->mbKnockforward = true;
    input->mKnockbackHeading = towards;
  }
  else
  {
    input->mbKnockback = true;
    input->mKnockbackHeading = AngleNormalize(towards + SY_DEG_TO_RAD(180.0f));
  }

  SyVect3 away;
  away.X = SY_SIN(towards) * -XZamount; 
  away.Y = Yamount;
  away.Z = SY_COS(towards) * -XZamount; 

  input->mKnockbackVect += away;
  input->mbKnockdown = input->mbKnockdown || bKnockdown;
  input->mHitReactTime = getUpTime;
}

void                
cPhysicsAnimated::Throw(tGameObjectID throwerID,
                        float heading,
                        float XZamount,
                        float Yamount)
{
//  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
//  cAnimCharControllerInput *input = graphic->GetAnimInput();

//  if (input == NULL)
//  {
//    return;
//  }

//  input->mbThrown = true;
//  input->mHitReactTime = 1.2f;
//  input->mHitReactTarget = throwerID;

  SyVect3 away;
  away.X = SY_SIN(heading) * XZamount; 
  away.Y = Yamount;
  away.Z = SY_COS(heading) * XZamount; 

  mThrowerID = throwerID;
  mCollisionRadius += THROWN_CHARACTER_COLLISION_RADIUS_INCREASE;
  Jump(away);
}

void           
cPhysicsAnimated::GetRingLocation(SyVect3 &Floor,SyVect3 &FloorNormal,SyCollSurfType &FloorSurfaceType)
{
  cRingFilter        Filter;
  SyCollSphere       CollSphere;
  SyVect3            Start, End;

  /* Initialize the filter */

  cGraphicActor *graphic = (cGraphicActor*) mOwner->GetGraphic();
  SyActorHandle handle = graphic->GetActorHandle();
  Filter.Init( handle );

  SyScene &Scene = *mOwner->GetTitan()->GetScene();
  /* Initialize the collision */

  Start     = mOwner->GetLocation();
  Start.Y   += 1.0f;
  End       = Start;
  End.Y    -= mHeight * 4.0f;

  CollSphere.Init( Start, End, mFloorCollRadius );

  /* Perform the floor collision */

  if(Scene.Collide( CollSphere, Filter ))
  {
    Floor     = CollSphere.GetHitPoint();
    Floor.Y  -= mFloorCollRadius;

    FloorNormal      = CollSphere.GetHitNormal();
    FloorSurfaceType = CollSphere.GetHitSurfaceType();

    if (FloorSurfaceType == SYCOLLSURFTYPE_NONE)
    {
      FloorSurfaceType = SYCOLLSURFTYPE_ROCK;
    }
    return;
  }

  Floor = mOwner->GetLocation();
  FloorNormal( 0.0f, 0.0f, 0.0f );
  FloorSurfaceType = SYCOLLSURFTYPE_NONE;

  return;
};

/*******************************/
/* FilterRing                 */
/*******************************/

int cRingFilter :: FilterActor( SyScene& Scene, SyActorHandle ActorHandle )
{
  return(1);
}

/*******************************/
/* FilterActor                 */
/*******************************/

int cFloorFilter :: 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 && pObj->GetType() != cGameObject::OBJ_PROP)
  {
    return (1);
  }

  return(0);
}

//--------------------------------------------------------------------------------------------- cPhysicsProjectile
static const float PROJECTILE_LIFESPAN = 5.0f;

cPhysicsProjectile::cPhysicsProjectile()
: cPhysicsDynamic(),
  mSourceID(ID_NONE),
  mTimer(PROJECTILE_LIFESPAN),
  mHitGround(false),
  mHeadingTowards(0),
  mLastHitID(ID_NONE),
  mSpellID(ID_NONE),
  mEffectScriptHandle(-1),
  mPinCushionHPR(0.0f, 0.0f, 0.0f),
  mPinCushionNode(0)
{
  InitPropObject( mCLASSID );
}


int           
cPhysicsProjectile::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysics::mCLASSID,
               mCLASSID,
               "cPhysicsProjectile", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;

};


SyPropObject* 
cPhysicsProjectile::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsProjectile();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void
cPhysicsProjectile::Exit()
{
  if (-1 != mEffectScriptHandle)
  {
    mOwner->GetTitan()->GetScene()->GetFXScriptSystem()->StopPlayback(mEffectScriptHandle);
    mEffectScriptHandle = -1;
  }

  cPhysics::Exit();
}

void
cPhysicsProjectile::SetCollideable(bool collide)
{
  mCollideable = collide;

  // projectiles never let other things collide with them, the projectile always
  // tests against other things itself, so disable our scene collision
  // and let the mcollideable flag determine whether or not we run our
  // CollisionTest function
  SyActorHandle handle = mOwner->GetGraphic()->GetActorHandle();

  if (handle != SyActorNull)
  {
    mOwner->GetTitan()->GetScene()->SetActorCollideable(handle, false);
  }
}

void 
cPhysicsProjectile::Update(float time)
{
  SyVect3 displacement(0.0f,0.0f,0.0f);
  mTimer -= time;

  if (mHitGround)
  {
    return;
  }

  displacement.MulAdd(mVelocity,time);

  cStatsProjectile *stats = (cStatsProjectile *) mOwner->GetStats();

  if (stats->GetMaster()->mSpins)
  {
    static const float SPIN_SPEED = SY_DEG_TO_RAD(360) * 5; // revs/sec
    float heading = mOwner->GetHeading();
    heading += SPIN_SPEED * time;
    mOwner->SetHeading(heading);
  }

  float gravity_mul = stats->GetMaster()->mGravity;
  if (gravity_mul > 0.0f)
  {
    float accel = gravity_mul * GRAVITY * time;    
    mVelocity.Y = mVelocity.Y + accel;

    
    float speed = stats->GetMaster()->mSpeed;
    float max_pitch =-SY_ATAN(mVelocity.Y/speed); // this is the pitch if the projectile were facing the target
    if (!stats->GetMaster()->mSpins)
    {
      mOwner->SetPitch(max_pitch);
    }
    else
    {
      float delta_heading = AngleDifference(mOwner->GetHeading(),mHeadingTowards);
      float pitch = max_pitch * SY_COS(delta_heading);
      mOwner->SetPitch(pitch);
    }
  }

  if (stats->GetMaster()->mAcceleration > 0.0001f || stats->GetMaster()->mAcceleration < -0.0001f)
  {
    SyVect3 accel(mVelocity);
    accel.Normalize();
    accel *= stats->GetMaster()->mAcceleration;
    mVelocity.MulAdd(accel, time);
  }

  SyWaterSystem * pWaterSystem = mOwner->GetTitan()->GetScene()->GetWaterSystem();
  if (pWaterSystem)
  {
    pWaterSystem->DemoPlunk( mOwner->GetLocation(), 0.75f );
  }

  if (stats->GetMaster()->mPinCushion && ID_NONE != mLastHitID)
  {
    cGameObject* pPinCushion = mOwner->GetTitan()->GetRegistry()->Fetch(mLastHitID);
    bool bPinCushionFail = true;

    if (pPinCushion)
    {
      SyMatrix44 nodeTransform;
      cGraphicActor* pHitGraphic = prop_cast<cGraphicCharacter*>(pPinCushion->GetGraphic());

      if (pHitGraphic)
      {
        pHitGraphic->Prerender();
        if (pHitGraphic->GetIdentNodeTransform(mPinCushionNode, &nodeTransform))
        {
          SyVect3 nodeLoc, nodeHPR;
          float nodeScale;
          nodeTransform.ConvertTo(nodeHPR, nodeScale, nodeLoc);
          mOwner->SetLocation(nodeLoc);
          mOwner->SetHPR(nodeHPR+mPinCushionHPR);
          bPinCushionFail = false;
        }
      }
    }

    if (bPinCushionFail)
    {
      mTimer = 0.0f;
    }
  }
  else
  {
    CollisionTest(time,displacement);
  }

  UpdateRemoteRenderLocation(time);
}

bool 
cPhysicsProjectile::CheckForDelete()
{
  if (mOwner->IsRemote())
  {
    return false; // owner has to delete them.
  }
  if (mTimer <= 0.0f)
  {
    return true;
  }

  return false;
}

void 
cPhysicsProjectile::Launch(cGameObject *src, const SyVect3 &target, const SyVect3* overrideSourcePos)
{
  cStatsProjectile *stats = (cStatsProjectile *) mOwner->GetStats();
  mSourceID = src->GetID();

  SyVect3 launch, adj_target(target);
  SyVect3 rightLoc;

  if (overrideSourcePos)
  {
    launch = *overrideSourcePos;
  }
  else
  {
    cGraphicCharacter* pSrcGraphic = prop_cast<cGraphicCharacter*>(src->GetGraphic());
    if (pSrcGraphic && pSrcGraphic->GetIdentNodeLocation(pSrcGraphic->GetAnimController()->GetContactNode(), &rightLoc))
    {
      adj_target.Y += SY_MIN(2.0f, rightLoc.Y - src->GetLocation().Y);
      launch = rightLoc;
    }
    else
    {
      launch = src->GetLocation();
      launch.Y += 0.75f; // approx. height of bow
      adj_target.Y += 0.5f;
    }
  }
  if (stats->GetMaster()->mMeteor > 0.0f)
  {
    launch.Y += stats->GetMaster()->mMeteor;

    if (stats->GetMaster()->mNum > 1)
    {
      launch.Y += (mOwner->GetTitan()->RandomFloat()-0.5f);
      launch.X += (mOwner->GetTitan()->RandomFloat()-0.5f)*((float)stats->GetMaster()->mNum); 
      launch.Z += (mOwner->GetTitan()->RandomFloat()-0.5f)*((float)stats->GetMaster()->mNum); 
      adj_target.X += (mOwner->GetTitan()->RandomFloat()-0.5f)*((float)stats->GetMaster()->mNum); 
      adj_target.Z += (mOwner->GetTitan()->RandomFloat()-0.5f)*((float)stats->GetMaster()->mNum); 
    }
  }

  mOwner->SetLocation(launch);
  float heading = mOwner->GetHeadingTowards(target);
  mOwner->SetHeading(heading);
  mHeadingTowards = heading;

  SyVect3 delta; 
  delta.Sub(adj_target,launch);

  float speed = stats->GetMaster()->mSpeed;
  float gravity_mul = stats->GetMaster()->mGravity;

  if (gravity_mul > 0.0f)
  {
    float delta_height = delta.Y;
    delta.Y = 0.0f;
    float distance = delta.Magnitude();

    if (speed > 0.0f && distance > 0.0f)
    {
      float target_time = distance/speed; 
      mVelocity.Y = delta_height/target_time - target_time * gravity_mul * GRAVITY * 0.5f;
    }
    else
    {
      mVelocity.Y = gravity_mul * GRAVITY * 0.5f;
    }

    mVelocity.X = SY_SIN(heading) * speed;
    mVelocity.Z = SY_COS(heading) * speed;
  }
  else 
  {
    delta.Normalize();
    mVelocity.Mul(delta,speed);

    if (stats->GetMaster()->mSeeking > 0.0f)
    {
      mVelocity.Y = 0.0f;
    }
  }

  if (stats->GetMaster()->mAttachFXID != ID_NONE)
  {
    SyScene* pScene = mOwner->GetTitan()->GetScene();
    SyActorHandle actorHandle = mOwner->GetGraphic()->GetActorHandle();

    SyResourceType resType;
    int32 resHandle;

    if(pScene->GetDictionary()->Find(stats->GetMaster()->mAttachFXID, resType, resHandle))
    {
      if (SYRESOURCETYPE_FXSCRIPT == resType)
      {
        mEffectScriptHandle = pScene->GetFXScriptSystem()->PlayScript(resHandle, 1, &actorHandle);
      }
      else
      {
        cGraphicActor* pGraphicActor = prop_cast<cGraphicActor*>(mOwner->GetGraphic());

        if (pGraphicActor)
        {
          pGraphicActor->AddEffect(0, stats->GetMaster()->mAttachFXID);
        }
      }
    }
    else
    {
      GAME_ASSERT(ERROR_DESIGN, false, "Could not find attach fx for projectile master '%s'",stats->GetMaster()->mID.GetName());
    }
  }

  SyAssert(src->GetStats() != NULL);

  mDmgPacket.mbMagic = mSpellID != ID_NONE;
  mDmgPacket.mSpellID = mSpellID;
  src->GetStats()->CalcRangedAttack(&mDmgPacket);
  mDmgPacket.MultiplyTotal(stats->GetMaster()->mDamageMultiplier, "Damage Multiplier", "Projectile");
}

void                 
cPhysicsProjectile::CollisionTest(float time,SyVect3 &displacement)
{
  SyVect3       Start, End;
  SyCollSphere  CollSphere;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  cStatsProjectile *stats = (cStatsProjectile *) mOwner->GetStats();

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  End       = Start + displacement;

  cGameObject *hit = NULL;
  int water = mOwner->GetTitan()->GetScene()->GetWaterSystem()->Plunk( mOwner->GetLocation(), 0.75f );
  bool bPlayImpact = water != SyWaterTile::FLUID_NOTFOUND;

  if (!mCollideable)
  {
    mOwner->SetLocation(End);
    return;
  }
  
  CollSphere.Init( Start, End,stats->GetMaster()->mCollisionRadius);

  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ))
  {
    SyActorHandle hitHandle=Filter.GetActor();
    bPlayImpact = true;

    // quick check to avoid a search of the registry
    hit = NULL;
    if (hitHandle != 0)
    {
      hit = mOwner->GetRegistry()->FetchByActorHandle(hitHandle);
    }

    if (hit != NULL)
    {
      if (CanHit(hit))
      {
        mLastHitID = hit->GetID();

        // damage target
        if (mDmgPacket.GetTotal() > 0)
        {
          cDamagePacket dmg = mDmgPacket;
          dmg.mAgentID = mOwner->GetID();
          hit->GetStats()->CalcRangedDefend(&dmg);
        }
      
        if (ID_NONE != mSpellID)
        {
          const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSpellID);
          SyAssertf(pSpell != NULL, "Can no longer find spell for projectile");
          if (pSpell)
          {
            pSpell->CreateSpellEffects(hit->GetRegistry()->Fetch(mSourceID), hit);
          }
        }

        bool bDie = true;
        if (stats->GetMaster()->mPinCushion)
        {
          //
          // Calculate node to stick to on character
          //
          if (hit->GetType() == cGameObject::OBJ_NPC || hit->GetType() == cGameObject::OBJ_PLAYER)
          {
            mPinCushionHPR(0.0f, 0.0f, 0.0f);
            cGraphicActor* pHitGraphic = prop_cast<cGraphicCharacter*>(hit->GetGraphic());

            if (pHitGraphic)
            {
              int numTotalNodes = 0;
              int numExistingNodes = 0;
              int startNode = 0;
              int randNode;
              bool bHasNodes[6] = { false, false, false, false, false, false };
              SyMatrix44 nodeTransforms[6];

              if (CollSphere.GetHitPoint().Y-hit->GetLocation().Y < 0.9f)
              {
                startNode = CHAR_NODE_PROJECTILE_ATTACH_LOW1;
                numTotalNodes = 4;
              }
              else
              {
                startNode = CHAR_NODE_PROJECTILE_ATTACH_HIGH1;
                numTotalNodes = 6;
              }

              for (int iNode=0; iNode<6; ++iNode)
              {
                bHasNodes[iNode] = pHitGraphic->GetIdentNodeTransform(startNode+iNode, &(nodeTransforms[iNode]));

                if (bHasNodes[iNode])
                {
                  ++numExistingNodes;
                }
              }

              if (numExistingNodes > 0)
              {
                randNode = mOwner->GetTitan()->Random(0, numExistingNodes-1) + CHAR_NODE_LEFT_FOOT;
                numExistingNodes = 0;

                for (int iNode=0; iNode<6; ++iNode)
                {
                  if (bHasNodes[iNode])
                  {
                    if (numExistingNodes == randNode)
                    {
                      SyVect3 nodeHPR, nodeLoc;
                      float nodeScale;
                      nodeTransforms[iNode].ConvertTo(nodeHPR, nodeScale, nodeLoc);
                      mPinCushionHPR.X = AngleDifference(mOwner->GetHeading(), nodeHPR.X);
                      mPinCushionHPR.Y = AngleDifference(mOwner->GetPitch(), nodeHPR.Y);
                      mPinCushionHPR.Z = AngleDifference(mOwner->GetRoll(), nodeHPR.Z);
                      bDie = false;
                      break;
                    }

                    ++numExistingNodes;
                  }
                }
              }
            }

            if (-1 != mEffectScriptHandle)
            {
              mOwner->GetTitan()->GetScene()->GetFXScriptSystem()->StopPlayback(mEffectScriptHandle);
              mEffectScriptHandle = -1;
            }
            else if (ID_NONE != stats->GetMaster()->mAttachFXID)
            {
              graphic->RemoveEffect(0, stats->GetMaster()->mAttachFXID);
            }
          }
        }
        else if (stats->GetMaster()->mPenetrates)
        {
          bDie = false;
        }

        if (bDie)
        {
          mTimer = 0.0f; // kill yourself later
          mVelocity(0.0f, 0.0f, 0.0f);
          stats->Die(mOwner->GetID());
        }
      }
      else 
      {
        mOwner->SetLocation(End);   // ignore collision
        return;
      }
    }
    else
    {
      mHitGround = true;
      mVelocity(0.0f, 0.0f, 0.0f);

      if (ID_NONE != mSpellID)
      {
        const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSpellID);
        SyAssertf(pSpell != NULL, "Can no longer find spell for projectile");
        if (pSpell)
        {
          SyVect3 hitPos = CollSphere.GetHitPoint();
          pSpell->CreateSpellEffects(mOwner->GetRegistry()->Fetch(mSourceID), NULL, &hitPos);
        }
      }

      stats->Die(mOwner->GetID());

      if (!stats->GetMaster()->mPinCushion)
      {
        mTimer = 0.0f;
      }
    }
  }

  /* Play impact effect based on surface type or hit object */
  if (bPlayImpact)
  {
    if (stats->GetMaster()->mImpactFXSetID != ID_NONE)
    {
      const cImpactFXSet* pImpactFXSet = mOwner->GetTitan()->GetDatabaseSys()->GetImpactFXSet(stats->GetMaster()->mImpactFXSetID);

      if (pImpactFXSet)
      {
        pImpactFXSet->PlayImpact(mOwner->GetTitan(), CollSphere, Filter, water);
      }
    }

    if (stats->GetMaster()->mImpactSoundSetID != ID_NONE)
    {
      const cImpactSoundSet* pImpactSoundSet = mOwner->GetTitan()->GetDatabaseSys()->GetImpactSoundSet(stats->GetMaster()->mImpactSoundSetID);

      if (pImpactSoundSet)
      {
        pImpactSoundSet->PlayHitSound(mOwner->GetTitan(), CollSphere, Filter, water);
      }
    }
  }

  /* Retrieve the result of the collision/skitters */

  End = CollSphere.GetHitPoint();

  mOwner->SetLocation(End);
}

bool
cPhysicsProjectile::CanHit(cGameObject* pHit)
{
  bool bSuccessful = pHit &&
                     pHit != mOwner &&
                     pHit->GetID() != mSourceID &&
                     pHit->GetID() != mLastHitID;
 
  if (!bSuccessful)
  {
    return false;
  }

  if (ID_NONE != mSpellID && 
      ID_NONE != mSourceID &&
      bSuccessful)
  {
    cGameObject* pSource = mOwner->GetTitan()->GetRegistry()->Fetch(mSourceID);
    const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(mSpellID);
    SyAssertf(pSpell != NULL, "Can no longer find spell for projectile");

    if (pSpell && pSource)
    {
      switch (pSpell->mTargeting)
      {
        case cSpellMaster::SPELL_TARGETING_ANY:
          bSuccessful = true;
          break;

        case cSpellMaster::SPELL_TARGETING_ALLIES:
          bSuccessful = pSource->GetIntel()->IsFriendly(pHit);
          break;

        case cSpellMaster::SPELL_TARGETING_ENEMIES:
          bSuccessful = pSource->GetIntel()->IsTargetable(pHit) && pHit->GetType() != cGameObject::OBJ_PROP;
          break;

        case cSpellMaster::SPELL_TARGETING_ENEMIES_AND_PROPS:
          bSuccessful = pSource->GetIntel()->IsTargetable(pHit);
          break;

        default:
          SyAssertf(false, "Spell '%s' has bad targeting type", pSpell->mID.GetName());
      }
    }
  }

  if (bSuccessful && 
      (pHit->GetType() == cGameObject::OBJ_NPC || pHit->GetType() == cGameObject::OBJ_PLAYER))
  {
    cDamagePacket packet;
    packet.mPacketType = cRulePacket::CALC_DODGE_CHANCE;
    packet.mAttackerID = mSourceID;
    packet.mDefenderID = pHit->GetID();
    packet.mbRanged = true;
    pHit->GetStats()->ProcessRulePacket(&packet);
    if (packet.GetTotal() > 0 &&
        pHit->GetTitan()->Random(1, 100) <= packet.GetTotal())
    {
      bSuccessful = false;
    }
  }

  return bSuccessful;
}

int                  
cPhysicsProjectile::CalcFloor( SyVect3&        Point, 
                               SyScene&        Scene, 
                               SyVect3&        Floor, 
                               SyVect3&        FloorNormal,
                               SyCollSurfType& FloorSurfaceType )
{
  return 0;
};

void cPhysicsProjectile::Shoot(tGameID projectileMasterID,
                               cGameObject* pOwner,
                               const SyVect3& targetPos,
                               cGameObject* pTarget,
                               const cSpellMaster* pSpellMaster,
                               const SyVect3* overrideSourcePos)
{
  SyAssert(projectileMasterID!=ID_NONE && pOwner!=NULL);
  
  cGameObjectRegistry* pRegistry = pOwner->GetRegistry();
  const cStatsProjectileMaster* pStats = pOwner->GetTitan()->GetDatabaseSys()->GetProjectileMaster(projectileMasterID);

  SyAssert(pStats && pStats->mNum > 0);

  SyVect3 offsetDir, offsetHPR, dirToTarget, startLoc(pOwner->GetLocation());
  tGameObjectID  projID;
  cGameObject* pObj;

  if (overrideSourcePos)
  {
    startLoc = *overrideSourcePos;
  }

  float angle, distToTarget;
  float anglePerProjectile = 0.0f;
  float angleOffset = 0.0f;

  if (pStats->mNum > 1)
  {
    anglePerProjectile = pStats->mMaxArc / ((float)(pStats->mNum-1));
    angleOffset = pStats->mMaxArc*(-0.5f);
  }
  
  // semi-hack to make sure even projectile counts split with one projectile
  // going straight ahead of NPC (so AI can target it).
  if (((pStats->mNum%2) == 0) && anglePerProjectile > 20.0f)
  {
    angleOffset += anglePerProjectile*0.5f;
  }

  for (int i=0; i<pStats->mNum; ++i)
  {
    projID = pRegistry->Create(cGameObject::OBJ_PROJECTILE);
    pObj = pRegistry->Fetch(projID);
    static_cast<cStatsProjectile*>(pObj->GetStats())->SetMaster(projectileMasterID);

    if (pSpellMaster)
    {
      static_cast<cPhysicsProjectile*>(pObj->GetPhysics())->mSpellID = pSpellMaster->mID.GetID();

//      if (pSpellMaster->mDeliveryDuration > 0.0f)
//      {
//        static_cast<cPhysicsProjectile*>(pObj->GetPhysics())->SetLifetime(pSpellMaster->mDeliveryDuration);
//      }
    }

    pRegistry->InitObject(projID, false);

    if (pStats->mNum > 1)
    {
      angle = ((float)i)*anglePerProjectile + angleOffset;
      angle = SY_DEG_TO_RAD(angle);
      dirToTarget = targetPos - startLoc;
      distToTarget = dirToTarget.NormalizeMagn();
      dirToTarget.HPR(&offsetHPR.X, &offsetHPR.Y, &offsetHPR.Z);
      offsetHPR.X += angle;
      offsetDir.HPR(offsetHPR.X, offsetHPR.Y, offsetHPR.Z);
      offsetDir *= distToTarget;
      static_cast<cPhysicsProjectile*>(pObj->GetPhysics())->Launch(pOwner, startLoc+offsetDir, overrideSourcePos);
    }
    else
    {
      static_cast<cPhysicsProjectile*>(pObj->GetPhysics())->Launch(pOwner, targetPos, overrideSourcePos);
    }

    if (pTarget)
    {
      SyAssert(prop_cast<cIntelProjectile*>(pObj->GetIntel())!=NULL);
      static_cast<cIntelProjectile*>(pObj->GetIntel())->SetTarget(pTarget->GetID());
    }
  }
}


//----------------------------------------------------------------- cPhysicsItem

cPhysicsItem::cPhysicsItem()
: cPhysicsDynamic(),
  mGroundHeight(0.0f),
  mBob(0.0f)
{
  InitPropObject( mCLASSID );
}


int           
cPhysicsItem::InitPropClass()
{
  /* Add the class */

  AddSubClass( mCLASSID, 
    cPhysicsDynamic::mCLASSID,
    mCLASSID,
    "cPhysicsItem", 
    Creator, 
    mCLASSID, 
    0 ); 
  return 0;
};


SyPropObject* 
cPhysicsItem::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsItem();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}


void 
cPhysicsItem::Update(float time)
{
  static const float ITEM_HEIGHT = 0.4f;

  bool bWasMoving = IsMoving();
  if (bWasMoving)
  {
    cPhysicsDynamic::Update(time);
  }

  bool bIsMoving = IsMoving();
  if (bWasMoving && !bIsMoving)
  {
    SyVect3        loc(mOwner->GetLocation());
    SyVect3        floor, floorNormal;
    SyCollSurfType floorSurfaceType;

    SyScene *scene = mOwner->GetTitan()->GetScene();

    if(CalcFloor(loc, *scene, floor, floorNormal, floorSurfaceType, ITEM_HEIGHT, ITEM_HEIGHT))
    {
      mGroundHeight = floor.Y;
    }
    else
    {
      mGroundHeight = mOwner->GetLocation().Y;
    }

    mOwner->SetPitch(-SY_PI*0.5f);
  }

  if (!bIsMoving)
  {
    static const float WAVE_RADIANS_PER_SEC = SY_PI*0.5f;
    static const float WAVE_HEIGHT = 0.2f;

    mBob += WAVE_RADIANS_PER_SEC * time;

    if (mBob > (SY_PI*2.0f))
    {
      mBob -= SY_PI*2.0f;
    }

    float newBob = SY_SIN(mBob) * WAVE_HEIGHT;

    SyVect3 loc(mOwner->GetLocation());

    loc.Y = mGroundHeight + ITEM_HEIGHT + WAVE_HEIGHT + newBob;   
    mOwner->SetLocation(loc);

    SyVect3 camHPR;
    mOwner->GetTitan()->GetCameraController()->GetCamera()->GetDir().HPR(&camHPR.X, &camHPR.Y, &camHPR.Z);
    mOwner->SetHeading(camHPR.X+SY_PI);
  }
}



//////////////////////////////////////////////////////////////////////////////////////////// Global Funcs
void 
RegPropClasses_Physics()
{
  cPhysics::InitPropClass();
  cPhysicsStatic::InitPropClass();
  cPhysicsDynamic::InitPropClass(); 
  cPhysicsAnimated::InitPropClass();
  cPhysicsProjectile::InitPropClass();
  cPhysicsProp::InitPropClass();
  cPhysicsItem::InitPropClass();
}

void 
Physics_RegisterTuningVariables()
{
  gTuningSys.AddFloat(&l_CHARACTER_FRICTION,"Character_Friction");
  gTuningSys.AddFloat(&l_MAX_STEP_SIZE,"Character_MaxStepSize");
  gTuningSys.AddFloat(&l_PROP_KNOCKBACK_REDUCE,"Prop_Knockback_Reduce");
  gTuningSys.AddInt(&l_THROWN_CHARACTER_DAMAGE,"Thrown_Character_Damage");
  gTuningSys.AddFloat(&l_THROWN_CHARACTER_KNOCKBACK_REDUCE,"Thrown_Character_Knockback_Reduce");
  gTuningSys.AddFloat(&l_THROWN_PROP_MIN_SPEED,"Thrown_Prop_MinSpeed");
  gTuningSys.AddFloat(&l_THROWN_PROP_MAX_SPEED,"Thrown_Prop_MaxSpeed");
  gTuningSys.AddFloat(&l_JUMP_GRAVITY,"Jump_Gravity");
  gTuningSys.AddFloat(&l_JUMPATTACK_GRAVITY,"JumpAttack_Gravity");
  gTuningSys.AddFloat(&l_KNOCKDOWN_GRAVITY,"Knockdown_Gravity");
  gTuningSys.AddFloat(&l_FALLING_DAMAGE_THRESHOLD,"FallingDamage_Threshold");
  gTuningSys.AddFloat(&l_FALLING_DAMAGE_HITPOINTS_PER_METER,"FallingDamage_HitPointsPerMeter");
}
// EOF

