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

//-------------------------------------------------------- Includes
#include "gameobj.h"
#include "intel.h"
#include "stats.h"
#include "inventory.h"
#include "graphic.h"
#include "physics.h"
#include "registry.h"
#include "titan.h"
#include "TitanPeeringNetwork.h"
#include "netpacket.h"
#include "script.h"
#include "cameracontroller.h"
#include "script_pawn.h"
#include "rule_condition.h"

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


//--------------------------------------------------------- Globals
//--------------------------------------------------------- Locals
static const float POSITION_UPDATE_RATE = 0.1f; // time between updates.
//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions

//------------------------------------ cGameObject

cGameObject::cGameObject() :
  mRemote(false),
  mbInScope(true),
  mPositionUpdateTime(0.0f),
  mIntel(NULL),
  mStats(NULL),
  mInventory(NULL),
  mGraphic(NULL),
  mPhysics(NULL),
  mScript(NULL)
{
  InitPropObject( GAMEOBJECT_CLASSID );
}

cGameObject::~cGameObject()
{
  delete mIntel;
  delete mStats;
  delete mInventory;
  delete mGraphic;
  delete mPhysics;
  delete mScript;
}

int           
cGameObject::InitPropClass()
{
  SyPropClassID ClassID = GAMEOBJECT_CLASSID;

/* Add the class */

  AddClass( ClassID, 
           "cGameObject", 
            Creator, 
            ClassID, 
            0 ); 

  AddInt32Property(ClassID,PropId_ID,SyMemberOffset(cGameObject,mID),"mID");
  SyPropEnum *propEnum;
  AddEnumProperty(ClassID,PropId_Type,SyMemberOffset(cGameObject,mType),"mType",&propEnum);
  propEnum->Add(cGameObject::OBJ_PLAYER,"PLAYER");
  propEnum->Add(cGameObject::OBJ_NPC,"NPC");
  propEnum->Add(cGameObject::OBJ_ITEM,"ITEM");
  propEnum->Add(cGameObject::OBJ_MARKER,"MARKER");
  propEnum->Add(cGameObject::OBJ_PROP,"PROP");

  AddSubObjectPtrProperty(ClassID,PropId_Intel,SyMemberOffset(cGameObject,mIntel),"mIntel");
  AddSubObjectPtrProperty(ClassID,PropId_Stats,SyMemberOffset(cGameObject,mStats),"mStats");
  AddSubObjectPtrProperty(ClassID,PropId_Inventory,SyMemberOffset(cGameObject,mInventory),"mInventory");
  AddSubObjectPtrProperty(ClassID,PropId_Graphic,SyMemberOffset(cGameObject,mGraphic),"mGraphic");
  AddSubObjectPtrProperty(ClassID,PropId_Physics,SyMemberOffset(cGameObject,mPhysics),"mPhysics");
  AddSubObjectPtrProperty(ClassID,PropId_Script,SyMemberOffset(cGameObject,mScript),"mScript");

  AddStringProperty(ClassID,PropId_Name,SyMemberOffset(cGameObject,mName),"mName");
  AddVect3Property(ClassID,PropId_HPR,SyMemberOffset(cGameObject,mPosition.GetHPR()),"mHPR");
  AddVect3Property(ClassID,PropId_Location,SyMemberOffset(cGameObject,mPosition.GetLocation()),"mLocation");

  return(0);
}

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

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


  return(pObject);
}

