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

//-------------------------------------------------------- Includes
#include "stats.h"
#include "gameobj.h"
#include "titan.h"
#include "registry.h"
#include "rule_condition.h"
#include "graphic.h"
#include "animcontroller.h"
#include "intel.h"
#include "registry.h"
#include "inventory.h"
#include "physics.h"
#include "netpacket.h"
#include "TitanPeeringNetwork.h"
#include "script.h"
#include "ai/behaviortypes.h"
#include "script_pawn.h"
#include "database.h"
#include "droplist.h"
#include "areaeffect.h"
#include "debris.h"
#include "spell.h"
#include "tuning.h"
#include "SyDamageNumMgr.h"
#include "SyScene.h"
#include "SyEsfParse.h"
#include "SySoundDevice.h"
#include "cameracontroller.h"
#include "debugoverlay.h"
#include "t4.h"
#include "quest.h"
#include "levelobj.h"
#include "gameerror.h"
#include "soundmanager.h"
#include "SyHavok.h"

//---------------------------------------------- Class Declarations
//--------------------------------------------------------- Globals
//----------------------------------------- Functions Declarations
//----------------------------------------- Local Vars
static float l_DEBRIS_IMPACT_FORCE = 2.0f;
static float l_PROP_IMPACT_FORCE = 1.0f;
static float l_PROP_IMPACT_ROTATE_SPEED = SY_DEG_TO_RAD(10.0f);
static float l_CHARACTER_IMPACT_FORCE = 1.0f;
static float l_CHARACTER_IMPACT_JUGGLE = 0.5f;
static int32 l_BonusPointsPerLevel = 2;
static int32 l_HealthPointsPerBonusPoint = 5;
static int32 l_ManaPointsPerBonusPoint = 5;
static float l_ResetTime = 5.0f;
//------------------------------------ Member Functions Definitions

//--------------------------------------------------------- Globals


//------------------------------------ cStats
cStats::cStats()
: mOwner(NULL),
  mActivateStringOverride(0)
{
  InitPropObject( mCLASSID );
}

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

  AddClass( mCLASSID, 
            "cStats", 
            Creator, 
            mCLASSID, 
            0 ); 

  AddSimpleMapProperty<tGameID,int32>(mCLASSID,
                                           0x0000,
                                           SyMemberOffset(cStats,mValues),
                                           "mValues",
                                           SYPROPTYPE_INT32,
                                           SYPROPTYPE_INT32);

  return 0;
}

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

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

  return(pObject);
}

void 
cStats::Reset()
{
  if (!mOwner->IsRemote())
  {
    cNetResetObjectPacket packet;

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

void                
cStats::GetActivateString(SyString *string)
{
  if( GetActivateType() == ACTIVATE_NONE )
  {
    return;
  }

  // string table IDs
  static const int stringIDs[] = 
  {
    SyHashResourceID("ACTION_UNKNOWN"), // <nothing>
    SyHashResourceID("ACTION_OPEN"), // <nothing>
    SyHashResourceID("ACTION_TALK_TO"), // <nothing>
    SyHashResourceID("ACTION_IGNITE"), // <nothing>
    SyHashResourceID("ACTION_TURN"), // <nothing>
    SyHashResourceID("ACTION_FLIP"), // <nothing>
    SyHashResourceID("ACTION_PICKUP"), // <nothing>
    SyHashResourceID("ACTION_PUTDOWN"), // <nothing>
    SyHashResourceID("ACTION_PUSH"), // <nothing>
    SyHashResourceID("ACTION_SAVEGAME"), // <nothing>
  };

  const int BUF_LEN = 512;
  char8 buffer[BUF_LEN];

  //SyString name;
  //GetTranslatedName( &name );
  // set argument #1 in our formatted string
  T4_SetContext('t');

  int stringId = stringIDs[ GetActivateType() ];

  if (mActivateStringOverride != 0)
  {
    stringId = mActivateStringOverride;
  }

  // try to translate
  if( mOwner->GetTitan()->T4Expand( buffer, BUF_LEN, stringId ) >= 0 )
  {
    // got the translated string
    string->Init( buffer );
    return;
  }

  #if 0
  // some T4 failure; fall back to old method
  const char *format_strs[] = 
  {
    "", // none- prevent crash
    "Open %s",
    "Talk To %s",
    "Ignite %s",
    "Turn %s",
    "Flip %s",
    "Pick Up %s",
    "Put Down", 
    "Push", 
    "Save Game"
  };

  if (GetActivateType() == ACTIVATE_NONE)
  {
    return;
  }
  
  GetTranslatedName(&name);

  sprintf(buffer,format_strs[GetActivateType()],name.AsChar());
  string->Init(buffer);
  #endif
}


void
cStats::CalcRangedDefend(cDamagePacket* pDamage)
{
  SyAssert(pDamage!=NULL);

  if (!pDamage)
  {
    return;
  }

  if (mOwner->IsRemote())
  {
    return;
  }

  // blocking
  cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(mOwner->GetGraphic());
  if (pCharGraphic && pCharGraphic->GetAnimController()->IsBlocking())
  {
    pDamage->mbBlocked = true;
  }

  SyAssert(pDamage->mbRanged);
  SyAssert(pDamage->mPacketType == cRulePacket::CALC_DAMAGE_RAW);

  pDamage->mbRanged = true;
  pDamage->mCombo = COMBO_RANGEDATTACK;
  pDamage->mDefenderID = mOwner->GetID();
  pDamage->mPacketType = cRulePacket::CALC_DAMAGE_REDUCED;

  ProcessRulePacket(pDamage);

  pDamage->mPacketType = cRulePacket::EVENT_HIT;
  cGameObject* pAttacker = mOwner->GetRegistry()->Fetch(pDamage->mAttackerID);

  if (pAttacker != NULL)
  {
    pAttacker->GetStats()->ProcessRulePacket(pDamage);
  }

  ApplyDamage(pDamage);
}

void
cStats::CalcRangedAttack(cDamagePacket* pDamage)
{
  SyAssert(pDamage!=NULL);

  if (!pDamage)
  {
    return;
  }

  pDamage->mbBlocked = false;
  pDamage->mbRanged = true;
  pDamage->mCombo = COMBO_RANGEDATTACK;
  pDamage->mAttackerID = mOwner->GetID();
  pDamage->mDefenderID = ID_NONE;
  pDamage->mPacketType = cRulePacket::CALC_DAMAGE_INITIAL;

  float targetRecoveryTime = 1.0f;
  cGraphicCharacter* pMyGraphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  if (pMyGraphic)
  {
    targetRecoveryTime = pMyGraphic->GetAnimController()->GetTargetRecoveryTime(COMBO_RANGEDATTACK);
  }

  pDamage->mRecoveryTime = targetRecoveryTime;

  ProcessRulePacket(pDamage);

  pDamage->mPacketType = cRulePacket::CALC_DAMAGE_RAW;
  ProcessRulePacket(pDamage);
}

// the owner of this object is trying to tell us something.
bool                
cStats::NetworkReceiveBroadcast(const char *packet, int size) 
{
  cNetPacket::eNetPacketType type;

  SyAssertf(mOwner->IsRemote(),"Network Broadcast on local object?");

  SyPack::Unpack8u(packet, &type);      // note: packet format must match cNetPacket::Pack in netpacket.cpp

  switch (type)
  {
  case cNetPacket::NET_DAMAGE:
    {
    }
    break;
  case cNetPacket::NET_ACTIVATE_CONFIRM:
    {
      cNetActivateConfirmPacket confirm;
      confirm.UnpackBuffer(packet, size);

      cGameObject *actor = mOwner->GetTitan()->GetRegistry()->Fetch(confirm.mActor);
      _PerformActivate(actor);
      return true;
    }
    break;
  case cNetPacket::NET_RESET_OBJECT:
    {
      Reset();
      return true;
    }
    break;
  case cNetPacket::NET_ADD_CONDITION:
    {
      cNetAddConditionPacket condition;
      condition.UnpackBuffer(packet, size);
      _PerformAddCondition(condition.mConditionName, condition.mSpellID, condition.mSourceID, condition.mItemMasterID, condition.mDuration, condition.mEffectID, condition.mParam1, condition.mParam2);
      return true;
    }
    break;
  case cNetPacket::NET_REMOVE_CONDITION:
    {
      cNetRemoveConditionPacket condition;
      condition.UnpackBuffer(packet, size);
      _PerformRemoveCondition(condition.mConditionName, condition.mSpellID, condition.mSourceID, condition.mItemMasterID);
      return true;
    }
    break;
  case cNetPacket::NET_REMOVE_CONDITIONS_FROM_SOURCE: 
    {
      cNetRemoveConditionsFromSourcePacket request;
      request.UnpackBuffer(packet, size);
      _PerformRemoveConditions(request.mSpellID, request.mSourceID, request.mItemMasterID);
      return true;
    }
    break;
  case cNetPacket::NET_CAST_SPELL:
    {
      cNetCastSpellPacket spellRequest;
      spellRequest.UnpackBuffer(packet, size);

      const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(spellRequest.mSpellID);
      SyAssertf(pSpell!=NULL, "Bad network cast spell request");
      if (pSpell)
      {
        cGameObject* pTarget = mOwner->GetRegistry()->Fetch(spellRequest.mTargetID);
        pSpell->CastSpell(mOwner, pTarget, &spellRequest.mTargetLoc, (eComboType)spellRequest.mCombo);
      }
      return true;
    }
    break;

  default:
  SyAssertf(0,"Unknown packet type");
  break;
  }
  return false;
}

// we own this object, and a peer is making a request
bool                
cStats::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_DAMAGE:
      {
      }
      break;
  case cNetPacket::NET_ACTIVATE_REQUEST:
    {
    // just the fact that we exist means the item hasn't been picked up by anyone else...
      cNetActivateRequestPacket request;
      request.UnpackBuffer(packet, size);

      cNetActivateConfirmPacket netpacket;
      cGameObject *actor = mOwner->GetRegistry()->Fetch(request.mActor);
      SyAssertf(actor!=NULL,"Network packet with bad actor");
      SyAssertf(actor->IsRemote(),"Network packet with bad actor");
      netpacket.mActor = request.mActor;

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

      // this will delete the object and issue a delete broadcast
      _PerformActivate(actor);
      return true;
    }
    break;
  case cNetPacket::NET_ADD_CONDITION:
    {
      cNetAddConditionPacket request;
      request.UnpackBuffer(packet, size);
      _PerformAddCondition(request.mConditionName,request.mSpellID, request.mSourceID, request.mItemMasterID, request.mDuration, request.mEffectID, request.mParam1, request.mParam2);
      return true;
    }
    break;
  case cNetPacket::NET_REMOVE_CONDITION:
    {
      cNetRemoveConditionPacket request;
      request.UnpackBuffer(packet, size);
      _PerformRemoveCondition(request.mConditionName,request.mSpellID, request.mSourceID, request.mItemMasterID);
      return true;
    }
    break;
  case cNetPacket::NET_REMOVE_CONDITIONS_FROM_SOURCE: 
    {
      cNetRemoveConditionsFromSourcePacket request;
      request.UnpackBuffer(packet, size);
      _PerformRemoveConditions(request.mSpellID, request.mSourceID, request.mItemMasterID);
      return true;
    }
    break;
  default:
  SyAssertf(0,"Unknown packet type");
  break;
  }

  return false;
}

void                
cStats::Activate(cGameObject *activater)
{
  if (!mOwner->IsLocal())
  {
    // have to issue a request...
    cNetActivateRequestPacket netpacket;
    netpacket.mActor = activater->GetID();
    
    char buf[1024];
    int len = netpacket.PackBuffer(buf, sizeof(buf));
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectMessage(mOwner->GetID(),buf,len);

    return;
  }

  cNetActivateConfirmPacket netpacket;
  netpacket.mActor = activater->GetID();

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

  _PerformActivate(activater);
};


void                
cStats::_PerformActivate(cGameObject *actor)
{
  cScript *script = mOwner->GetScript();
  if (script != NULL)
  {
    script->Run(actor,SE_ACTIVATE);
  }
  mOwner->GetTitan()->GetScriptSys()->ScriptEvent(PET_ACTIVATE, mOwner->GetID(), actor->GetID());
}

void                        
cStats::AddCondition(const char* name,
                     tGameID spellID,
                     tGameObjectID sourceID,
                     tGameID itemMasterID,
                     float duration,
                     tGameID effectID,
                     int param1,
                     int param2)
{
  _PerformAddCondition(name, spellID, sourceID, itemMasterID, duration, effectID, param1, param2);

  cNetAddConditionPacket packet;
  strncpy(packet.mConditionName, name, SY_MIN(((int)strlen(name)+1),cNetAddConditionPacket::MAX_CONDITION_NAME_LEN));
  packet.mConditionName[cNetAddConditionPacket::MAX_CONDITION_NAME_LEN] = 0;
  packet.mSpellID = spellID;
  packet.mSourceID = sourceID;
  packet.mItemMasterID = itemMasterID;
  packet.mDuration = duration;
  packet.mEffectID = effectID;
  packet.mParam1 = param1;
  packet.mParam2 = param2;

  char buf[1024];
  int len = packet.PackBuffer(buf, sizeof(buf));

  if (mOwner->IsRemote())
  {
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectMessage(mOwner->GetID(), buf, len);
  }
  else
  {
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectBroadcast(mOwner->GetID(), buf, len);
  }
}

void
cStats::RemoveCondition(const char *name, tGameID spellID, tGameObjectID sourceID, tGameID itemMasterID)
{
  _PerformRemoveCondition(name, spellID, sourceID, itemMasterID);

  cNetRemoveConditionPacket packet;
  strncpy(packet.mConditionName, name, SY_MIN(((int)strlen(name)+1),cNetAddConditionPacket::MAX_CONDITION_NAME_LEN));
  packet.mConditionName[cNetAddConditionPacket::MAX_CONDITION_NAME_LEN] = 0;
  packet.mSpellID = spellID;
  packet.mSourceID = sourceID;
  packet.mItemMasterID = itemMasterID;

  char buf[1024];
  int len = packet.PackBuffer(buf, sizeof(buf));

  if (mOwner->IsRemote())
  {
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectMessage(mOwner->GetID(), buf, len);
  }
  else
  {
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectBroadcast(mOwner->GetID(), buf, len);
  }
}

void
cStats::RemoveConditions(tGameID spellID, tGameObjectID sourceID, tGameID itemMasterID)
{
  _PerformRemoveConditions(spellID, sourceID, itemMasterID);

  cNetRemoveConditionsFromSourcePacket packet;
  packet.mSpellID = spellID;
  packet.mSourceID = sourceID;
  packet.mItemMasterID = itemMasterID;

  char buf[1024];
  int len = packet.PackBuffer(buf, sizeof(buf));

  if (mOwner->IsRemote())
  {
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectMessage(mOwner->GetID(), buf, len);
  }
  else
  {
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectBroadcast(mOwner->GetID(), buf, len);
  }
}


