/**************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 "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 "debris.h"
#include "tuning.h"

//---------------------------------------------- Class Declarations
//--------------------------------------------------------- Globals
//----------------------------------------- Functions Declarations
//----------------------------------------- Local Vars
static float l_IMPACT_FORCE = 2.0f;

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


//------------------------------------ cStats
cStats::cStats()
{
  InitPropObject( mCLASSID );
}

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

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

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

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

  return(pObject);
}


void                
cStats::GetActivateString(SyString *string)
{
  // TODO: LOCALIZE ME

  const char *format_strs[] = 
  {
    "", // none- prevent crash
    "Open %s",
    "Talk To %s",
    "Ignite %s",
    "Turn %s",
    "Flip %s",
    "Pick Up %s",
    "Save Game"
  };

  if (GetActivateType() == ACTIVATE_NONE)
  {
    return;
  }
  char buffer[512];

  SyString name;
  
  GetTranslatedName(&name);

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


void
cStats::GetTranslatedName(SyString *name)
{
  // TODO: Localize me!

  name->Init(GetMasterName());
}

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->mDefenderID = mOwner->GetID();
  pDamage->mPacketType = cRulePacket::CALC_DAMAGE_REDUCED;
  cDamagePacket initialDamage = *pDamage;

  ProcessRulePacket(pDamage);

  pDamage->mPacketType = cRulePacket::EVENT_HIT;
  ProcessRulePacket(pDamage);

  if (pDamage->mbBlocked)
  {
    ApplyDamage(pDamage);

    if (GetBlock() < 0)
    {
      // recalculate damage packet without strikethrough
      initialDamage.mbBlocked = false;
      initialDamage.mPacketType = cRulePacket::CALC_DAMAGE_REDUCED;
      ProcessRulePacket(&initialDamage);
      initialDamage.mPacketType = cRulePacket::EVENT_HIT;
      ProcessRulePacket(&initialDamage);
      ApplyDamage(&initialDamage);
    }
  }
  else
  {
    ApplyDamage(pDamage);
  }
}

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

cStatsCharacterMaster::cStatsCharacterMaster() :
  mModel(NULL),

  mNameString(NULL),
  mDescriptionString(NULL),
  mClass(NULL),
  mLevel(0),
  mAnimSet(0),
  mNaturalMelee(0),
  mNaturalRanged(0),
  mNaturalArmor(0),     
  mTreasureSet(0),
  mAttackDelay(1.0f),
  mBlockDelay(1.0f),
  mMovementSpeed(1.0f),
  mCloseDistance(1.0f),
  mAccuracy(1.0f),
  mCollisionRadius(0.5f),
  mActivationRadius(0.0f),
  mDefendRadius(0.0f),
  mNPCBehaviorType(0),
  mNPCFaction(0)
{
}

cStatsCharacterMaster::~cStatsCharacterMaster()
{
  delete [] mModel;
  delete [] mNameString;
  delete [] mDescriptionString;
}

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

cStatsCharacter::cStatsCharacter() :
  mpMaster(NULL),
  mpClass(NULL),
  mpLevel(NULL),
  mCurDamage(0),
  mCurPowerLoss(0),
  mCurBlockLoss(0),
  mHealthRegenFrac(0),
  mPowerRegenFrac(0),
  mBlockRegenFrac(0),
  mbDead(false),
  mActivateType(ACTIVATE_NONE)
{
  InitPropObject( mCLASSID );
}



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");
  return 0;
}

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

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

  return(pObject);
}

void                
cStatsCharacter::Reset()
{
  mCurDamage = 0;
  mbDead = false;
}

void                
cStatsCharacter::Update(float time)
{
  mAbilities.Update(time);
  mConditions.Update(time);

  Regenerate(time);
}

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

void
cStatsCharacter::Regenerate(float time)
{
  // check network ownership requirements
  if (!NetworkHasAuthorityOver(mOwner))
  {
    return;
  }
  int HealthRegen = CalcHealthRegen();
  float health_amount = ((float)HealthRegen)/1000.0f * time;
  mHealthRegenFrac += health_amount;
  if (mCurDamage > 0.0f)
  {
    int health_whole = (int) mHealthRegenFrac;
    mCurDamage -= health_whole;
    mHealthRegenFrac -= (float) health_whole; 

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

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

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

  int PowerRegen = CalcManaRegen();
  float power_amount = ((float)PowerRegen)/1000.0f * time;
  mPowerRegenFrac += power_amount;
  if (mCurPowerLoss > 0.0f)
  {
    int power_whole = (int) mPowerRegenFrac;
    mCurDamage -= power_whole;
    mPowerRegenFrac -= (float) power_whole; 

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



void                        
cStatsCharacter::SetMaster(const char *newmaster)
{
  SyAssert(mOwner);
  SyAssert(mOwner->GetTitan());

  int id =  SyHashResourceID(newmaster);


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

  mpMaster = db->GetCharacterMaster(id);
  SyAssertf(mpMaster!=NULL,"Unable to find character master '%s' for object '%s'",mMasterName.AsChar(),mOwner->GetName()); // todo: more graceful failure
  mMasterName.Init(mpMaster->mID.GetName());

  mpClass = db->GetCharacterClass(mpMaster->mClass);
  mpLevel = db->GetCharacterLevel(mpMaster->mLevel);
  SyAssertf(mpClass!=NULL,"Unknown class");
  SyAssertf(mpLevel!=NULL,"Unknown level");

  SyAssert(mpClass);

}

void
cStatsCharacter::SetOwner(cGameObject *owner)
{
  mOwner = owner;
  mAbilities.SetTitan(mOwner->GetTitan());   // change on levelup (permanent)
  mAbilities.SetOwnerID(mOwner->GetID());   // change on levelup (permanent)
  mConditions.SetTitan(mOwner->GetTitan());   // change on levelup (permanent)
  mConditions.SetOwnerID(mOwner->GetID());   // change on levelup (permanent)
}

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

  return mpMaster->mModel;
}

int
cStatsCharacter::GetHealth()
{

  return CalcMaxHealth() - mCurDamage;
}

int
cStatsCharacter::GetBlock()
{

  return CalcMaxBlock() - mCurBlockLoss;
}

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;
    SyAssertf(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);
      SyAssertf(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)
{
  // 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))
  {
    return;
  }

  
  // blocking
  cDamagePacket damage;

  cGraphicCharacter *targetGraphic = prop_cast<cGraphicCharacter *>(target_obj->GetGraphic());

  if (targetGraphic && targetGraphic->GetAnimController()->IsBlocking())
  {
    damage.mbBlocked = true;
  }

  damage.mAttackerID = mOwner->GetID();
  damage.mDefenderID = target_id;
  damage.mCombo=attackIndex;
  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);

  if (damage.mbBlocked)
  {
    target_obj->GetStats()->ApplyDamage(&damage);

    if (target_obj->GetStats()->GetBlock() < 0)
    {


      // recalculate damage packet without strikethrough
      damage.mbBlocked = false;
      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);
    }
    else
    {
  
      return; // no damage, blocked...
    }
  }
  target_obj->GetStats()->ApplyDamage(&damage);
}

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

  if (!pDamage)
  {
    return;
  }

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

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


bool                
cStatsCharacter::TargetInAttackRange(cGameObject *target, eComboType attackIndex)
{
  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);

      if (target->GetDistance(mOwner) <= range)
      {
        return true;
      }
    }
  }

  return false;
}


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

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

  cAnimCharControllerInput *input = pCharGraphic->GetAnimInput();

  if (packet->mbBlocked)
  {
    mCurBlockLoss += packet->GetTotal();

    int max_block = CalcMaxBlock();
    if (mCurBlockLoss > max_block)
    {
      // able to strike through defenses
      static const int STRIKETHROUGH_PENALTY = 5;
      mCurBlockLoss = STRIKETHROUGH_PENALTY + max_block;
    }
  }
  else
  {
    mCurDamage += packet->GetTotal();


    if (mCurDamage >= CalcMaxHealth())
    {
      Die(packet->mAttackerID);
    }
    else
    {
      input->mHitReact= true;
      input->mHitReactTarget= packet->mAttackerID;
      input->mHitReactAmount= (float) packet->GetTotal();

      // notify AI of attack 
      mOwner->GetIntel()->OnAttacked(packet->mAttackerID,packet->GetTotal());
    }
  }
 
  // todo: don't always get knocked down...

  // 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::Die(tGameObjectID killer)
{
  if (!mbDead)
  {
    mCurDamage = CalcMaxHealth();
    // unconscious
    cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(mOwner->GetGraphic());
    SyAssert(pCharGraphic);
    cAnimCharControllerInput *input = pCharGraphic->GetAnimInput();
    input->mDeath= true;
    mbDead = true;

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

    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->GetTitan(),mOwner->GetLocation());
      }
      else
      {
        SyAssertf(0,"Unknown treasure set for NPC '%s'(%s)",mOwner->GetName(),mpMaster->mNameString);
      }
    }

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

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

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

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::NetworkReceiveBroadcast(const char *packet,int size)
{
  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());

  SyAssert(pCharGraphic);
  
  cAnimCharControllerInput *input = pCharGraphic->GetAnimInput();
  SyAssertf(input!=NULL,"Bad object construction: no input?");
  if (pPacket.mDeath)
  {
    input->mDeath= true;
    mbDead = true;
  }
  input->mHitReact= pPacket.mHitReact;
  input->mHitReactAmount= (float)pPacket.mHitReactAmount;
  input->mHitReactTarget= pPacket.mAttacker;
  
}; // 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()->GetGlobalRules()->ProcessPacket(packet);
  mAbilities.ProcessPacket(packet);
  mConditions.ProcessPacket(packet);

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

  // todo: Environmental
  LogPacketEnd(packet,mOwner->GetTitan());

}

void                
cStatsCharacter::ActivateObject(tGameObjectID targetID)
{
  cGameObject *target = mOwner->GetRegistry()->Fetch(targetID);
  if (target == NULL )
  {
    SyAssertf(0,"Invalid object in activate command");
    return;
  }
  static const float ATTACK_RANGE(2.0f);
  if (target->GetDistance(mOwner) > ATTACK_RANGE)
  {
    // object out of range
    return;
  }

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

  
  target->Activate(mOwner);

}


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

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

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

  return 1.0f;
}

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

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

  return 1.0f;
}

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

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

  return 1.0f;
}

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

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

  return 1.0f;
}

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

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

  return 1.0f;
}

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

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

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

  return NPCBEH_NONE;
}

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

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

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

  return NPCFACTION_MONSTER;
}

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

void                        
cStatsCharacter::AddCondition(cRule *condition)
{
  mConditions.Add(condition);
}

void
cStatsCharacter::RemoveCondition(const char *name)
{
  mConditions.Remove(name);
}


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

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

  targetPos.X +=  SY_SIN(heading) * 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()
{  
  return mActivateType;
}


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

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

cStatsPropMaster::cStatsPropMaster()
: mMaxHealth(0),
  mModelName(NULL),
  mAnimSet(0),
  mTreasureSet(0),
  mbActivates(false),
  mbExplodes(false),
  mbPushable(false),
  mScriptName(NULL),
  mDebris(0)
{
}

cStatsPropMaster::~cStatsPropMaster()
{
  delete [] mModelName;
  mModelName = 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");

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


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

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);
  SyAssertf(mpMaster!=NULL,"Unknown prop master '%s' for object '%s'",master_name,mOwner->GetName()); 
  mMasterName.Init(mpMaster->mID.GetName());
}


const char*
cStatsProp::GetModel()
{
  return mpMaster->mModelName;
}

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

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

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

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

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

  // network update
  cNetDamagePacket netpacket;
  netpacket.mTotalDamage = mCurDamage;
  netpacket.mAttacker = packet->mAttackerID;
  netpacket.mDeath = mbDead;
//  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                
cStatsProp::Die(tGameObjectID killer)
{
  if (!mbDead)
  {
    mCurDamage=CalcMaxHealth();

    mbDead = true;

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

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

    if (mpMaster->mDebris != 0)
    {
      SyVect3 force(0,0,0); // todo
      if (killer != ID_NONE)
      {
        cGameObject *killer_obj = mOwner->GetRegistry()->Fetch(killer);

        SyVect3 towards;
        towards.Sub(mOwner->GetLocation(),killer_obj->GetLocation());
        force.Mul(towards,l_IMPACT_FORCE);
      }
      mOwner->GetTitan()->GetDebrisSys()->Spawn(mOwner->GetLocation(),force,mpMaster->mDebris);
    }
  }
}

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

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

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

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

  return mpMaster->mMaxHealth;
}

void
cStatsProp::NetworkReceiveBroadcast(const char *packet,int size)
{
  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)
  {
    input->mDeath= true;
    mbDead = true;
  }

  // todo add state transition
}

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

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

void                        
cStatsProp::AddCondition(cRule *condition)
{
  mConditions.Add(condition);
}

void
cStatsProp::RemoveCondition(const char *name)
{
  mConditions.Remove(name);
}
//------------------------------------ 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)
{
  delete mpItem;
  mpItem = new cItem();
  SyAssert(mOwner);
  SyAssert(mOwner->GetTitan());

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

  tGameID id = SyHashResourceID(master_name);
  const cStatsItemMaster *master= db->GetItemMaster(id);
  SyAssertf(master!=NULL,"Unknown item master '%s'",master_name); // todo: more graceful failure
  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);
  SyAssert(master); // todo: more graceful failure
  mpItem->SetMaster(master);
}

const char *        
cStatsItem::GetModel()
{
  // TODO: also save item info
  if (mpItem == NULL)
  {
    return NULL;
  }
  SyAssertf(mpItem!=NULL,"Uninitialized Item");
  const cStatsItemMaster *itemmaster = mpItem->GetMaster();
  SyAssertf(itemmaster!=NULL,"Uninitialized Item Master");
  return itemmaster->mWorldModel;
};


void                
cStatsItem::NetworkReceiveBroadcast(const char *packet, int size) // the ownint peer of this object has broadcast an update
{
  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_PICKUP_CONFIRM:
    {
    // just the fact that we exist means the item hasn't been picked up by anyone else...
      cNetPickupConfirmPacket confirm;
      confirm.UnpackBuffer(packet, size);

      cGameObject *actor = mOwner->GetRegistry()->Fetch(confirm.mActor);

      SyAssertf(actor!=NULL,"Lost actor.");
      if (actor != NULL)
      {
        // this will pick up the item, but not actually delete the object because we don't own it.
        ((cInventoryCharacter *)actor->GetInventory())->PickUp(mOwner);
      }
    }
    break;
  default:
  SyAssertf(0,"Unknown packet type");
  break;
  }
}

void                
cStatsItem::NetworkReceiveMessage(const char *packet,int size) // the ownint peer of this object has broadcast an update
{
  cNetPacket::eNetPacketType type;

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

  type=((cNetPacket *)packet)->mType;

  switch (type)
  {
  case cNetPacket::NET_PICKUP_REQUEST:
    {
    // just the fact that we exist means the item hasn't been picked up by anyone else...
      cNetPickupRequestPacket request;
      request.UnpackBuffer(packet, size);

      cNetPickupConfirmPacket 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
      ((cInventoryCharacter *)actor->GetInventory())->PickUp(mOwner);
    }
    break;
  default:
  SyAssertf(0,"Unknown packet type");
  break;
  }
}

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

void                
cStatsItem::Activate(cGameObject *activater)
{
  if (!mOwner->IsLocal())
  {
    // have to issue a request...
    cNetPickupRequestPacket netpacket;
    netpacket.mActor = activater->GetID();
    mOwner->GetTitan()->GetPeeringNetwork()->ObjectMessage(mOwner->GetID(),(char *)&netpacket,sizeof(netpacket));

    return;
  }

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

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

  ((cInventoryCharacter *)activater->GetInventory())->PickUp(mOwner);
};
//------------------------------------ cStatsProjectileMaster


cStatsProjectileMaster::cStatsProjectileMaster() :
  mModel(NULL),
  mSpeed(10.0f),
  mSeeking(0.0f),
  mGravity(true),
  mSpins(false),
  mPenetrates(false),
  mCollisionRadius(0.1f),
  mAddCondition(NULL),
  mAddConditionTimeMS(1000),
  mNum(1),
  mMaxArc(10.0f),
  mAcceleration(0.0f),
  mOrbiting(0.0f)
{
}

cStatsProjectileMaster::~cStatsProjectileMaster()
{
  delete [] mModel;
  mModel = NULL;

  delete [] mAddCondition;
  mAddCondition = NULL;
}

//------------------------------------ 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,
               "cStatsItem", 
               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()
{
}

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

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

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

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

  return mpMaster->mModel;
};
  
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();

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

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

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

void 
Stats_RegisterTuningVariables()
{
  gTuningSys.AddFloat(&l_IMPACT_FORCE,"Debris_AttackForce");
}


// EOF