tGameObjectID          
cGameObject::Create(cGameObjectRegistry *owner,tGameID id,tObjectType newtype) // creates and registers subobjects
{
  mType = newtype;
  mID  = id;
  //mNameID.SetName(name);

  switch (newtype)
  {
    case OBJ_PLAYER:
      {
        mIntel = new cIntelPlayer();
        mStats = new cStatsCharacter();
        mInventory = new cInventoryCharacter(); 
        mGraphic = new cGraphicCharacter();
        mPhysics = new cPhysicsAnimated();
        mScript = new cScript();
      }
      break;

    case OBJ_NPC:
      {
        mIntel = new cIntelNPC();
        mStats = new cStatsCharacter();
        mInventory = new cInventoryCharacter(); 
        mGraphic = new cGraphicCharacter();
        mPhysics = new cPhysicsAnimated();
        mScript = new cScript();
      }
      break;

    case OBJ_ITEM: // in the world
      {
        mIntel = new cIntelNone();
        mStats = new cStatsItem();
        mInventory = new cInventoryNone(); // unless backpack??? 
        mGraphic = new cGraphicActor();
        mPhysics = new cPhysicsDynamic();
        mScript = new cScript();
      }
      break;

    case OBJ_MARKER:
      {
        mIntel = new cIntelNone();
        mStats = new cStats();
        mInventory = new cInventoryNone(); 
        mGraphic = new cGraphic();
        mPhysics = new cPhysicsStatic();
        mScript = new cScript();
      }
      break;

    case OBJ_PROJECTILE:
      {
        mIntel = new cIntelProjectile();
        mStats = new cStatsProjectile();
        mInventory = new cInventoryNone(); 
        mGraphic = new cGraphicActor();
        mPhysics = new cPhysicsProjectile();
        mScript = new cScript();
      }
      break;

    case OBJ_PROP:
      {
        mIntel = new cIntelProp();
        mStats = new cStatsProp();
        mInventory = new cInventoryNone(); 
        mGraphic = new cGraphicProp();
        mPhysics = new cPhysicsProp();
        mScript = new cScript();
      }
      break;

    default:
      SyAssert(0); // unknown object type
      return ID_NONE;
      break;
  }

  FixUp(owner);

  return GetID();
}

void
cGameObject::FixUp(cGameObjectRegistry *owner)
{     
  mOwner = owner;
  mIntel->SetOwner(this);
  mStats->SetOwner(this);
  mInventory->SetOwner(this);
  mGraphic->SetOwner(this);
  mPhysics->SetOwner(this);
  mScript->SetOwner(this);
}

void        
cGameObject::Init()
{
  mStats->Init();
  mInventory->Init();
  mGraphic->Init();
  mPhysics->Init();
  mScript->Init();
  mIntel->Init();   // AI update has dependencies on stats being initialized

  GetTitan()->GetScriptSys()->ScriptEvent(PET_INIT,GetID(),ID_NONE);
}

void        
cGameObject::Exit()
{
  mIntel->Exit();
  mStats->Exit();
  mInventory->Exit();
  mGraphic->Exit();
  mPhysics->Exit();
  mScript->Exit();
}

void
cGameObject::Reset()
{
  mIntel->Reset();
  mGraphic->Reset();
  mPhysics->Reset();
  mStats->Reset();
  mScript->Reset();
}

void cGameObject::OnWake()
{
  mIntel->OnWake();
  mGraphic->EnterScope();
}

void cGameObject::OnSleep()
{
  mIntel->OnSleep();
}

void        
cGameObject::Update(float time) // once/frame
{
  // first check to see we're in scope
  UpdateScope();

  if (!mbInScope)
  {
    return;
  }

  // different call order depending on whether physics is driving animation or verse vica

  mStats->Update(time);  
  mInventory->Update(time);
  mIntel->Update(time);    //  read controller
  mScript->Update(time);

  if (mPhysics->GetLocomotion()==cPhysics::LOCO_ANIMATION || mPhysics->GetLocomotion()==cPhysics::LOCO_CUTSCENE )
  {
    mGraphic->Update(time);  //  update animation state machine
    mPhysics->Update(time);  //  update location
  }
  else
  {
    mPhysics->Update(time);  //  update location
    mGraphic->Update(time);  //  update animation state machine
  }
  
  NetworkUpdate(time);
  // now is the correct time to delete objects

}

void cGameObject::Activate(cGameObject *activater)
{
  SyAssert(mStats);

  cScript *script = GetScript();
  if (script != NULL)
  {
    script->Run(activater,SE_ACTIVATE);
  }
  mOwner->GetTitan()->GetScriptSys()->ScriptEvent(PET_ACTIVATE,GetID(),activater->GetID());

  mStats->Activate(activater);
}

void cGameObject::Open()
{
  SyAssert(mIntel);

  mIntel->Open();
}