void                
cStats::Value_Set(tGameID id,int32 value)
{
  int index = mValues.Find(id); 

  if (index == mValues.End())
  {
    mValues.Insert(id,value);
    return;
  };

  mValues(index) = value;
};


int32               
cStats::Value_Get(tGameID id)
{
  int index = mValues.Find(id); 

  if (index == mValues.End())
  {
    return 0;
  };

  return mValues(index);
}

//------------------------------------ cStatsCharacterMaster

cStatsCharacterMaster::cStatsCharacterMaster() :
  mModelDataID(ID_NONE),
  mNameString(0),
  mDescriptionString(0),
  mClass(NULL),
  mLevel(0),
  mLevelDiff(0),
  mAnimSet(0),
  mNaturalMelee(0),
  mNaturalRanged(0),
  mNaturalSpell(0),
  mNaturalArmor(0),     
  mTreasureSet(0),
  mMovementSpeed(1.0f),
  mCloseDistance(1.0f),
  mMeleeRange(6.0f),
  mAccuracy(1.0f),
  mCollisionRadius(0.75f),
  mActivationRadius(0.0f),
  mNPCBehaviorType(0),
  mNPCFaction(0),
  mBlockBehavior(ID_NONE),
  mAbilitySet(ID_NONE),
  mRace(ID_NONE),
  mScriptName(NULL),
  mScale(1.0f),
  mWeight(0.0f)
{
  for (int i=0; i<MAX_ATTACHMENTS; ++i)
  {
    mAttachmentModelNames[i] = "";
  }
}

cStatsCharacterMaster::~cStatsCharacterMaster()
{
  delete [] mScriptName;
}


//------------------------------------ cStatsBlockBehavior

cStatsBlockBehavior::cStatsBlockBehavior()
: mNumHitsBeforeBlock(1),
  mPlayerBlockStrategy(PBS_IGNORE),
  mNPCBlockStrategy(NPCBS_AFTER_HIT),
  mBlockHoldTime(1.0f),
  mBlockReactTime(0.0f)
{
  for (int i=0; i<BLOCK_ATTACK_MAX; ++i)
  {
    mBlockPercentChance[i] = 0;
    mDodgePercentChance[i] = 0;
    mRipostePercentChance[i] = 0;
  }
}

//------------------------------------ cStatsAbilitySet

cStatsAbilitySet::cStatsAbilitySet()
{
}

cStatsAbilitySet::~cStatsAbilitySet()
{
  mEntries.Clear();
}

cStatsAbilitySet::Entry::Entry()
: mCondition(ABILITY_CONDITION_ANY),
  mDelay(0.0f),
  mWeight(1),
  mAbilityID1(ID_NONE),
  mAbilityID2(ID_NONE),
  mAbilityID3(ID_NONE)
{
}

bool cStatsAbilitySet::GetAbility(UsageCondition condition,
                                  tGameID curAbilityID,
                                  tGameID* pNextAbilityID) const
{
  SyAssert(pNextAbilityID != NULL);

  int i;
  int totalWeight = 0;

  for (i=mEntries.Begin(); i!=mEntries.End(); i=mEntries.Next(i))
  {
    if (condition == mEntries(i).mCondition ||
        condition == ABILITY_CONDITION_ANY)
    {
      totalWeight += mEntries(i).mWeight;
      
      if (ID_NONE != mEntries(i).mAbilityID1 &&
          mEntries(i).mAbilityID1 == curAbilityID &&
          ID_NONE != mEntries(i).mAbilityID2)
      {
        *pNextAbilityID = mEntries(i).mAbilityID2;
        return true;
      }
      else if (ID_NONE != mEntries(i).mAbilityID2 &&
               mEntries(i).mAbilityID2 == curAbilityID &&
               ID_NONE != mEntries(i).mAbilityID3)
      {
        *pNextAbilityID = mEntries(i).mAbilityID3;
        return true;
      }
    }
  }

  if (totalWeight <= 0)
  {
    return false;
  }

  int curWeight = 0;
  int selectedWeight = Titan::Get()->Random(1, totalWeight);

  for (i=mEntries.Begin(); i!=mEntries.End(); i=mEntries.Next(i))
  {
    if (condition == mEntries(i).mCondition ||
        condition == ABILITY_CONDITION_ANY)
    {
      curWeight += mEntries(i).mWeight;

      if (curWeight >= selectedWeight)
      {
        *pNextAbilityID = mEntries(i).mAbilityID1;
        return true;
      }
    }
  }

  return false;
}

bool
cStatsAbilitySet::GetDelay(tGameID abilityID, float* pDelay) const
{
  SyAssert(pDelay != NULL);

  for (int i=mEntries.Begin(); i!=mEntries.End(); i=mEntries.Next(i))
  {
    if (abilityID == mEntries(i).mAbilityID1 ||
        abilityID == mEntries(i).mAbilityID2 ||
        abilityID == mEntries(i).mAbilityID3)
    {
      *pDelay = mEntries(i).mDelay;
      return true;
    }
  }

  return false;
}

bool
cStatsAbilitySet::GetNextAbility(tGameID curAbilityID, tGameID* pNextAbility) const
{
  SyAssert(pNextAbility != NULL);

  for (int i=mEntries.Begin(); i!=mEntries.End(); i=mEntries.Next(i))
  {
    if (curAbilityID == mEntries(i).mAbilityID1)
    {
      *pNextAbility = mEntries(i).mAbilityID2;
      return ID_NONE != mEntries(i).mAbilityID2;
    }
    else if (curAbilityID == mEntries(i).mAbilityID2)
    {
      *pNextAbility = mEntries(i).mAbilityID3;
      return ID_NONE != mEntries(i).mAbilityID3;
    }
  }

  return false;
}

bool cStatsAbilitySet::HasAbilityForCondition(UsageCondition condition) const
{
  for (int i=mEntries.Begin(); i!=mEntries.End(); i=mEntries.Next(i))
  {
    if (condition == mEntries(i).mCondition)
    {
      return true;
    }
  }

  return false;
}

//------------------------------------ cStatsCharacter

cStatsCharacter::cStatsCharacter() :
  mpMaster(NULL),
  mpClass(NULL),
  mpLevel(NULL),
  mCurDamage(0),
  mCurManaLoss(0),
  mCurBlockLoss(0),
  mHealthRegenFrac(0),
  mManaRegenFrac(0),
  mBlockRegenFrac(0),
  mEssence(0),
  mExperience(0),
  mAbilityPoints(0),
  mbDead(false),
  mbDisableDrop(false),
  mActivateType(ACTIVATE_NONE),
  mAbilitySetOverrideID(ID_NONE),
  mCurLevel(-1),
  mBonusPoints(0),
  mBonusHealth(0),
  mBonusMana(0), // from leveling
  mBonusAttackPower(0),
  mBonusMagicPower(0),
  mBonusMeleeDefense(0),
  mBonusSpellDefense(0),
  mDeadTime(0)
{
  InitPropObject( mCLASSID );
}


cStatsCharacter::~cStatsCharacter()
{
}


int           
cStatsCharacter::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cStats::mCLASSID,
               mCLASSID,
               "cStatsCharacter", 
               Creator, 
               mCLASSID, 
               0 ); 

  AddStringProperty(mCLASSID,PropId_MasterName,SyMemberOffset(cStatsCharacter,mMasterName),"mMasterName");
  AddInt32Property(mCLASSID,PropId_CurDamage,SyMemberOffset(cStatsCharacter,mCurDamage),"mCurDamage");
  AddBoolProperty(mCLASSID,PropId_Dead,SyMemberOffset(cStatsCharacter,mbDead),"mbDead");
  AddVectorProperty(mCLASSID,PropId_AbilityIDs,SyMemberOffset(cStatsCharacter,mAbilityIDs),"mAbilityIDs",SYPROPTYPE_UINT32);
  AddVectorProperty(mCLASSID,PropId_AbilityRanks,SyMemberOffset(cStatsCharacter,mAbilityRanks),"mAbilityRanks",SYPROPTYPE_UINT32);
  AddInt32Property(mCLASSID,PropId_CurLevel,SyMemberOffset(cStatsCharacter,mCurLevel),"mCurLevel");


  AddInt32Property(mCLASSID,PropId_BonusPoints,SyMemberOffset(cStatsCharacter,mBonusPoints),"mBonusPoints");
  AddInt32Property(mCLASSID,PropId_BonusHealth,SyMemberOffset(cStatsCharacter,mBonusHealth),"mBonusHealth");
  AddInt32Property(mCLASSID,PropId_BonusMana,SyMemberOffset(cStatsCharacter,mBonusMana),"mBonusMana");
  AddInt32Property(mCLASSID,PropId_BonusAttackPower,SyMemberOffset(cStatsCharacter,mBonusAttackPower),"mBonusAttackPower");
  AddInt32Property(mCLASSID,PropId_BonusMagicPower,SyMemberOffset(cStatsCharacter,mBonusMagicPower),"mBonusMagicPower");
  AddInt32Property(mCLASSID,PropId_BonusMeleeDefense,SyMemberOffset(cStatsCharacter,mBonusMeleeDefense),"mBonusMeleeDefense");
  AddInt32Property(mCLASSID,PropId_BonusSpellDefense,SyMemberOffset(cStatsCharacter,mBonusSpellDefense),"mBonusSpellDefense");
  return 0;
}

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

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

  return(pObject);
}

void                
cStatsCharacter::Reset()
{
  cStats::Reset();

  mCurDamage = 0;
  mCurBlockLoss = 0;
  mCurManaLoss = 0;
  mbDead = false;

  mOwner->GetPhysics()->SetCollideable(true);
}

void                
cStatsCharacter::Summon()
{
  cAnimCharControllerInterface* pAnimCtrlr = static_cast<cGraphicCharacter*>(mOwner->GetGraphic())->GetAnimController();
  cAnimCharControllerInput* pInput = pAnimCtrlr->GetInput();

  if (pAnimCtrlr->GetAnimHandle(ANIM_SUMMONED) != -1)
  {
    pInput->mScriptAnimID = ANIM_SUMMONED;
    pInput->mScriptAnimAllowHitReactions = false;
    AddCondition("Invisible", ID_NONE, ID_NONE, ID_NONE, 0.25f, ID_NONE);
    pAnimCtrlr->Update(0.001f);
    if (pAnimCtrlr->GetAnimDuration() > 0.0f)
    {
      AddCondition("Invulnerable", ID_NONE, ID_NONE, ID_NONE, pAnimCtrlr->GetAnimDuration(), ID_NONE);
    }
  }
}

void                
cStatsCharacter::Update(float time)
{
  mAbilityRules.Update(time);
  mConditionRules.Update(time);

#ifdef _DRAWDEBUGOVERLAY
  if (mConditionRules.GetNumRules() > 0)
  {
    DEBUGOVERLAY_DRAWSPHERE(mOwner->GetID(), "AreaEffect", mOwner->GetLocation()+SyVect3(0.0f, 2.5f, 0.0f), 0.2f, cDebugOverlay::WHITE);
  }
#endif

  Regenerate(time);

  if (IsDead())
  {
    mDeadTime += time;
    if (mOwner->GetType()==cGameObject::OBJ_PLAYER && mDeadTime > l_ResetTime)
    {
      mDeadTime = 0.0f;
      mOwner->Reset();
      mOwner->GetTitan()->GetCameraController()->RequestHeading(mOwner->GetHeading());
    }
  }
}

void                
cStatsCharacter::Init()
{
  SetMaster(mMasterName.AsChar());

  // initialize abilities & xp by starting class
  mExperience = mpLevel->mXPRequired;
  const SyMap<tGameID, cDatabaseSys::AbilityVector>& abilityMap = mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMap();
  const cAbilityMaster* pAbility;
  bool bHasAbility;

  cIntelPlayer* pPlayerIntel = prop_cast<cIntelPlayer*>(mOwner->GetIntel());

  for (int i=abilityMap.Begin(); i!=abilityMap.End(); i=abilityMap.Next(i))
  {
    for (int j=abilityMap(i).Begin(); j!=abilityMap(i).End(); j=abilityMap(i).Next(j))
    {
      pAbility = abilityMap(i)(j);

      if (mpClass->mName.GetID() != pAbility->mClass ||
          mpLevel->mLevel < pAbility->mLevelMin ||
          pAbility->mCost != 0)
      {
        continue;
      }

      bHasAbility = false;
      for (int k=mAbilityIDs.Begin(); k!=mAbilityIDs.End() && !bHasAbility; k=mAbilityIDs.Next(k))
      {
        if (pAbility->mID.GetID() == (tGameID)mAbilityIDs(k) &&
            pAbility->mRank >= (int)mAbilityRanks(k))
        {
          mAbilityRanks(k) = pAbility->mRank;
          bHasAbility = true;

          if (pPlayerIntel)
          {
            pPlayerIntel->AutoMapAbility(pAbility);
          }
        }
      }

      if (!bHasAbility)
      {
        mAbilityIDs.Add(pAbility->mID.GetID());
        mAbilityRanks.Add(pAbility->mRank);
        if (pPlayerIntel)
        {
          pPlayerIntel->AutoMapAbility(pAbility);
        }
      }
    }
  }

  if (mpMaster && ID_NONE != mpMaster->mNaturalMelee)
  {
    cInventoryCharacter *pInv = static_cast<cInventoryCharacter*>(mOwner->GetInventory());
    SyAssert(pInv!=NULL);
    cItem* pItem = pInv->Add(mpMaster->mNaturalMelee);
    GAME_ASSERT(ERROR_DESIGN, pItem!=NULL, "Can not find Natural Melee weapon for character master '%s'", mMasterName.AsChar());

    if (pItem)
    {
      pInv->Equip(pItem, EQUIP_MELEE);
    }
  }
}

void cStatsCharacter::PostInit()
{
  if (mpMaster)
  {
    // cast natural spell on init
    if (ID_NONE != mpMaster->mNaturalSpell)
    {
      const cSpellMaster* pNaturalSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(mpMaster->mNaturalSpell);
      SyAssert(pNaturalSpell!=NULL);
      if (pNaturalSpell)
      {
        pNaturalSpell->CastSpell(mOwner, mOwner);
      }
    }
  }
}

void
cStatsCharacter::Regenerate(float time)
{
  // check network ownership requirements
  if (!NetworkHasAuthorityOver(mOwner))
  {
    return;
  }

  if (mCurDamage > 0)
  {
    int HealthRegen = CalcHealthRegen();
    float health_amount = ((float)HealthRegen)/1000.0f * time;
    mHealthRegenFrac += health_amount;

    int health_whole = (int) mHealthRegenFrac;
    mCurDamage -= health_whole;
    mHealthRegenFrac -= (float) health_whole; 

    if (mCurDamage < 0)
    {
      mCurDamage = 0;
    }
  }
  else
  {
    mHealthRegenFrac = 0.0f;
  }

  if (mCurBlockLoss > 0)
  {
    int BlockRegen = CalcBlockRegen();
    float block_amount = ((float)BlockRegen)/1000.0f * time;
    mBlockRegenFrac += block_amount;

    int block_whole = (int) mBlockRegenFrac;
    mCurBlockLoss -= block_whole;
    mBlockRegenFrac -= (float) block_whole; 

    if (mCurBlockLoss < 0)
    {
      mCurBlockLoss = 0;
    }
  }
  else
  {
    mBlockRegenFrac = 0.0f;
  }

  if (mCurManaLoss > 0)
  {
    int PowerRegen = CalcManaRegen();
    float power_amount = ((float)PowerRegen)/1000.0f * time;
    mManaRegenFrac += power_amount;

    int power_whole = (int) mManaRegenFrac;
    mCurManaLoss -= power_whole;
    mManaRegenFrac -= (float) power_whole; 

    if (mCurManaLoss < 0)
    {
      mCurManaLoss = 0;
    }
  }
  else
  {
    mManaRegenFrac = 0.0f;
  }
}



void                        
cStatsCharacter::SetMaster(const char *newmaster)
{
  GAME_ASSERT(ERROR_DESIGN, newmaster!=NULL, "Trying to set null master on character");
  SyAssert(mOwner);
  SyAssert(mOwner->GetTitan());

  cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
  SyAssert(db);

  if (!newmaster)
  {
    mpMaster = db->GetCharacterMaster(SyHashResourceID("PAL1_Archer"));
  }
  else
  {
    int id = SyHashResourceID(newmaster);

    mpMaster = db->GetCharacterMaster(id);
    GAME_ASSERT(ERROR_DESIGN, mpMaster!=NULL, "Unable to find character master '%s' for object '%s'", newmaster, mOwner->GetName());
    if (mpMaster == NULL)
    {
      mpMaster = db->GetCharacterMaster(SyHashResourceID("PAL1_Archer"));
    }
  }

  mMasterName.Init(mpMaster->mID.GetName());

  // only use the stored level if we've read one in from the player's save 
  if (mCurLevel <= 0 || mOwner->GetType() == cGameObject::OBJ_NPC)
  {
    if (mpMaster->mLevel > 0 || mOwner->GetType() == cGameObject::OBJ_PLAYER)
    {
      mCurLevel = SY_MAX(mpMaster->mLevel, 1);
    }
    else
    {
      cGameObject *pPlayer = mOwner->GetTitan()->GetRegistry()->BeginType(cGameObject::OBJ_PLAYER);
      int highestPlayerLevel = 0;
      while (pPlayer != NULL)
      {
        cStatsCharacter *pPlayerStats = prop_cast<cStatsCharacter *>(pPlayer->GetStats());

        if (pPlayerStats &&
            pPlayerStats->GetLevel() && 
            pPlayerStats->GetLevel()->mLevel > highestPlayerLevel)
        {
          highestPlayerLevel = pPlayerStats->GetLevel()->mLevel;
        }

        pPlayer = mOwner->GetTitan()->GetRegistry()->NextType(pPlayer);
      }

      mCurLevel = SY_CLAMP(highestPlayerLevel+mpMaster->mLevelDiff, 1, 99);
    }
  }

  mpClass = db->GetCharacterClass(mpMaster->mClass);
  mpLevel = db->GetCharacterLevel(mCurLevel);
  GAME_ASSERT(ERROR_DESIGN, mpClass!=NULL,"Unknown class for character master '%s', object '%s'", mMasterName.AsChar(), mOwner->GetName());
  GAME_ASSERT(ERROR_DESIGN, mpLevel!=NULL,"Unknown level for character master '%s', object '%s'", mMasterName.AsChar(), mOwner->GetName());

  if (!mpClass)
  {
    mpClass = db->GetCharacterClass(SyHashResourceID("Default"));
  }

  if (!mpLevel)
  {
    mpLevel = db->GetCharacterLevel(1);
  }

  // ensure all related esfs are loaded at object creation
  if (mpMaster)
  {
    const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);
    if (pModelData)
    {
      pModelData->Load(mOwner);
    }
  }
}

void
cStatsCharacter::SetProperName(const char *name)
{
  mProperName.Init(name);
}


void
cStatsCharacter::SetOwner(cGameObject *owner)
{
  mOwner = owner;
  mAbilityRules.SetTitan(mOwner->GetTitan());  
  mAbilityRules.SetOwnerID(mOwner->GetID());   
  mConditionRules.SetTitan(mOwner->GetTitan());   
  mConditionRules.SetOwnerID(mOwner->GetID());   
}

const char *                
cStatsCharacter::GetModelName()
{
  if (mpMaster == NULL)
  {
    SyAssert(0); // Set master before calling this function
    return NULL;
  }

  const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

  if (pModelData)
  {
    return pModelData->mModelName;
  }

  return NULL;
}

const char *                
cStatsCharacter::GetRagdollName()
{
  if (mpMaster == NULL)
  {
    SyAssert(0); // Set master before calling this function
    return NULL;
  }

  const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

  if (pModelData)
  {
    return pModelData->mRagdollName;
  }

  return NULL;
}



float 
cStatsCharacter::GetModelScale()
{
  if (mpMaster == NULL)
  {
    SyAssert(0); // Set master before calling this function
    return 1.0f;
  }

  return mpMaster->mScale;
}

const char *                
cStatsCharacter::GetFileName()
{
  if (mpMaster == NULL)
  {
    SyAssert(0); // Set master before calling this function
    return NULL;
  }

  const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

  if (pModelData)
  {
    return pModelData->mFileName;
  }

  return NULL;
}

tGameID
cStatsCharacter::GetRace() const
{
  return mpMaster->mRace;
}

int
cStatsCharacter::GetHealth()
{
  return CalcMaxHealth() - mCurDamage;
}

int
cStatsCharacter::GetBlock()
{

  return CalcMaxBlock() - mCurBlockLoss;
}

int
cStatsCharacter::GetMana()
{
  return CalcMaxMana() - mCurManaLoss;
}

int
cStatsCharacter::GetEssence()
{
  return mEssence;
}

tGameID
cStatsCharacter::GetProjectileTypeID()
{
  SyAssert(prop_cast<cInventoryCharacter*>(mOwner->GetInventory())!=NULL);

  cInventoryCharacter* pInv = static_cast<cInventoryCharacter*>(mOwner->GetInventory());
  cItem *pWeapon = pInv->GetEquippedItem(EQUIP_RANGED);
  tGameID projID = ID_NONE;

  if (pWeapon)
  {
    projID = pWeapon->GetMaster()->mProjectile;
    GAME_ASSERT(ERROR_DESIGN, projID != ID_NONE, "Character has ranged weapon with no projectile type");
  }

  if (ID_NONE == projID)
  {
    tGameID weaponID = GetMaster()->mNaturalRanged;
    if (weaponID != ID_NONE)
    {
      const cStatsItemMaster * master =  mOwner->GetTitan()->GetDatabaseSys()->GetItemMaster(weaponID);
      GAME_ASSERT(ERROR_DESIGN, master!=NULL,"Unknown Natural Ranged Weapon in %s",GetMaster()->mNameString);
      projID = master->mProjectile;
    }
  }

  if (ID_NONE == projID)
  {
    // hacked in default projectile type, should prob be removed eventually
    projID = SyHashResourceID("Arrow");
  }

  return projID;
}

void                
cStatsCharacter::AttackFrame(tGameObjectID target_id, eComboType attackIndex, bool bFromArea)
{
  // Our animation has hit an attack frame...
  if (target_id == ID_NONE)
  {
    SyAssertf(0,"Bad Target ID in Attack Hit");
    return;
  }

  cGameObject *target_obj = mOwner->GetRegistry()->Fetch(target_id);

  if (target_obj == NULL)
  {
    return;
  }
  
  // check network ownership requirements
  if (!NetworkHasAuthorityOver(target_obj))
  {
    return;
  }

  if (!TargetInAttackRange(target_obj, attackIndex, bFromArea))
  {
    return;
  }
  
  // blocking
  cDamagePacket damage;

  cGraphicCharacter *targetGraphic = prop_cast<cGraphicCharacter *>(target_obj->GetGraphic());
  if (targetGraphic && targetGraphic->GetAnimController()->IsBlocking())
  {
    damage.mbBlocked = true;
  }

  float targetRecoveryTime = 1.0f;
  cGraphicCharacter* pMyGraphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  if (pMyGraphic)
  {
    targetRecoveryTime = pMyGraphic->GetAnimController()->GetTargetRecoveryTime(attackIndex);
  }

  damage.mObjectID = mOwner->GetID();
  damage.mAttackerID = mOwner->GetID();
  damage.mDefenderID = target_id;
  damage.mCombo = attackIndex;
  damage.mRecoveryTime = targetRecoveryTime;

  // SGC: todo: Set damage type.
  damage.mPacketType = cRulePacket::CALC_DAMAGE_INITIAL;


  ProcessRulePacket(&damage);

  damage.mPacketType = cRulePacket::CALC_DAMAGE_RAW;
  ProcessRulePacket(&damage);

  damage.mPacketType = cRulePacket::CALC_DAMAGE_REDUCED;
  target_obj->GetStats()->ProcessRulePacket(&damage);

  damage.mPacketType = cRulePacket::EVENT_HIT;
  ProcessRulePacket(&damage);

  target_obj->GetStats()->ApplyDamage(&damage);

}


bool                
cStatsCharacter::TargetInAttackRange(cGameObject *target, eComboType attackIndex, bool bFromArea)
{
  SyAssert(target);

  if (!target)
  {
    return false;
  }

  float range(1.0f);
  cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(mOwner->GetGraphic());
  SyAssert(pCharGraphic);

  if (pCharGraphic)
  {
    cAnimCharControllerInterface *iface = pCharGraphic->GetAnimController();
    SyAssert(iface);
    
    if (iface != NULL)
    {
      range = iface->GetRange(attackIndex);

      SyVect3 dir;
      dir.HPR(mOwner->GetHeading(), 0.0f, 0.0f);

      if (bFromArea || target->GetDistanceInDirection(mOwner->GetLocation(), dir) <= range)
      {
        cGraphicCharacter* pTargetGraphic = prop_cast<cGraphicCharacter *>(target->GetGraphic());

        if (pTargetGraphic)
        {
          if (COMBO_JUMPATTACK != attackIndex &&
              pTargetGraphic->GetAnimController()->IsGettingUp())
          {
            return false;
          }
          else
          {
            cDamagePacket dodgeChance;
            dodgeChance.mPacketType = cRulePacket::CALC_DODGE_CHANCE;
            dodgeChance.mAttackerID = mOwner->GetID();
            dodgeChance.mDefenderID = target->GetID();
            dodgeChance.mbRanged = COMBO_RANGEDATTACK != attackIndex;
            target->GetStats()->ProcessRulePacket(&dodgeChance);

            if (dodgeChance.GetTotal() > 0 &&
                mOwner->GetTitan()->Random(1, 100) <= dodgeChance.GetTotal())
            {
              return false;
            }
          }
        }

        return true;
      }
    }
  }

  return false;
}