bool              
cGameObject::SetSafeLocation(const SyVect3 &loc) // finds a safe location near this spot
{
  return mPhysics->SetSafeLocation(loc);
};

const char *
cGameObject::GetModel()
{
  return GetStats()->GetModel();
}

tGameID
cGameObject::GetAnimSet()
{
  return GetStats()->GetAnimSet();
}


bool              
cGameObject::CheckForDelete()
{
  if (mPhysics->CheckForDelete())
  {
    return true;
  }
  if (mIntel->CheckForDelete())
  {
    return true;
  }
  return false;
}

void        
cGameObject::Prerender() // once/frame
{
  mGraphic->Prerender(); // update visual location in scene from game object
}

Titan *           
cGameObject::GetTitan()
{
  return mOwner->GetTitan();
}

float
cGameObject::GetDistance(cGameObject *other)
{
  const SyVect3 &loc = GetLocation();
  float center_distance =  loc.Distance(other->GetLocation());
  float distance = center_distance - GetPhysics()->GetCollisionRadius() - other->GetPhysics()->GetCollisionRadius();
  return distance;
}

float
cGameObject::GetDistance(const SyVect3 other_loc)
{
  const SyVect3 &loc = GetLocation();
  float center_distance =  loc.Distance(other_loc);
  float distance = center_distance - GetPhysics()->GetCollisionRadius();
  return distance;
}

float
cGameObject::GetHeadingTowards(cGameObject *other)
{
  return GetHeadingTowards(other->GetLocation());
}

float 
cGameObject::GetHeadingTowards(const SyVect3 &other_loc)
{
  const SyVect3 &loc = GetLocation();
  SyVect3 delta;
  delta.Sub(other_loc,loc);
  return SY_ATAN2(delta.X,delta.Z);
}

float
cGameObject::GetPitchTowards(cGameObject *other)
{
  const SyVect3 &loc = GetLocation();
  const SyVect3 &other_loc = other->GetLocation();
  
  SyVect3 delta;
  delta.Sub(other_loc,loc);
  return SY_ATAN2(delta.Y,SY_SQRT(delta.X*delta.X + delta.Y*delta.Y));
}

tGameID           
cGameObject::GetNameID()
{
  return SyHashResourceID(mName.AsChar());
}

int               
cGameObject::Reset( SyObjFile& File )// resets to state in the file 
{
  delete mIntel;
  delete mStats;
  delete mInventory;
  delete mGraphic;
  delete mPhysics;
  delete mScript;

  mIntel = NULL;
  mStats = NULL;
  mInventory = NULL;
  mGraphic = NULL;
  mPhysics = NULL;
  mScript = NULL;
  return Read(File);
}

int               
cGameObject::GetState( SyObjFile& File ) // resets to state in the file 
{
  return Write(File,SYPROPSERIALTYPE_STANDARD);
}


void              
cGameObject::NetworkReceiveBroadcast(const char *packet,int packetlen)
{
  cNetPacket::eNetPacketType type;

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

  SyPack::Unpack8u(packet, &type);      // note: pack-format must match cNetPacket::Pack

  switch (type)
  {
  case cNetPacket::NET_POSITION:
    {
      cNetPositionPacket position_packet;
      position_packet.UnpackBuffer(packet, packetlen);

      mPhysics->NetworkSetLocation(position_packet.mLocation);
      mPhysics->NetworkSetVelocity(position_packet.mVelocity);
      mPhysics->NetworkSetHeading(position_packet.mHeading);

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

      if (pCharGraphic != NULL)
      {
        cAnimCharControllerInput *input = pCharGraphic->GetAnimInput();
        if (input !=NULL)
        {
          input->mHeadingRequest = position_packet.mHeadingRequest;
          input->mSpeedRequest = position_packet.mSpeedRequest;
        }
      }
    }
    break;

  case cNetPacket::NET_ANIMSTATE:
    {
      mGraphic->NetworkReceiveBroadcast(packet,packetlen);
    }
    break;

  case cNetPacket::NET_DAMAGE:
  case cNetPacket::NET_PICKUP_CONFIRM:
    {
      mStats->NetworkReceiveBroadcast(packet,packetlen);
    }
    break;
  default:
  SyAssertf(0,"Unknown packet type");
  break;
  }

}

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

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

  SyPack::Unpack8u(packet, &type);      // note: pack-format must match cNetPacket::Pack

  switch (type)
  {
  case cNetPacket::NET_PICKUP_REQUEST:
    {
      mStats->NetworkReceiveMessage(packet,packetlen);
    }
    break;
  default:
  SyAssertf(0,"Unknown packet type");
  break;
  }
}