void 
cStatsCharacter::ApplyDamage(cDamagePacket *packet)
{
  if (mbDead)
  {
    return;
  }
 
  packet->mPacketType = cRulePacket::EVENT_APPLY_DAMAGE;
  ProcessRulePacket(packet);

  GAME_ASSERT(ERROR_DESIGN, packet->GetTotal() >= 0, "Can't do negative damage, must use ApplyHealing");

  cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(mOwner->GetGraphic());
  SyAssert(pCharGraphic);
  cAnimCharControllerInput *input = pCharGraphic->GetAnimInput();

  float impact_force = l_CHARACTER_IMPACT_FORCE;

  cGameObjectRegistry *pRegistry = mOwner->GetRegistry();
  cGameObject* pAttacker = pRegistry->Fetch(packet->mAttackerID);

  static const int32 ID_LastAgentID = SyHashResourceID("LAST_DAMAGE_AGENT");
  static const int32 ID_LastDamageAmount = SyHashResourceID("LAST_DAMAGE_AMOUNT");
  static const int32 ID_LastDamageType = SyHashResourceID("LAST_DAMAGE_TYPE");
  static const int32 ID_LastDamageSpellID = SyHashResourceID("LAST_DAMAGE_SPELLHASH");
  static const int32 ID_LastDamageCombo = SyHashResourceID("LAST_DAMAGE_COMBO");

  Value_Set(ID_LastAgentID,packet->mAgentID);
  Value_Set(ID_LastDamageAmount,packet->GetTotal());
  Value_Set(ID_LastDamageType,packet->GetDamageType());
  Value_Set(ID_LastDamageSpellID, packet->mSpellID);
  Value_Set(ID_LastDamageCombo, packet->mCombo);

  mOwner->GetTitan()->GetScriptSys()->ScriptEvent(PET_HIT,mOwner->GetID(),packet->mAttackerID);

  packet->SetTotal(Value_Get(ID_LastDamageAmount),"SCRIPT","SCRIPT");
  int newDamageType = Value_Get(ID_LastDamageType);
  if (newDamageType >= 0 && newDamageType < NUM_DAMAGE_TYPES)
  {
    packet->SetDamageType((eDamageType)newDamageType);
  }

  if (packet->mbBlocked)
  {
    SyAssert(pAttacker);

    cDamagePacket strikethroughPacket;
    strikethroughPacket.mPacketType = cRulePacket::CALC_STRIKETHROUGH;
    strikethroughPacket.mObjectID = packet->mAttackerID;
    strikethroughPacket.mCombo = packet->mCombo;    
    pAttacker->GetStats()->ProcessRulePacket(&strikethroughPacket);

    mCurBlockLoss += strikethroughPacket.GetTotal();

    if (GetBlock() < 0)
    {
      packet->mbBlocked = false;
    }
    else
    {
//      if (mOwner->GetTitan()->GetShowDamageFlash())
//      {
//        mOwner->GetTitan()->GetDamageNumMgr()->Add(mOwner->GetID(),strikethroughPacket.GetTotal(),SyColor32F(0.5f, 0.5f, 0.5f, 1.0f));
//      }

      impact_force *= 0.75f;
    }
  }

  if (!packet->mbBlocked)
  {
    mCurDamage += packet->GetTotal();
  }

  HitReact(packet->mAttackerID, packet->GetTotal(), packet->mRecoveryTime, packet->mbBlocked);

  // notify AI of attack 
  mOwner->GetIntel()->OnHit(packet->mAttackerID, packet->GetTotal(), packet->mRecoveryTime, packet->mbRanged);

  // physics
  cPhysicsAnimated *pPhys = prop_cast<cPhysicsAnimated *>(mOwner->GetPhysics());
  if (pPhys != NULL &&
      pAttacker != NULL &&
      packet->mRecoveryTime > 0.0f)
  {
    SyVect3 towards;
    towards.Sub(mOwner->GetLocation(),pAttacker->GetLocation());
    towards.Y = 0.0f;
    towards.Normalize();
    
    // juggle
    if (mOwner->GetType() == cGameObject::OBJ_NPC &&
        pCharGraphic && pCharGraphic->GetAnimController()->IsInAir())
    {
      SyVect3 vel(pPhys->GetVelocity());
      if (vel.Y < 0.0f)
      {
        vel.Y = 0.0f;
        pPhys->SetVelocity(vel);
      }

      towards *= impact_force;
      towards.Y += l_CHARACTER_IMPACT_JUGGLE;
      impact_force = towards.NormalizeMagn();
    }

    pPhys->Impact(towards,impact_force);
  }

  // network update
  cNetDamagePacket netpacket;
  netpacket.mTotalDamage = mCurDamage;
  netpacket.mAttacker = packet->mAttackerID;
  netpacket.mDeath = input->mDeath;
  netpacket.mHitReact = input->mHitReact;
  netpacket.mHitReactTime = packet->mRecoveryTime;
  netpacket.mDamageType = packet->GetDamageType();

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

void 
cStatsCharacter::ApplyHealing(cCalcPacket *packet)
{
  if (mbDead)
  {
    return;
  }

  packet->mObjectID = mOwner->GetID();
  packet->mPacketType = cRulePacket::EVENT_APPLY_HEALING;
  ProcessRulePacket(packet);

  GAME_ASSERT(ERROR_CODE, packet->GetTotal() >= 0, "Can't do negative healing, should be applying damage");

  int amt = packet->GetTotal();

  mCurDamage -= amt;

  if (mCurDamage < 0)
  {
    amt += mCurDamage;
    mCurDamage = 0;
  }

  if (mOwner->GetTitan()->GetShowDamageFlash())
  {
    mOwner->GetTitan()->GetDamageNumMgr()->Add(mOwner->GetID(), amt, SyColor32F(0.2f, 1.0f, 0.2f, 1.0f));
  }

  // network update
/*
  cNetDamagePacket netpacket;
  netpacket.mTotalDamage = mCurDamage;
  netpacket.mAttacker = ID_NONE;
  netpacket.mDeath = false;
  netpacket.mHitReact = false;

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

void 
cStatsCharacter::ApplyManaCost(cCalcPacket *packet)
{
  if (mbDead)
  {
    return;
  }

  packet->mObjectID = mOwner->GetID();
  packet->mPacketType = cRulePacket::EVENT_APPLY_MANA_COST;
  ProcessRulePacket(packet);

  mCurManaLoss += packet->GetTotal();

  if (mCurManaLoss < 0)
  {
    mCurManaLoss = 0;
  }

  // network update
//  cNetDamagePacket netpacket;
//  netpacket.mTotalDamage = mCurDamage;
//  netpacket.mAttacker = packet->mAttackerID;
//  netpacket.mDeath = input->mDeath;
//  netpacket.mHitReact = input->mHitReact;
//  netpacket.mHitReactAmount = packet->GetTotal();
//
//  char buf[1024];
//  int len = netpacket.PackBuffer(buf, sizeof(buf));
//  mOwner->GetTitan()->GetPeeringNetwork()->ObjectBroadcast(mOwner->GetID(), buf, len);
}

void 
cStatsCharacter::AddEssence(cCalcPacket *packet)
{
  if (mbDead)
  {
    return;
  }

//  packet->mObjectID = mOwner->GetID();
//  packet->mPacketType = cRulePacket::EVENT_APPLY_MANA_COST;
//  ProcessRulePacket(packet);

  mEssence += packet->GetTotal();

  GAME_ASSERT(ERROR_CODE, mEssence >= 0, "Trying to remove more essence than character has");

  if (mEssence < 0)
  {
    mEssence = 0;
  }
}

void                
cStatsCharacter::Die(tGameObjectID killer)
{
  if (!mbDead)
  {
    mCurDamage = CalcMaxHealth();
    // unconscious
    SyAssert(prop_cast<cGraphicCharacter *>(mOwner->GetGraphic()));
    mbDead = true;

    cGameObject *killer_obj = mOwner->GetRegistry()->Fetch(killer);

    // temp: SGC Where to put this?
    //((cInventoryCharacter *)mOwner->GetInventory())->DropAll();

    if (mOwner->IsLocal())
    {
      tGameID treasure_set = mpMaster->mTreasureSet;
      if (treasure_set != ID_NONE)
      {
        cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
        cTreasureSet *treasureset = db->GetTreasureSet(treasure_set);
        if (treasureset != NULL)
        {
          treasureset->Drop(mOwner, mOwner->GetLocation(), killer_obj);
        }
        else
        {
          GAME_ASSERT(ERROR_DESIGN, false,"Unknown treasure set for NPC '%s'(%s)",mOwner->GetName(),mpMaster->mNameString);
        }
      }
      else // all right - try falling back to the default level treasure set
      {
        cTreasureSet* treasureSet = mOwner->GetTitan()->GetRegistry()->GetLevelObject()->GetDefaultTreasureSet();
        if (treasureSet)
        {
          treasureSet->Drop(mOwner, mOwner->GetLocation(), killer_obj);
        }
      }

      if (!mbDisableDrop)
      {
        static const tGameID HEALTH_DROP_MASTER = SyHashResourceID("HealthDropProjectile");
        static const tGameID MANA_DROP_MASTER = SyHashResourceID("ManaDropProjectile");
        static const tGameID ESSENCE_DROP_MASTER = SyHashResourceID("EssenceDropProjectile");

        int dropChance = SY_ROUND(mpLevel->mStats.mDropChance * mpClass->mStats.mDropChance);

        cCalcPacket bonusHealthDropChance;
        bonusHealthDropChance.mPacketType = cRulePacket::CALC_HEALTH_DROP_CHANCE;

        cCalcPacket bonusManaDropChance;
        bonusManaDropChance.mPacketType = cRulePacket::CALC_MANA_DROP_CHANCE;

        cCalcPacket bonusEssenceDropChance;
        bonusEssenceDropChance.mPacketType = cRulePacket::CALC_ESSENCE_DROP_CHANCE;

        if (killer_obj)
        {
          cStatsCharacter* pKillerStats = prop_cast<cStatsCharacter*>(killer_obj->GetStats());
          if (pKillerStats)
          {
            pKillerStats->ProcessRulePacket(&bonusHealthDropChance);
            pKillerStats->ProcessRulePacket(&bonusManaDropChance);
            pKillerStats->ProcessRulePacket(&bonusEssenceDropChance);
          }
        }

        int dropRand1 = mOwner->GetTitan()->Random(0, 100);      

        int dropHealthWeight = SY_ROUND(mpLevel->mStats.mDropHealthWeight * mpClass->mStats.mDropHealthWeight);
        int dropManaWeight = SY_ROUND(mpLevel->mStats.mDropManaWeight * mpClass->mStats.mDropManaWeight);
        int dropEssenceWeight = SY_ROUND(mpLevel->mStats.mDropEssenceWeight * mpClass->mStats.mDropEssenceWeight);

        int dropRand2 = dropHealthWeight+dropManaWeight+dropEssenceWeight + 1;

        if (dropHealthWeight+dropManaWeight+dropEssenceWeight > 0)
        {
          dropRand2 = mOwner->GetTitan()->Random(1, dropHealthWeight+dropManaWeight+dropEssenceWeight);
        }

        if ((dropRand1 < dropChance && dropRand2 <= dropHealthWeight) ||
            mOwner->GetTitan()->Random(1, 100) <= bonusHealthDropChance.GetTotal())
        {
          float dropHealthAmount = mpLevel->mStats.mDropHealthAmount * mpClass->mStats.mDropHealthAmount;
          cDropList::DropHealth(mOwner, mOwner->GetLocation(), SY_ROUND(dropHealthAmount), HEALTH_DROP_MASTER);
        }
          
        if ((dropRand1 < dropChance && dropRand2 > dropHealthWeight && dropRand2 <= dropHealthWeight+dropManaWeight) ||
            mOwner->GetTitan()->Random(1, 100) <= bonusManaDropChance.GetTotal())
        {
          float dropManaAmount = mpLevel->mStats.mDropManaAmount * mpClass->mStats.mDropManaAmount;
          cDropList::DropMana(mOwner, mOwner->GetLocation(), SY_ROUND(dropManaAmount), MANA_DROP_MASTER);
        }
        
        if ((dropRand1 < dropChance && dropRand2 > dropHealthWeight+dropManaWeight && dropRand2 <= dropHealthWeight+dropManaWeight+dropEssenceWeight) ||
            mOwner->GetTitan()->Random(1, 100) <= bonusEssenceDropChance.GetTotal())
        {
          float dropEssenceAmount = mpLevel->mStats.mDropEssenceAmount * mpClass->mStats.mDropEssenceAmount;
          cDropList::DropEssence(mOwner, mOwner->GetLocation(), SY_ROUND(dropEssenceAmount), ESSENCE_DROP_MASTER);
        }
      }

      mOwner->GetTitan()->GetScriptSys()->ScriptEvent(PET_DEATH,mOwner->GetID(),killer);
      // notify AI of death
      mOwner->GetIntel()->OnDeath();
    }

    mOwner->GetPhysics()->SetCollideable(false);

    const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

    if (pModelData)
    {
      pModelData->Die(mOwner, killer_obj);
    }

    AwardKillExperience(mOwner);
  }     
}

bool
cStatsCharacter::CanLift(cGameObject* pTarget)
{
  if (!pTarget)
  {
    return false;
  }

  cGraphicCharacter* pTargetGraphic = prop_cast<cGraphicCharacter*>(pTarget->GetGraphic());

  if (pTargetGraphic && pTargetGraphic->GetAnimController()->GetAnimState() == AS_SCRIPT)
  {
    return false;
  }

  float throwStr = 0.0f;

  const cCharacterClass* pClass = mOwner->GetTitan()->GetDatabaseSys()->GetCharacterClass(GetMaster()->mClass);
  if (pClass)
  {
    throwStr = pClass->mStats.mThrowStrength;
  }

  cStatsCharacter* pTargetCharStats = prop_cast<cStatsCharacter*>(pTarget->GetStats());

  if (pTargetCharStats && throwStr >= pTargetCharStats->GetMaster()->mWeight)
  {
    return true;
  }

  cStatsProp* pTargetPropStats = prop_cast<cStatsProp*>(pTarget->GetStats());

  if (!pTargetPropStats || !pTargetPropStats->IsLiftable())
  {
    return false;
  }

  return throwStr >= pTargetPropStats->GetMaster()->mWeight;
}

void
cStatsCharacter::LevelUp()
{
  mCurDamage = 0;
  mCurBlockLoss = 0;
  mCurManaLoss = 0;

  ++mAbilityPoints;
  mBonusPoints += l_BonusPointsPerLevel;

  // award any 0-cost abilities to the player
  const SyMap<tGameID, cDatabaseSys::AbilityVector>& abilityMap = mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMap();
  const cAbilityMaster* pAbility;
  bool bHasAbility;

  cIntelPlayer* pPlayerIntel = prop_cast<cIntelPlayer*>(mOwner->GetIntel());

  for (int i=abilityMap.Begin(); i!=abilityMap.End(); i=abilityMap.Next(i))
  {
    for (int j=abilityMap(i).Begin(); j!=abilityMap(i).End(); j=abilityMap(i).Next(j))
    {
      pAbility = abilityMap(i)(j);

      if (mpClass->mName.GetID() != pAbility->mClass ||
          mpLevel->mLevel != pAbility->mLevelMin ||
          pAbility->mCost != 0)
      {
        continue;
      }

      bHasAbility = false;
      for (int k=mAbilityIDs.Begin(); k!=mAbilityIDs.End() && !bHasAbility; k=mAbilityIDs.Next(k))
      {
        if (pAbility->mID.GetID() == (tGameID)mAbilityIDs(k) &&
            pAbility->mRank >= (int)mAbilityRanks(k))
        {
          mAbilityRanks(k) = pAbility->mRank;
          bHasAbility = true;

          if (pPlayerIntel)
          {
            pPlayerIntel->AutoMapAbility(pAbility);
          }
        }
      }

      if (!bHasAbility)
      {
        mAbilityIDs.Add(pAbility->mID.GetID());
        mAbilityRanks.Add(pAbility->mRank);
        if (pPlayerIntel)
        {
          pPlayerIntel->AutoMapAbility(pAbility);
        }
      }
    }
  }

  if (mOwner->GetType() == cGameObject::OBJ_PLAYER)
  {
    UpdateNPCLevels();

    static tGameID sLevelUpFXID = SyHashResourceID("fx_spell_player_levelUp");

    SyScene* pScene = mOwner->GetTitan()->GetScene();
    int fxScriptHandle = -1;
    SyActorHandle actorHandle = mOwner->GetGraphic()->GetActorHandle();
    if (SyActorNull != actorHandle &&
        pScene->GetDictionary()->FindTyped(sLevelUpFXID, SYRESOURCETYPE_FXSCRIPT, fxScriptHandle))
    {
      pScene->GetFXScriptSystem()->PlayScript(fxScriptHandle, 1, &actorHandle);
    }
  }
}

void
cStatsCharacter::SetLevel(int lvl)
{
  mAbilityPoints += lvl - mCurLevel;

  const cCharacterLevel* pLevel = mOwner->GetTitan()->GetDatabaseSys()->GetCharacterLevel(lvl);
  SyAssert(pLevel);

  if (!pLevel)
  {
    return;
  }

  mCurLevel = lvl;
  mExperience = pLevel->mXPRequired;
  mpLevel = pLevel;
  mCurDamage = 0;
  mCurManaLoss = 0;
  mCurBlockLoss = 0;

  cIntelPlayer* pPlayerIntel = prop_cast<cIntelPlayer*>(mOwner->GetIntel());

  // remove abilities that have too high a level requirement
  const cAbilityMaster* pAbility;
  int rank;
  bool bRemoveAbility;

  for (int i=mAbilityIDs.Begin(); i!=mAbilityIDs.End(); )
  {
    bRemoveAbility = false;
    rank = mAbilityRanks(i);
    pAbility = mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMaster(mAbilityIDs(i), mAbilityRanks(i));

    while (pAbility && 
           !bRemoveAbility && 
           pAbility->mLevelMin > mCurLevel)
    {
      mAbilityPoints += pAbility->mCost;
      if (rank > 1)
      {
        --rank;
        pAbility = mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMaster(mAbilityIDs(i), rank);

        if (!pAbility)
        {
          bRemoveAbility = true;
        }
        else
        {
          mAbilityRanks(i) = rank;
          if (pPlayerIntel)
          {
            pPlayerIntel->AutoMapAbility(pAbility);
          }
        }
      }
      else
      {
        bRemoveAbility = true;
      }
    }

    if (bRemoveAbility)
    {
      mAbilityRanks.ReplaceLast(i);
      i = mAbilityIDs.ReplaceLast(i);

      if (pPlayerIntel)
      {
        SyAssert(pAbility!=NULL);
        pPlayerIntel->RemoveMappedAbility(pAbility);
      }
    }
    else
    {
      i = mAbilityIDs.Next(i);
    }
  }

  if (mOwner->GetType() == cGameObject::OBJ_PLAYER)
  {
    UpdateNPCLevels();
  }
}

void
cStatsCharacter::UpdateNPCLevels()
{
  SyAssert(mOwner->GetType() == cGameObject::OBJ_PLAYER); // gonna cause recursive blow up if called on npc b/c this is called from SetLevel

  if (mOwner->GetType() != cGameObject::OBJ_PLAYER)
  {
    return;
  }

  cGameObject *pPlayer = mOwner->GetRegistry()->BeginType(cGameObject::OBJ_PLAYER);
  int highestPlayerLevel = 0;

  while (pPlayer != NULL)
  {
    cStatsCharacter *pPlayerStats = prop_cast<cStatsCharacter *>(pPlayer->GetStats());

    if (pPlayerStats && pPlayerStats->GetLevel()->mLevel > highestPlayerLevel)
    {
      highestPlayerLevel = pPlayerStats->GetLevel()->mLevel;
    }

    pPlayer = mOwner->GetRegistry()->NextType(pPlayer);
  }

  SyAssert(highestPlayerLevel > 0);

  cGameObject* pNPC = mOwner->GetRegistry()->BeginType(cGameObject::OBJ_NPC);

  while (pNPC)
  {
    cStatsCharacter *pNPCStats = prop_cast<cStatsCharacter *>(pNPC->GetStats());

    // only use the stored level if we've read one in from the player's save 
    if (pNPCStats && pNPCStats->GetMaster() && pNPCStats->GetMaster()->mLevel <= 0)
    {
      int npcLevel = SY_CLAMP(highestPlayerLevel+pNPCStats->GetMaster()->mLevelDiff, 1, 99);
      pNPCStats->SetLevel(npcLevel);
    }

    pNPC = mOwner->GetRegistry()->NextType(pNPC);
  }
}

void
cStatsCharacter::AwardKillExperience(cGameObject* pDeadObj)
{
  SyAssert(pDeadObj != NULL);
  
  if (!pDeadObj || 
      pDeadObj->GetType() != cGameObject::OBJ_NPC)
  {
    return;
  }

  cGameObjectRegistry* pRegistry = pDeadObj->GetRegistry();
  cGameObject* pObj = pRegistry->BeginType(cGameObject::OBJ_PLAYER);
  cStatsCharacter* pStats;

  int highestLevel = 0;
  int numPlayers = 0;

 
  while(pObj != NULL)
  {
    SyAssert(pObj->GetType()==cGameObject::OBJ_PLAYER);

    ++numPlayers;

    SyAssert(prop_cast<cStatsCharacter*>(pObj->GetStats())!=NULL);
    pStats = static_cast<cStatsCharacter*>(pObj->GetStats());

    if (pStats->GetLevel()->mLevel > highestLevel)
    {
      highestLevel = pStats->GetLevel()->mLevel;
    }
    pObj = pRegistry->NextType(pObj);
  }

  SyAssert(prop_cast<cStatsCharacter*>(pDeadObj->GetStats())!=NULL);

  int xp = pDeadObj->GetTitan()->GetDatabaseSys()->GetExperienceTable()->GetExperience(static_cast<cStatsCharacter*>(pDeadObj->GetStats())->GetLevel()->mLevel-highestLevel);  
  xp = xp/numPlayers + ((xp%numPlayers)==0?0:1);

  xp = SY_ROUND(((float)xp)*static_cast<cStatsCharacter*>(pDeadObj->GetStats())->GetClass()->mXPMultiplier);

  AwardGroupExperience(pRegistry, xp);
}

void
cStatsCharacter::AwardExperience(int xp)
{
  cCalcPacket packet;
  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_EXPERIENCE_GAIN;
  packet.SetTotal(xp, "Award Kill Experience", "Experience Table");
  ProcessRulePacket(&packet);

  mExperience += packet.GetTotal();

  if (mOwner->GetType() == cGameObject::OBJ_PLAYER)
  {
    const cCharacterLevel *pNextLevel = mOwner->GetTitan()->GetDatabaseSys()->GetCharacterLevel(mCurLevel+1);
    if (pNextLevel && mExperience >= pNextLevel->mXPRequired)
    {
      mCurLevel++;
      mpLevel = pNextLevel;
      LevelUp();
    }
  }
}

void
cStatsCharacter::AwardGroupExperience(cGameObjectRegistry* pRegistry, int xp)
{

  cGameObject* pObj = pRegistry->BeginType(cGameObject::OBJ_PLAYER);
  while(pObj != NULL)
  {
    SyAssert(pObj->GetType()==cGameObject::OBJ_PLAYER);
    SyAssert(prop_cast<cStatsCharacter*>(pObj->GetStats())!=NULL);
    static_cast<cStatsCharacter*>(pObj->GetStats())->AwardExperience(xp);

    pObj = pRegistry->NextType(pObj);
  }
}

bool
cStatsCharacter::NetworkHasAuthorityOver(cGameObject *obj)
{
#if 0
  // victim overrides attacker 
  if (obj->GetType()==cGameObject::OBJ_PLAYER )
  {
    if (obj->IsRemote())
    {
      return false; 
    }
    else
    {
      return true;
    }
  }

  if (mOwner->GetType()==cGameObject::OBJ_PLAYER)
  {
    if (mOwner->IsRemote())
    {
      return false;
    }
    else
    {
      return true; // local players are always authoritative
    }
  }
#endif

  return !obj->IsRemote();
}

void 
cStatsCharacter::HitReact(tGameObjectID attacker, int dmgAmt, float recoveryTime, bool bBlocked)
{
  cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(mOwner->GetGraphic());
  SyAssert(pCharGraphic);

  cAnimCharControllerInput *input = pCharGraphic->GetAnimInput();
  SyAssertf(input!=NULL,"Bad object construction: no input?");

  if (!bBlocked)
  {
    if (mCurDamage >= CalcMaxHealth())
    {
      input->mDeath = true;
      input->mHitReactTarget = attacker;
    }
    else
    {
      input->mDeath = false;
    }
  }

  if (!input->mbKnockdown && recoveryTime > 0.0f)
  {
    input->mHitReact = true;
    input->mHitReactTime = recoveryTime;
    input->mHitReactTarget = attacker;
  }

  if (mOwner->GetTitan()->GetShowDamageFlash() && !bBlocked && dmgAmt > 0)
  {
    mOwner->GetTitan()->GetDamageNumMgr()->Add(mOwner->GetID(), dmgAmt, SyColor32F(1.0f, 0.2f, 0.2f, 1.0f));
  }
}

bool                
cStatsCharacter::NetworkReceiveBroadcast(const char *packet,int size)
{
  if (cStats::NetworkReceiveBroadcast(packet,size))
  {
    return true;
  }

  SyAssertf(mOwner->IsRemote(),"Network Broadcast on local object?");

  cNetPacket::eNetPacketType type;

  SyPack::Unpack8u(packet, &type);   

  if (type != cNetPacket::NET_DAMAGE)
  {
    return false;
  }

  cNetDamagePacket pPacket;
  pPacket.UnpackBuffer(packet, size);

  SyAssertf(mOwner->IsRemote(), "Owner receiving network instructions?"); 
  SyAssertf(pPacket.mType == cNetPacket::NET_DAMAGE, "Unknown packet");
  int dmgAmt = pPacket.mTotalDamage - mCurDamage;
  mCurDamage = pPacket.mTotalDamage;
  HitReact(pPacket.mAttacker, dmgAmt, pPacket.mHitReactTime, false);
  
  return true;
}; // the owning peer of this object has broadcast an update

void                
cStatsCharacter::ProcessRulePacket(cRulePacket *packet)
{
  packet->mObjectID = mOwner->GetID();

  LogPacketStart(packet,mOwner->GetTitan());
  mOwner->GetTitan()->GetGlobalCharacterRules()->ProcessPacket(packet);
  mAbilityRules.ProcessPacket(packet);
  mConditionRules.ProcessPacket(packet);

  // todo: "virtual" items  / natural attacks
  mOwner->GetInventory()->ProcessRulePacket(packet);

  // process environmental effects
  cAreaEffectSys::Get()->ProcessPacket(packet);

  LogPacketEnd(packet,mOwner->GetTitan());
}

bool                
cStatsCharacter::ActivateObject(tGameObjectID targetID)
{
  cGameObject *target = mOwner->GetRegistry()->Fetch(targetID);
  if (target == NULL )
  {
    return true;  // valid in case object got blown up in between request and activate
  }

  static const float ATTACK_RANGE(2.5f);
  if (target->GetDistance(mOwner) > ATTACK_RANGE)
  {
    // object out of range
    return false;
  }

  if (!mOwner->IsLocal())
  {
    return false;
  }

  eActivateType actType = target->GetStats()->GetActivateType();
  cGameObject::tObjectType objType = target->GetType();

//  BP 3/31 use both attack buttons to throw instead of action button
//  bool bPickupPropOrChar = ACTIVATE_PICKUP == actType && (cGameObject::OBJ_PROP == objType || cGameObject::OBJ_NPC == objType);
  bool bPickupPropOrChar = ACTIVATE_PICKUP == actType && cGameObject::OBJ_PROP == objType;
  bool bPutdownProp = ACTIVATE_PUTDOWN == actType && cGameObject::OBJ_PROP == objType;
  bool bPushProp = ACTIVATE_PUSH == actType && cGameObject::OBJ_PROP == objType;
  bool bFlipLever = ACTIVATE_FLIP == actType && cGameObject::OBJ_PROP == objType;

  // don't actually activate throwable props/npcs
  if (!bPickupPropOrChar && !bPutdownProp && !bPushProp && !bFlipLever)
  {
    target->Activate(mOwner);
    return true;
  }

  return false;
}


int                         
cStatsCharacter::CalcMaxHealth()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_HEALTH_MAX;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcHealthRegen()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_HEALTH_REGEN;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcMaxMana()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_MANA_MAX;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcManaRegen()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_MANA_REGEN;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcMaxBlock()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_BLOCK_MAX;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcBlockRegen()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_BLOCK_REGEN;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcAttackSpeed()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_ATTACK_SPEED;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcMovementSpeed()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_MOVEMENT_SPEED;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcAttackPower()
{
  cDamagePacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.SetDamageType(DT_PHYSICAL);
  packet.mPacketType = cRulePacket::CALC_OFFENSE; // i know the terminology isn't consistant - i blame design :)
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcMagicPower()
{
  cCalcPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::CALC_SPELL_POWER;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcMeleeDefense()
{
  cDamagePacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.SetDamageType(DT_PHYSICAL);
  packet.mPacketType = cRulePacket::CALC_DEFENSE;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}

int                         
cStatsCharacter::CalcSpellDefense()
{
  cDamagePacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.SetDamageType(DT_MAGICAL);
  packet.mPacketType = cRulePacket::CALC_DEFENSE;
  ProcessRulePacket(&packet);
  return packet.GetTotal();
}


void                        
cStatsCharacter::AddHealth(int points)
{
  points = SY_MIN(points,mBonusPoints);
  mBonusHealth += points*l_HealthPointsPerBonusPoint;
  mBonusPoints -= points;
}

void                        
cStatsCharacter::AddMana(int points)
{
  points = SY_MIN(points,mBonusPoints);
  mBonusMana += points*l_ManaPointsPerBonusPoint;
  mBonusPoints -= points;
}

void                        
cStatsCharacter::AddAttackPower(int points)
{
  points = SY_MIN(points,mBonusPoints);
  mBonusAttackPower += points;
  mBonusPoints -= points;
}

void                        
cStatsCharacter::AddMagicPower(int points)
{
  points = SY_MIN(points,mBonusPoints);
  mBonusMagicPower += points;
  mBonusPoints -= points;
}

void                        
cStatsCharacter::AddMeleeDefense(int points)
{
  points = SY_MIN(points,mBonusPoints);
  mBonusMeleeDefense += points;
  mBonusPoints -= points;
}

void                        
cStatsCharacter::AddSpellDefense(int points)
{
  points = SY_MIN(points,mBonusPoints);
  mBonusSpellDefense += points;
  mBonusPoints -= points;
}


bool
cStatsCharacter::QueryFlag(const char* flagName)
{
  cFlagPacket packet;

  packet.mObjectID = mOwner->GetID();
  packet.mPacketType = cRulePacket::QUERY_FLAG;
  packet.SetFlag(flagName);
  packet.Set(false);
  mConditionRules.ProcessPacket(&packet);
  return packet.Get();
};

const cStatsBlockBehavior*
cStatsCharacter::GetBlockBehavior()
{
  SyAssert(mpMaster && mpMaster->mBlockBehavior != ID_NONE);

  const cStatsBlockBehavior* pBB = mOwner->GetTitan()->GetDatabaseSys()->GetBlockBehavior(mpMaster->mBlockBehavior);

  GAME_ASSERT(ERROR_DESIGN, pBB != NULL, "Could not find block behavior for character type '%s'", mpMaster->mID.GetName());

  return pBB;
}

void
cStatsCharacter::OverrideAbilitySet(tGameID abilitySetID)
{
  mAbilitySetOverrideID = abilitySetID;

  GAME_ASSERT(ERROR_DESIGN, abilitySetID == ID_NONE || mOwner->GetTitan()->GetDatabaseSys()->GetAbilitySet(abilitySetID)!=NULL, "Could not find override abilitySet");
}

const cStatsAbilitySet*
cStatsCharacter::GetAbilitySet()
{
  SyAssert(mpMaster && mpMaster->mAbilitySet != ID_NONE);

  const cStatsAbilitySet* pAS = NULL;
  
  if (ID_NONE != mAbilitySetOverrideID)
  {
    pAS = mOwner->GetTitan()->GetDatabaseSys()->GetAbilitySet(mAbilitySetOverrideID);
  }

  if (!pAS)
  {
    pAS = mOwner->GetTitan()->GetDatabaseSys()->GetAbilitySet(mpMaster->mAbilitySet);
  }

  GAME_ASSERT(ERROR_DESIGN, pAS != NULL, "Could not find ability set for character type '%s'", mpMaster->mID.GetName());

  return pAS;
}

const cAbilityMaster*
cStatsCharacter::GetAbility(tGameID abilityID)
{
  SyAssert(mAbilityIDs.Size() == mAbilityRanks.Size());

  for (int i=mAbilityIDs.Begin(); i!=mAbilityIDs.End(); i=mAbilityIDs.Next(i))
  {
    if (mAbilityIDs(i) == (uint32)abilityID)
    {
      return mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMaster(mAbilityIDs(i), mAbilityRanks(i));
    }
  }

  const cStatsAbilitySet* pAbilitySet = GetAbilitySet();
  for (int i=pAbilitySet->mEntries.Begin(); i!=pAbilitySet->mEntries.End(); i=pAbilitySet->mEntries.Next(i))
  {
    if (pAbilitySet->mEntries(i).mAbilityID1 == abilityID ||
        pAbilitySet->mEntries(i).mAbilityID2 == abilityID ||
        pAbilitySet->mEntries(i).mAbilityID3 == abilityID)
    {
      return mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMaster(abilityID, -1);
    }
  }

  // always allow comboL
  static tGameID sComboLID = SyHashResourceID("ComboL");
  if (sComboLID == abilityID)
  {
    return mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMaster(sComboLID, -1);
  }

  return NULL;
}

float
cStatsCharacter::GetAIMovementSpeed()
{
  SyAssert(mpClass != NULL && mpMaster != NULL);

  if (mpClass && mpMaster)
  {
    return mpClass->mStats.mMovementSpeed * mpMaster->mMovementSpeed;
  }

  return 1.0f;
}

float
cStatsCharacter::GetAICloseDistance()
{
  SyAssert(mpClass != NULL && mpMaster != NULL);

  if (mpClass && mpMaster)
  {
    return mpClass->mStats.mCloseDistance * mpMaster->mCloseDistance;
  }

  return 1.0f;
}

float
cStatsCharacter::GetAIMeleeRange()
{
  SyAssert(mpClass != NULL && mpMaster != NULL);

  if (mpClass && mpMaster)
  {
    return mpClass->mStats.mMeleeRange * mpMaster->mMeleeRange;
  }

  return 1.0f;
}
float
cStatsCharacter::GetAIAccuracy()
{
  SyAssert(mpClass != NULL && mpMaster != NULL);

  if (mpClass && mpMaster)
  {
    return mpClass->mStats.mAccuracy * mpMaster->mAccuracy;
  }

  return 1.0f;
}

char
cStatsCharacter::GetAIBehaviorType()
{
  SyAssert(mpClass != NULL && mpMaster != NULL);

  if (mpMaster && mpMaster->mNPCBehaviorType != NPCBEH_CLASSDEFAULT)
  {
    return mpMaster->mNPCBehaviorType;
  }

  if (mpClass && mpClass->mNPCBehaviorType != NPCBEH_CLASSDEFAULT)
  {
    return mpClass->mNPCBehaviorType;
  }

  return NPCBEH_NONE;
}

char
cStatsCharacter::GetFaction()
{
  SyAssert(mpClass != NULL && mpMaster != NULL);

  if (mOwner->GetType() == cGameObject::OBJ_PLAYER)
  {
    return NPCFACTION_ALLY;
  }

  if (mpMaster && mpMaster->mNPCFaction != NPCFACTION_CLASSDEFAULT)
  {
    return mpMaster->mNPCFaction;
  }

  if (mpClass && mpClass->mNPCFaction != NPCFACTION_CLASSDEFAULT)
  {
    return mpClass->mNPCFaction;
  }

  return NPCFACTION_MONSTER;
}

bool                        
cStatsCharacter::HasCondition(const char *name)
{
  return mConditionRules.Contains(name);
}

void                        
cStatsCharacter::_PerformAddCondition(const char* name,
                                      tGameID spellID,
                                      tGameObjectID sourceID,
                                      tGameID itemMasterID,
                                      float duration,
                                      tGameID effectID,
                                      int param1,
                                      int param2)
{
  if (spellID != ID_NONE)
  {
    const cSpellMaster* pSpell = mOwner->GetTitan()->GetDatabaseSys()->GetSpellMaster(spellID);
    SyAssert(pSpell!=NULL);
    if (pSpell && pSpell->mbBlockable)
    {
      cGraphicCharacter* pCharGraphic = static_cast<cGraphicCharacter*>(mOwner->GetGraphic());

      if (pCharGraphic && pCharGraphic->GetAnimController()->IsBlocking())
      {
        return;
      }
    }
  }

  cRule_Condition* pRule = mOwner->GetTitan()->GetRuleSys()->CreateCondition(name, duration, param1, param2);
  SyAssert(pRule != NULL);
  if (pRule != NULL)
  {
    pRule->SetSourceID(sourceID);
    pRule->SetSpellID(spellID);
    pRule->SetEffect(effectID);
    pRule->SetItemMasterID(itemMasterID);
    mConditionRules.Add(pRule);
  }
}

void
cStatsCharacter::_PerformRemoveCondition(const char *name, tGameID spellID, tGameObjectID sourceID, tGameID itemMasterID)
{
  mConditionRules.Remove(name, spellID, sourceID, itemMasterID);
}

void
cStatsCharacter::_PerformRemoveConditions(tGameID spellID, tGameObjectID sourceID, tGameID itemMasterID)
{
  mConditionRules.RemoveAll(spellID, sourceID, itemMasterID);
}

bool
cStatsCharacter::HasAbilityCombo(eComboType combo)
{
  if (mOwner->GetType() == cGameObject::OBJ_NPC ||
      COMBO_L == combo ||
      COMBO_RANGEDATTACK == combo ||
      COMBO_JUMPATTACK == combo ||
      mOwner->GetTitan()->GetUnlockAllAbilities())
  {
    return true;
  }

  cDatabaseSys* pDB = mOwner->GetTitan()->GetDatabaseSys();
  const cAbilityMaster* pAbility;

  SyAssert(mAbilityIDs.Size() == mAbilityRanks.Size());

  for (int i=mAbilityIDs.Begin(); i!=mAbilityIDs.End(); i=mAbilityIDs.Next(i))
  {
    pAbility = pDB->GetAbilityMaster(mAbilityIDs(i), mAbilityRanks(i));
    if (pAbility && pAbility->mCombo == combo)
    {
      return true;
    }
  }

  return false;
}

int
cStatsCharacter::GetNumAvailableAbilities()
{
  int numAvailable = 0;

  const SyMap<tGameID, cDatabaseSys::AbilityVector>& abilityMap = mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMap();
  const cAbilityMaster* pAbility;
  bool bHasAbility;

  for (int i=abilityMap.Begin(); i!=abilityMap.End(); i=abilityMap.Next(i))
  {
    for (int j=abilityMap(i).Begin(); j!=abilityMap(i).End(); j=abilityMap(i).Next(j))
    {
      pAbility = abilityMap(i)(j);

      if (mpClass->mName.GetID() != pAbility->mClass)
      {
        continue;
      }

      if (mpLevel->mLevel < pAbility->mLevelMin)
      {
        continue;
      }

      bHasAbility = false;
      for (int k=mAbilityIDs.Begin(); k!=mAbilityIDs.End() && !bHasAbility; k=mAbilityIDs.Next(k))
      {
        if (pAbility->mID.GetID() == (tGameID)mAbilityIDs(k) &&
            pAbility->mRank == (int)mAbilityRanks(k))
        {
          bHasAbility = true;
        }
      }

      if (!bHasAbility)
      {
        ++numAvailable;
        break;
      }
    }
  }

  return numAvailable;
}

void
cStatsCharacter::GetAvailableAbility(int index, tGameID& abilityID, int& rank)
{
  int numAvailable = 0;

  const SyMap<tGameID, cDatabaseSys::AbilityVector>& abilityMap = mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMap();
  const cAbilityMaster* pAbility;
  bool bHasAbility;

  abilityID = ID_NONE;
  rank = 0;

  for (int i=abilityMap.Begin(); i!=abilityMap.End(); i=abilityMap.Next(i))
  {
    for (int j=abilityMap(i).Begin(); j!=abilityMap(i).End(); j=abilityMap(i).Next(j))
    {
      pAbility = abilityMap(i)(j);

      if (mpClass->mName.GetID() != pAbility->mClass)
      {
        continue;
      }

      if (mpLevel->mLevel < pAbility->mLevelMin)
      {
        continue;
      }

      bHasAbility = false;
      for (int k=mAbilityIDs.Begin(); k!=mAbilityIDs.End() && !bHasAbility; k=mAbilityIDs.Next(k))
      {
        if (pAbility->mID.GetID() == (tGameID)mAbilityIDs(k) &&
            pAbility->mRank == (int)mAbilityRanks(k))
        {
          bHasAbility = true;
        }
      }

      if (!bHasAbility)
      {
        if (index == numAvailable)
        {
          abilityID = pAbility->mID.GetID();
          rank = pAbility->mRank;
          return;
        }

        ++numAvailable;
        break;
      }
    }
  }
}

bool
cStatsCharacter::BuyAbility(tGameID abilityID, int rank)
{
  const cAbilityMaster* pAbility = mOwner->GetTitan()->GetDatabaseSys()->GetAbilityMaster(abilityID, rank);

  if (!pAbility)
  {
    return false;
  }

  cIntelPlayer* pPlayerIntel = prop_cast<cIntelPlayer*>(mOwner->GetIntel());

  int existingAbilityIndex = -1;
  for (int k=mAbilityIDs.Begin(); k!=mAbilityIDs.End() && existingAbilityIndex == -1; k=mAbilityIDs.Next(k))
  {
    if (abilityID == (tGameID)mAbilityIDs(k))
    {
      existingAbilityIndex = k;
    }
  }

  if (!(existingAbilityIndex >= 0 && ((int)mAbilityRanks(existingAbilityIndex)) == rank) &&
      pAbility->mCost <= mAbilityPoints)
  {
    mAbilityPoints -= pAbility->mCost;
    if (existingAbilityIndex >= 0)
    {
      mAbilityRanks(existingAbilityIndex) = ((uint32)rank);
    }
    else
    {
      mAbilityIDs.Add((uint32)abilityID);
      mAbilityRanks.Add((uint32)rank);
    }

    if (pPlayerIntel)
    {
      pPlayerIntel->AutoMapAbility(pAbility);
    }
  }

  return true;
}

void
cStatsCharacter::Shoot(const SyVect3& targetPos)
{
  cPhysicsProjectile::Shoot(GetProjectileTypeID(), mOwner, targetPos);
}

void
cStatsCharacter::Shoot(const SyVect3& start, float heading, float pitch, float dist)
{
  SyVect3 targetPos = start;

  targetPos.X += SY_SIN(heading) * dist;
  targetPos.Y += SY_SIN(pitch) * dist;
  targetPos.Z += SY_COS(heading) * dist;

  Shoot(targetPos);
}


void                        
cStatsCharacter::Shoot(cGameObject *pTarget) // target could be NULL for shooting in random direction.
{
  SyAssertf(pTarget!=NULL, "cStatsCharacter::Shoot(cGameObject *) passed in null target - no longer valid, use Shoot(SyVect3)");

  if (pTarget != NULL)
  {
    cPhysicsProjectile::Shoot(GetProjectileTypeID(), mOwner, pTarget->GetLocation(), pTarget);
  }
}

cStats::eActivateType       
cStatsCharacter::GetActivateType() const
{  
  return mActivateType;
}


void                
cStatsCharacter::SetActivateType(cStats::eActivateType type)
{
  mActivateType = type;
}


void                
cStatsCharacter::T4_SetContext(char8 slot)
{
  if (mProperName.AsChar()[0] != '\0')
  {
    mOwner->GetTitan()->T4SetFinal(slot,mProperName.AsChar());
    return;
  }
  static const int unknown = SyHashResourceID("CHARACTER_UNKNOWN");
  uint32 id = mpMaster->mNameString;
  if (id == 0)
  {
    mOwner->GetTitan()->T4SetContext( slot,unknown);
    return;
  }
  int result = mOwner->GetTitan()->T4SetContext( slot,id);
  if (result < 0)
  {
    mOwner->GetTitan()->T4SetContext( slot,unknown);
  }
}

const char *        
cStatsCharacter::GetScriptName()
{
  return mpMaster->mScriptName;
}

void cStatsCharacter::SetHitScriptCallback( const char* callback )
{
  mScriptCallback_onHit.Init( callback );
}

void cStatsCharacter::SetDeathScriptCallback( const char* callback )
{
  mScriptCallback_onDeath.Init( callback );
}

//------------------------------------ cStatsPropMaster

cStatsPropMaster::cStatsPropMaster()
: mMaxHealth(0),
  mModelDataID(ID_NONE),
  mAnimSet(0),
  mTreasureSet(0),
  mMobility(PROP_IMMOBILE),
  mWeight(0.0f),
  mThrownDamageMultiplier(1.0f),
  mNumThrows(1),
  mThrowSpellID(ID_NONE),
  mSpeedThreshold(5.0),
  mScriptName(NULL),
  mT4_NameID(0),
  mScale(1.0f),
  mActivateRadius(2.0f),
  mFaction(NPCFACTION_PROP),
  mPhysicsMass(-1.0f),
  mPhysicsFriction(0.8f)
{
}

cStatsPropMaster::~cStatsPropMaster()
{
  delete [] mScriptName;
  mScriptName = NULL;
}

//------------------------------------ cStatsProp
cStatsProp::cStatsProp()
: mpMaster(NULL),
  mCurDamage(0),
  mCurState(0),
  mbDead(false),
  mActivateType(ACTIVATE_NONE)
{
  InitPropObject( mCLASSID );
}

int           
cStatsProp::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cStats::mCLASSID,
               mCLASSID,
               "cStatsProp", 
               Creator, 
               mCLASSID, 
               0 ); 

  AddStringProperty(mCLASSID,PropId_MasterName,SyMemberOffset(cStatsProp,mMasterName),"mMasterName");
  AddInt32Property(mCLASSID,PropId_CurDamage,SyMemberOffset(cStatsProp,mCurDamage),"mCurDamage");
  AddBoolProperty(mCLASSID,PropId_Dead,SyMemberOffset(cStatsProp,mbDead),"mbDead");


  SyPropEnum *propEnum;
  AddEnumProperty(mCLASSID,PropId_ActivateType,SyMemberOffset(cStatsProp,mActivateType),"mActivateTarget",&propEnum);
  propEnum->Add(ACTIVATE_NONE,"ACTIVATE_NONE");
  propEnum->Add(ACTIVATE_OPEN,"ACTIVATE_OPEN");
  propEnum->Add(ACTIVATE_TALKTO,"ACTIVATE_TALKTO");
  propEnum->Add(ACTIVATE_IGNITE,"ACTIVATE_IGNITE");
  propEnum->Add(ACTIVATE_TURN,"ACTIVATE_TURN");
  propEnum->Add(ACTIVATE_FLIP,"ACTIVATE_FLIP");
  propEnum->Add(ACTIVATE_PICKUP,"ACTIVATE_SAVEGAME");

  AddStringProperty(mCLASSID,PropId_ModelOverride,SyMemberOffset(cStatsProp,mModelOverride),"mModelOverrideName");

  return 0;
}

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

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

  return(pObject);
}

void                
cStatsProp::Init()
{
  SetMaster(mMasterName.AsChar());

  if (IsLiftable())
  {
    SetActivateType(ACTIVATE_PICKUP);
  }
  else if (IsPushable())
  {
    SetActivateType(ACTIVATE_PUSH);
  }
}


void cStatsProp::SetOwner(cGameObject *owner)
{
  mOwner = owner;
  mConditions.SetTitan(mOwner->GetTitan());
  mConditions.SetOwnerID(mOwner->GetID());
}
                                 
void cStatsProp::Reset()
{
  cStats::Reset();

  mCurDamage = 0;
  mbDead = false;
  mOwner->GetPhysics()->SetCollideable(true);
}

void                
cStatsProp::Update(float time)
{
  mConditions.Update(time);
}

void                        
cStatsProp::SetMaster(const char *master_name)
{
  SyAssert(mOwner);
  SyAssert(mOwner->GetTitan());

  cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
  SyAssert(db);

  tGameID id = SyHashResourceID(master_name);
  mpMaster = db->GetPropMaster(id);
  GAME_ASSERT(ERROR_DESIGN, mpMaster!=NULL,"Unknown prop master '%s' for object '%s'",master_name,mOwner->GetName()); 

  if (!mpMaster)
  {
    mpMaster = db->GetPropMaster(SyHashResourceID("GenericBarrel"));
  }

  if (mpMaster)
  {
    mMasterName.Init(mpMaster->mID.GetName());
  }

  // ensure all related esfs are loaded at level start/object creation
  if (mpMaster)
  {
    const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);
    if (pModelData)
    {
      pModelData->Load(mOwner);
    }
  }
}

const char*
cStatsProp::GetModelName()
{
  if (mModelOverride.Length() > 0)
  {
    return mModelOverride.AsChar();
  }
   
  const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

  if (pModelData)
  {
    return pModelData->mModelName;
  }

  return NULL;
}

float             
cStatsProp::GetModelScale()
{
  if (mpMaster == NULL)
  {
    SyAssert(0); // Set master before calling this function
    return 1.0f;
  }

  return mpMaster->mScale;
}

const char*
cStatsProp::GetFileName()
{
  if (mModelOverride.Length() > 0)
  {
    return GetModelOverride();
  }

  const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

  if (pModelData)
  {
    return pModelData->mFileName;
  }

  return NULL;
}

float cStatsProp::GetActivateRadius() const
{
  return ( mpMaster ? mpMaster->mActivateRadius : 2.0f );
}

void cStatsProp::SetHitScriptCallback( const char* callback )
{
  mScriptCallback_onHit.Init( callback );
}

void cStatsProp::SetDeathScriptCallback( const char* callback )
{
  mScriptCallback_onDeath.Init( callback );
}

void
cStatsProp::ApplyDamage(cDamagePacket *packet)
{
  packet->mPacketType = cRulePacket::EVENT_APPLY_DAMAGE;
  ProcessRulePacket(packet);

//  cAnimControllerInput *input =((cGraphicCharacter *) mOwner->GetGraphic())->GetAnimInput();

  cGameObject* pAttacker = mOwner->GetRegistry()->Fetch(packet->mAttackerID);
  cPhysicsProp *pPhys = prop_cast<cPhysicsProp *>(mOwner->GetPhysics());
  if (pPhys != NULL &&
      pAttacker != NULL &&
      packet->mRecoveryTime > 0.0f)
  {
    SyVect3 towards;
    towards.Sub(mOwner->GetLocation(),pAttacker->GetLocation());
    towards.Y = 0.0f;
    towards.Normalize();
    towards *= l_PROP_IMPACT_FORCE;
    pPhys->Impulse(towards, SY_DEG_TO_RAD(l_PROP_IMPACT_ROTATE_SPEED));
  }

  if (CalcMaxHealth() == 0)
  {
    return;
  }

  //cGameObjectRegistry *pRegistry = mOwner->GetRegistry();

  static const int32 ID_LastAgentID = SyHashResourceID("LAST_DAMAGE_AGENT");
  static const int32 ID_LastDamageAmount = SyHashResourceID("LAST_DAMAGE_AMOUNT");
  static const int32 ID_LastDamageType = SyHashResourceID("LAST_DAMAGE_TYPE");
  static const int32 ID_LastDamageSpellID = SyHashResourceID("LAST_DAMAGE_SPELLHASH");
  static const int32 ID_LastDamageCombo = SyHashResourceID("LAST_DAMAGE_COMBO");

  Value_Set(ID_LastAgentID,packet->mAgentID);
  Value_Set(ID_LastDamageAmount,packet->GetTotal());
  Value_Set(ID_LastDamageType,packet->GetDamageType());
  Value_Set(ID_LastDamageSpellID, packet->mSpellID);
  Value_Set(ID_LastDamageCombo, packet->mCombo);

  mOwner->GetTitan()->GetScriptSys()->ScriptEvent(PET_HIT,mOwner->GetID(),packet->mAttackerID);

  packet->SetTotal(Value_Get(ID_LastDamageAmount),"SCRIPT","SCRIPT");
  int newDamageType = Value_Get(ID_LastDamageType);
  if (newDamageType >= 0 && newDamageType < NUM_DAMAGE_TYPES)
  {
    packet->SetDamageType((eDamageType)newDamageType);
  }

  SyAssertf(!packet->mbBlocked, "Props can't block!");
  mCurDamage += packet->GetTotal();

  if (mCurDamage >= CalcMaxHealth())
  {
    Die(packet->mAttackerID);
  }
  else
  {
    // notify AI of attack 
    mOwner->GetIntel()->OnHit(packet->mAttackerID, packet->GetTotal(), 0.0f, packet->mbRanged);
  }


  // network update
  cNetDamagePacket netpacket;
  netpacket.mTotalDamage = mCurDamage;
  netpacket.mAttacker = packet->mAttackerID;
  netpacket.mDeath = mbDead;
//  netpacket.mHitReact = input->mHitReact;
//  netpacket.mHitReactTime = input->mHitReactTime;

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

void                
cStatsProp::Die(tGameObjectID killer)
{
  if (!mbDead)
  {
    mCurDamage = CalcMaxHealth();

    mbDead = true;

    cGameObject *killer_obj = mOwner->GetRegistry()->Fetch(killer);

    if (mOwner->IsLocal())
    {
      mOwner->GetTitan()->GetScriptSys()->ScriptEvent(PET_DEATH,mOwner->GetID(),killer);
      cStatsProp* pStats = static_cast<cStatsProp*>(mOwner->GetStats());

      if (pStats->GetMaster()->mTreasureSet != ID_NONE)
      {
        cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
        cTreasureSet *treasureset = db->GetTreasureSet(GetMaster()->mTreasureSet);

        if (treasureset != NULL)
        {
          SyVect3 location = mOwner->GetLocation();
          location.Y += 1.5f; 
          treasureset->Drop(mOwner, location, killer_obj);
        }
        else
        {
          GAME_ASSERT(ERROR_DESIGN, false,"Unknown treasure set for prop '%s'",mOwner->GetName());
        }    
      }
    }

    // notify AI of death
    mOwner->GetIntel()->OnDeath();

    const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

    if (pModelData)
    {
      if (pModelData->Die(mOwner, killer_obj))
      {
        cAreaEffect_Radius* pRadius = new cAreaEffect_Radius();
        pRadius->SetRadius(3.0f);
        pRadius->SetLocation(mOwner->GetLocation());
        cGameEffect_Nudge *pNudge = new cGameEffect_Nudge();
        pRadius->AddGameEffect(pNudge);
        pRadius->Update(1.0f);
        delete pRadius;

        // something just got broken so shake the camera just a little for a very short time
        mOwner->GetTitan()->GetCameraController()->Shake( 0.0f, 0.1f, 20.0f, 0.25f );
      }
    }
  }
}

void
cStatsProp::ProcessRulePacket(cRulePacket *packet)
{
  packet->mObjectID = mOwner->GetID();

  LogPacketStart(packet,mOwner->GetTitan());
  mOwner->GetTitan()->GetGlobalPropRules()->ProcessPacket(packet);
  mConditions.ProcessPacket(packet);
  LogPacketEnd(packet,mOwner->GetTitan());
}

int
cStatsProp::GetHealth()
{
  return CalcMaxHealth() - mCurDamage;
}

int
cStatsProp::CalcMaxHealth()
{
  SyAssert(mpMaster);

  return mpMaster->mMaxHealth;
}

bool
cStatsProp::NetworkReceiveBroadcast(const char *packet,int size)
{
  cNetPacket::eNetPacketType type;

  if (cStats::NetworkReceiveBroadcast(packet,size))
  {
    return true;
  }

  SyAssertf(mOwner->IsRemote(),"Network Broadcast on local object?");

  SyPack::Unpack8u(packet, &type);   

  if (type != cNetPacket::NET_DAMAGE)
  {
    return false;
  }

  cNetDamagePacket pPacket;
  pPacket.UnpackBuffer(packet, size);

  SyAssertf(mOwner->IsRemote(), "Owner receiving network instructions?"); 
  SyAssertf(pPacket.mType == cNetPacket::NET_DAMAGE, "Unknown packet");

  mCurDamage = pPacket.mTotalDamage;

  //cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(mOwner->GetGraphic());
  //SyAssertf(pCharGraphic!=NULL, "Bad object construction: no graphic?");

  //cAnimCharControllerInput *input = pCharGraphic->GetAnimInput();
  //SyAssertf(input!=NULL,"Bad object construction: no input?");

  if (pPacket.mDeath)
  {
    Die(pPacket.mAttacker);
  }

  // todo add state transition
  return true;
}

bool
cStatsProp::NetworkHasAuthorityOver(cGameObject *obj)
{
  return !obj->IsRemote();
}

bool                        
cStatsProp::HasCondition(const char *name)
{
  return mConditions.Contains(name);
}

void                        
cStatsProp::_PerformAddCondition(const char* name,
                                 tGameID spellID,
                                 tGameObjectID sourceID,
                                 tGameID itemMasterID,
                                 float duration,
                                 tGameID effectID,
                                 int param1,
                                 int param2)
{
  cRule_Condition* pRule = mOwner->GetTitan()->GetRuleSys()->CreateCondition(name, duration, param1, param2);
  SyAssert(pRule != NULL);
  if (pRule != NULL)
  {
    pRule->SetSourceID(sourceID);
    pRule->SetSpellID(spellID);
    pRule->SetEffect(effectID);
    pRule->SetItemMasterID(itemMasterID);
    mConditions.Add(pRule);
  }
}

void
cStatsProp::_PerformRemoveCondition(const char *name, tGameID spellID, tGameObjectID sourceID, tGameID itemMasterID)
{
  mConditions.Remove(name, spellID, sourceID, itemMasterID);
}


void
cStatsProp::_PerformRemoveConditions(tGameID spellID, tGameObjectID sourceID, tGameID itemMasterID)
{
  mConditions.RemoveAll(spellID, sourceID, itemMasterID);
}

const char*         
cStatsProp::GetModelOverride()
{
  if (mModelOverride.AsChar()[0] == '\0')
  {
    return "";
  }

  static const char *path = "game_assets\\art\\props\\";
  static const char *extension = ".esf";

  mModelOverrideFullPath.Clear();
  mModelOverrideFullPath += path;
  mModelOverrideFullPath += mModelOverride;
  mModelOverrideFullPath += extension;

  return mModelOverrideFullPath.AsChar();

};

void                
cStatsProp::T4_SetContext(char8 slot)
{
  static const int unknown = SyHashResourceID("OBJECT_UNKNOWN");
  uint32 id = mpMaster->mT4_NameID;
  if (id == 0)
  {
    mOwner->GetTitan()->T4SetContext( slot,unknown);

    //mOwner->GetTitan()->T4SetFinal(slot,"Object");

    return;
  }
  int result = mOwner->GetTitan()->T4SetContext( slot,id);
  if (result < 0)
  {
    mOwner->GetTitan()->T4SetContext( slot,unknown);
  }

}
//------------------------------------ cStatsItem

cStatsItem::cStatsItem() : 
  mpItem(NULL)
{
  mpItem = new cItem();
  InitPropObject( STATSITEM_CLASSID );
};

cStatsItem::~cStatsItem()
{
  delete mpItem;
}

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

  AddSubClass( mCLASSID, 
               cStats::mCLASSID,
               mCLASSID,
               "cStatsItem", 
               Creator, 
               mCLASSID, 
               0 ); 

  AddSubObjectPtrProperty(mCLASSID,0x1001,SyMemberOffset(cStatsItem,mpItem),"Item");
  return 0;
}

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

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

  return(pObject);
}

void                
cStatsItem::Init()
{
  mOwner->GetPhysics()->SetCollideable(false);
  cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
  SyAssert(db);
  if (mpItem != NULL)
  {
    mpItem->Init(db);
  }
}

void                
cStatsItem::Update(float time)
{
}

void                        
cStatsItem::CreateItem(const char *master_name)
{
  cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
  SyAssert(db);
  tGameID id = SyHashResourceID(master_name);
  const cStatsItemMaster *master= db->GetItemMaster(id);
  if (master == NULL)
  {
    // todo:  Let designers know they have bad data...
    return;
  };

  delete mpItem;
  mpItem = new cItem();
  SyAssert(mOwner);
  SyAssert(mOwner->GetTitan());

  mpItem->SetMaster(master);
}

void                        
cStatsItem::SetItem(cItem *item) // transfers ownership
{
  delete mpItem;
  SyAssertf(item!=NULL,"Bad item");
  mpItem = item;
}

cItem *
cStatsItem::TakeItem()
{
  cItem *cur = mpItem;
  mpItem = NULL;

  if (mOwner->IsLocal())
  {
    cGameObjectRegistry *reg = mOwner->GetRegistry();
    reg->Delete(mOwner->GetID());
  }

  return cur;
}


void                
cStatsItem::SetMaster(const char *master_name)
{
  if (mpItem == NULL)
  {
    CreateItem(master_name);
    return;
  }

  tGameID id = SyHashResourceID(master_name);
  SyAssert(mOwner);
  SyAssert(mOwner->GetTitan());
  cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
  SyAssert(db);
  const cStatsItemMaster *master= db->GetItemMaster(id);
  if (master == NULL)
  {
    GAME_ASSERT(ERROR_DESIGN, false, "Could find item master '%s' to create item", master_name);
    return;
  }

  mpItem->SetMaster(master);
}

const char *        
cStatsItem::GetModelName()
{
  GAME_ASSERT(ERROR_CODE, mpItem!=NULL, "Uninitialized Item");

  if (mpItem == NULL)
  {
    return NULL;
  }

  const cStatsItemMaster *itemmaster = mpItem->GetMaster();

  GAME_ASSERT(ERROR_CODE, itemmaster!=NULL, "Uninitialized Item Master");

  if (itemmaster == NULL)
  {
    return NULL;
  }

  return itemmaster->mWorldModel;
}

const char *        
cStatsItem::GetFileName()
{
  GAME_ASSERT(ERROR_CODE, mpItem!=NULL, "Uninitialized Item");

  if (mpItem == NULL)
  {
    return NULL;
  }

  const cStatsItemMaster *itemmaster = mpItem->GetMaster();
  
  GAME_ASSERT(ERROR_CODE, itemmaster!=NULL, "Uninitialized Item Master");

  if (itemmaster == NULL)
  {
    return NULL;
  }

  return itemmaster->mFileName;
}


void 
cStatsItem::_PerformActivate(cGameObject *actor)
{
  cStats::_PerformActivate(actor);
  cInventoryCharacter *inv_char;

  inv_char = prop_cast<cInventoryCharacter *>(actor->GetInventory());
  inv_char->PickUp(mOwner);
}

const char *        
cStatsItem::GetMasterName() const
{
  return ( mpItem ? mpItem->GetMasterName() : "Unknown" );
}


void                
cStatsItem::T4_SetContext(char8 slot)
{
  mpItem->T4_SetContext_Name(mOwner,slot);
}
//------------------------------------ cStatsProjectileMaster


cStatsProjectileMaster::cStatsProjectileMaster() :
  mModelDataID(ID_NONE),
  mSpeed(10.0f),
  mSeeking(0.0f),
  mGravity(1.0f),
  mSpins(false),
  mPenetrates(false),
  mPinCushion(false),
  mCollisionRadius(0.1f),
  mNum(1),
  mMaxArc(10.0f),
  mAcceleration(0.0f),
  mOrbiting(0.0f),
  mMeteor(0.0f),
  mVerticalWave(0.0f),
  mDamageMultiplier(0.0f),
  mHomingRange(15.0f),
  mCreationFXID(ID_NONE),
  mAttachFXID(ID_NONE),
  mImpactFXSetID(ID_NONE),
  mImpactSoundSetID(ID_NONE)
{
}

cStatsProjectileMaster::~cStatsProjectileMaster()
{
}

//------------------------------------ cStatsProjectile

cStatsProjectile::cStatsProjectile()
:mMasterID(ID_NONE),
 mpMaster(NULL),
 mbDead(false)
{
  InitPropObject( mCLASSID );
}

cStatsProjectile::~cStatsProjectile()
{
}
  
  /* Property Class Support */
int                  
cStatsProjectile::InitPropClass()
{
/* Add the class */

  AddSubClass( mCLASSID, 
               cStats::mCLASSID,
               mCLASSID,
               "cStatsProjectile", 
               Creator, 
               mCLASSID, 
               0 ); 

  AddInt32Property(mCLASSID,PropId_MasterID,SyMemberOffset(cStatsProjectile,mMasterID),"mMasterID");

  return 0;
}

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

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

  return(pObject);
}


void                
cStatsProjectile::Init()
{
  if (mMasterID != NULL)
  {
    SetMaster(mMasterID);
  }

  // ensure all related esfs are loaded at level start/object creation
  if (mpMaster)
  {
    const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);
    if (pModelData)
    {
      pModelData->Load(mOwner);
    }
  }
}