void              
cGameObject::NetworkUpdate(float time)
{
  if (!mRemote)
  {
    mPositionUpdateTime += time;
    if(mPositionUpdateTime > POSITION_UPDATE_RATE)
    {
      mPositionUpdateTime = 0.0f;
      cNetPositionPacket packet;
      packet.mLocation    = GetLocation();
      packet.mVelocity =  mPhysics->GetVelocity();
      packet.mHeading = GetHeading();

      cGraphicCharacter* pCharGraphic = prop_cast<cGraphicCharacter *>(mGraphic);
      if (pCharGraphic != NULL && pCharGraphic->GetAnimInput() !=NULL)
      {
        packet.mHeadingRequest = pCharGraphic->GetAnimInput()->mHeadingRequest;
        packet.mSpeedRequest = pCharGraphic->GetAnimInput()->mSpeedRequest;
      }

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

void
cGameObject::UpdateScope()
{
  static const float SIMULATION_DISTANCE = 100.0f; // meters
  static const float SIMULATION_BORDER = 5.0f; // grey area to prevent thrashing

  SyVect3 camera_loc = GetTitan()->GetCameraController()->GetCamera()->GetLocation();

  float distance = camera_loc.Distance(GetLocation());
  if (mbInScope)
  {
    if (distance > SIMULATION_DISTANCE)
    {
      mbInScope = false;
      OnSleep();
    }
  }
  else
  {
    if (distance < SIMULATION_DISTANCE - SIMULATION_BORDER)
    {
      mbInScope = true;
      OnWake();
    }
  }
}

void cGameObject::AddCondition(const char* name, int numParams, cell* pParams)
{
  if (GetType() == OBJ_NPC ||
      GetType() == OBJ_PLAYER)
  {
    SyAssert(prop_cast<cStatsCharacter*>(GetStats())!=NULL);

    cStatsCharacter* pStats = static_cast<cStatsCharacter*>(GetStats());
    cRule_Condition* pRule = GetTitan()->GetRuleSys()->CreateCondition(name, numParams, pParams);
    SyAssert(pRule != NULL);
    if (pRule != NULL)
    {
      pStats->AddCondition(pRule);
    }
  }
  else if (GetType() == OBJ_PROP)
  {
    SyAssert(GetStats()->DynamicCast(STATSPROP_CLASSID) != NULL);
    cStatsProp* pStats = static_cast<cStatsProp*>(GetStats());
    cRule_Condition* pRule = GetTitan()->GetRuleSys()->CreateCondition(name, numParams, pParams);
    SyAssert(pRule != NULL);
    if (pRule != NULL)
    {
      pStats->AddCondition(pRule);
    }
  }
}

void cGameObject::RemoveCondition(const char* name)
{
  if (GetType() == OBJ_NPC ||
      GetType() == OBJ_PLAYER)
  {
    SyAssert(prop_cast<cStatsCharacter*>(GetStats())!=NULL);
    cStatsCharacter* pStats = static_cast<cStatsCharacter*>(GetStats());
    pStats->RemoveCondition(name);
  }
  else if (GetType() == OBJ_PROP)
  {
    SyAssert(prop_cast<cStatsProp*>(GetStats())!=NULL);
    cStatsProp* pStats = static_cast<cStatsProp*>(GetStats());
    pStats->RemoveCondition(name);
  }
}

//////////////////////////////////////////////////////////////////////////////////////////// Global Funcs

void 
RegPropClasses_GameObj()
{
  cGameObject::InitPropClass();
  RegPropClasses_Intel();
  RegPropClasses_Stats();
  RegPropClasses_Inventory();
  RegPropClasses_Graphic();
  RegPropClasses_Physics();
  RegPropClasses_Script();
}
// EOF