void                
cStatsProjectile::SetMaster(tGameID id)
{
  SyAssert(mOwner);
  SyAssert(mOwner->GetTitan());

  cDatabaseSys *db = mOwner->GetTitan()->GetDatabaseSys();
  SyAssert(db);

  mpMaster = db->GetProjectileMaster(id);
  GAME_ASSERT(ERROR_DESIGN, mpMaster!=NULL,"Unknown projectile master"); 
  mMasterID = id;
}

const char *        
cStatsProjectile::GetModelName()
{
  if (mpMaster == NULL)
  {
    SyAssert(0); // Set master before calling this function
    return NULL;
  }

  const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

  if (pModelData)
  {
    return pModelData->mModelName;
  }

  return NULL;
}

const char *        
cStatsProjectile::GetFileName()
{
  if (mpMaster == NULL)
  {
    SyAssert(0); // Set master before calling this function
    return NULL;
  }

  const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

  if (pModelData)
  {
    return pModelData->mFileName;
  }

  return NULL;
}
  
void                
cStatsProjectile::Die(tGameObjectID killer)
{
  if (!mbDead)
  {
    mbDead = true;

//    mOwner->GetTitan()->GetScriptSys()->ScriptEvent(PET_DEATH,mOwner->GetID(),killer);

    // notify AI of death
    mOwner->GetIntel()->OnDeath();

    cPhysicsProjectile* pPhysics = static_cast<cPhysicsProjectile*>(mOwner->GetPhysics());
    pPhysics->SetCollideable(false);

    const cStatsModelData* pModelData = mOwner->GetTitan()->GetDatabaseSys()->GetModelData(mpMaster->mModelDataID);

    if (pModelData)
    {
      pModelData->Die(mOwner, NULL);
    }
  }     
}

//----------------------------------------------------------------------------------- cStatsMarker

cStatsMarker::cStatsMarker() :
mWidth(1.0f),
mHeight(20.0f),
mLength(1.0f),
mTime(0.0f)
{
  InitPropObject( mCLASSID );
}

cStatsMarker::~cStatsMarker()
{
}

int                  
cStatsMarker::InitPropClass()
{
  AddSubClass( mCLASSID, 
               cStats::mCLASSID,
               mCLASSID,
               "cStatsMarker", 
               Creator, 
               mCLASSID, 
               0 ); 

  AddFloat32Property(mCLASSID,PropId_Width,SyMemberOffset(cStatsMarker,mWidth),"mWidth");
  AddFloat32Property(mCLASSID,PropId_Height,SyMemberOffset(cStatsMarker,mHeight),"mHeight");
  AddFloat32Property(mCLASSID,PropId_Length,SyMemberOffset(cStatsMarker,mLength),"mLength");

  AddPropertyAnnotation( mCLASSID, PropId_Width, "min", "0.5" );
  AddPropertyAnnotation( mCLASSID, PropId_Width, "max", "5.0" );
  AddPropertyAnnotation( mCLASSID, PropId_Height, "min", "0.5" );
  AddPropertyAnnotation( mCLASSID, PropId_Height, "max", "20.0" );
  AddPropertyAnnotation( mCLASSID, PropId_Length, "min", "0.5" );
  AddPropertyAnnotation( mCLASSID, PropId_Length, "max", "5.0" );

  AddStringProperty(mCLASSID,PropId_Next,SyMemberOffset(cStatsMarker,mNext),"mNext");
  AddStringProperty(mCLASSID,PropId_Branch,SyMemberOffset(cStatsMarker,mBranch),"mBranch");

  AddFloat32Property(mCLASSID,PropId_Time,SyMemberOffset(cStatsMarker,mTime),"mTime");
  AddPropertyAnnotation( mCLASSID, PropId_Time, "min", "0.0" );
  AddPropertyAnnotation( mCLASSID, PropId_Time, "max", "15.0" );
  return 0;
}

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

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

  return(pObject);
}

void cStatsMarker::ProcessRulePacket(cRulePacket *packet)
{
  packet->mObjectID = mOwner->GetID();

  LogPacketStart(packet, mOwner->GetTitan());
  mOwner->GetTitan()->GetGlobalMarkerRules()->ProcessPacket(packet);
  LogPacketEnd(packet, mOwner->GetTitan());
}

SyVect3                     
cStatsMarker::GetMin()
{
  SyVect3 ans;
  ans = mOwner->GetLocation();
  ans.X -= mWidth * 0.5f;
  ans.Y -= 0.5f; // fudge
  ans.Z -= mLength * 0.5f;

  return ans;
}

SyVect3                     
cStatsMarker::GetMax()
{
  SyVect3 ans;
  ans = mOwner->GetLocation();
  ans.X += mWidth * 0.5f;
  ans.Y += mHeight;
  ans.Z += mLength * 0.5f;

  return ans;
}

tGameID                     
cStatsMarker::GetNext()
{
  cGameObject *obj = mOwner->GetRegistry()->Fetch(mNext.AsChar());
  if (obj != NULL)
  {
    GAME_ASSERT(ERROR_DESIGN, obj != mOwner, "Marker '%s' has mNext field that points to itself", mOwner->GetName());

    if (obj != mOwner)
    {
      return obj->GetID(); 
    }
  }

  return 0;
}

tGameID                     
cStatsMarker::GetBranch()
{
  return SyHashResourceID(mBranch.AsChar()); 
}

//----------------------------------------------------------------------------------- cStatsDummy

cStatsDummy::cStatsDummy()
{
  InitPropObject( mCLASSID );
}

cStatsDummy::~cStatsDummy()
{
}

int                  
cStatsDummy::InitPropClass()
{
  AddSubClass( mCLASSID, 
    cStats::mCLASSID,
    mCLASSID,
    "cStatsDummy", 
    Creator, 
    mCLASSID, 
    0 ); 

  return 0;
}

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

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

  return(pObject);
};


//-------------------------------------------------------------cStatsModelData

cStatsModelData::cStatsModelData()
: mID(0),
  mType(MODELDATA_CHARACTER),
  mModelName(NULL),
  mFileName(NULL),
  mSoundName(NULL),
  mRagdollName(NULL),
  mDeathFXID(ID_NONE),
  mSummonFXID(ID_NONE),
  mDebrisID(ID_NONE),
  mDefaultAnimSetID(ID_NONE),
  mSoundSurfaceType(CHARACTER_SOUND_SURFACE_FLESH)
{
}

cStatsModelData::~cStatsModelData()
{
  delete [] mModelName;
  mModelName = NULL;
  delete [] mFileName;
  mFileName = NULL;
  delete [] mSoundName;
  mSoundName = NULL;
  delete [] mRagdollName;
  mRagdollName = NULL;
}

bool cStatsModelData::Die(cGameObject* pOwner, cGameObject* pKiller) const
{
  bool bDestroyed = false;

  if (ID_NONE != mDebrisID)
  { 
    SyVect3 force(0.0f, 0.0f, 0.0f);
      
    if (pKiller)
    {
      SyVect3 towards;
      towards.Sub(pOwner->GetLocation(), pKiller->GetLocation());
      towards.Normalize();
      force.MulAdd(towards, l_DEBRIS_IMPACT_FORCE);
    }

    force.MulAdd(pOwner->GetPhysics()->GetVelocity(), 0.1f);

    pOwner->GetTitan()->GetDebrisSys()->Spawn(pOwner, force, mDebrisID);

    // make invisible
    pOwner->GetGraphic()->SetVisible(false);    

    bDestroyed = true;
  }

  if (ID_NONE != mDeathFXID)
  {
    SyScene* pScene = pOwner->GetTitan()->GetScene();
    int scriptHandle = -1;

    if (pScene->GetDictionary()->FindTyped(mDeathFXID, SYRESOURCETYPE_FXSCRIPT, scriptHandle))
    {
      pScene->GetFXScriptSystem()->PlayScriptAtLocation(scriptHandle, pOwner->GetLocation(), pOwner->GetHPR(), 1.0f);
    }
    else
    {
      GAME_ASSERT(ERROR_DESIGN, false, "Could not find death fx for model data '%s' in database", mID.GetName());
    }

    bDestroyed = true;
  }

  if (mSoundName && mSoundName[0] != 0)
  {
    pOwner->GetTitan()->GetSoundMgr()->Play3D(pOwner->GetID(), mSoundName, 1, pOwner->GetLocation(), 7.0f);
  }

  return bDestroyed;
}

bool cStatsModelData::Load(cGameObject* pOwner) const
{
  // force load of assets to get put into wad file

  bool bSuccessful = true;

  if (mFileName && mFileName[0] != 0)
  {
    if (-1 == pOwner->GetTitan()->LoadAssetSprite(pOwner->GetTitan()->GetTitanUI()->GetESFParser(), mFileName))
    {
      bSuccessful = false;
    }
  }

#ifdef HAVOK_ENABLED
  if (mRagdollName && mRagdollName[0] != 0)
  {
    if (SyHavok::GetEnableRagdoll())
    {
      // load the ragdoll master
      if (-1 == pOwner->GetTitan()->GetScene()->GetCollideDev()->GetHavok()->LoadRagdollMaster( mRagdollName ) )
      {
        bSuccessful = false;
      }
    }
  }
#endif

  if (mSoundName && mSoundName[0] != 0)
  {
    if (-1 == pOwner->GetTitan()->LoadAssetSound(pOwner->GetTitan()->GetTitanUI()->GetESFParser(), mSoundName))
    {
      bSuccessful = false;
    }
  }

  if (ID_NONE != mDebrisID)
  {
    pOwner->GetTitan()->GetDatabaseSys()->LoadDebrisModels(mDebrisID);
  }

  return bSuccessful;
}

//----------------------------------------------------------------------------------- Global Function Defs
void 
RegPropClasses_Stats()
{
  RegPropClasses_Item();

  cStats::InitPropClass();
  cStatsCharacter::InitPropClass();
  cStatsProp::InitPropClass();
  cStatsItem::InitPropClass();            
  cStatsProjectile::InitPropClass();
  cStatsMarker::InitPropClass();
  cStatsDummy::InitPropClass();
}

void 
Stats_RegisterTuningVariables()
{
  gTuningSys.AddFloat(&l_DEBRIS_IMPACT_FORCE,"Debris_AttackForce");
  gTuningSys.AddFloat(&l_PROP_IMPACT_FORCE,"Prop_AttackForce");
  gTuningSys.AddFloat(&l_PROP_IMPACT_ROTATE_SPEED,"Prop_AttackRotateSpeed");
  gTuningSys.AddFloat(&l_CHARACTER_IMPACT_FORCE,"Character_AttackForce");
  gTuningSys.AddFloat(&l_CHARACTER_IMPACT_JUGGLE,"Character_AttackJuggle");
  gTuningSys.AddInt(&l_BonusPointsPerLevel, "Stats_BonusPointsPerLevel");
  gTuningSys.AddInt(&l_HealthPointsPerBonusPoint, "Stats_HealthPointsPerBonusPoint");
  gTuningSys.AddInt(&l_ManaPointsPerBonusPoint, "Stats_ManaPointsPerBonusPoint");
  gTuningSys.AddFloat(&l_ResetTime, "Stats_ResetTime");

  cDropList::RegisterTuningVariables();
}


// EOF
