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

//-------------------------------------------------------- Includes
#include "script_pawn.h"
#include "script_func.h"

#ifndef SN_TARGET_PS3
#include <io.h>
#include <windows.h>
#endif

#include "pawn/amx/amxdbg.h"
#include "registry.h"
#include "stats.h"
#include "SyFile.h"
#include "levelobj.h"
#include "tuning.h"
#include "gameerror.h"
#include "cameracontroller.h"
#include "titanpeeringnetwork.h"
#include "titaninputhandler.h"

//---------------------------------------------- Class Declarations
static const float CUTSCENE_SKIP_FADE_OUT_TIME = 0.3f;
static const float CUTSCENE_FADE_IN_TIME = 0.3f;

#if (defined(_GAME_ERROR_REPORTING) && !defined(SN_TARGET_PS3))
#define WRITE_SCRIPT_LOG
#define SCRIPT_LOG_FILE_NAME "scriptdebuglog.txt"
#endif

#if (defined(_DEBUG) && !defined(SN_TARGET_PS3))
#define DEBUG_SCRIPTS 1
#else
#define DEBUG_SCRIPTS 0
#endif

typedef enum
{
  SNT_NONE,
  SNT_WAIT,
  SNT_ANIMATION,
  SNT_PATH
} eScriptNotificationType;



class cScriptInstance
{
public:
  cScriptInstance(cScriptSys *sys);
  ~cScriptInstance();

  bool Run(const char* scriptName, tGameObjectID actorID = ID_NONE, tGameObjectID targetID = ID_NONE);

  bool Run(const cGameObject* actor, tGameObjectID targetID, ePawnEventType type,int *result = NULL);
  bool Notify(eScriptNotificationType type,tGameObjectID ThisObj);
  void Update(float time);
  bool Load(const char *filename);


  void SetSleepTime(float time){mSleepTime = time;}
  void SetSleepActor(tGameObjectID sleep_actor){mSleepActor = sleep_actor;}

  bool  IsCompleted(){return mCompleted;}
  tGameObjectID GetThis() const {return mThis;}
  tGameObjectID GetOther() const {return mOther;}
  cScriptSys *GetScriptSys() const {return mpScriptSys;}
  bool IsActive() const {return mpScriptSys->GetActive()==this;}
  void Clone(cScriptInstance *other, bool bSetActive); // makes this into a clone of other

  void DebugLoadInfo(const char *filename);
  void DebugEnable();
  void DebugDisable();
  const char *DebugGetFilename(AMX *amx);
  int         DebugGetLinenum(AMX *amx);
  void        DebugOutputVariables(AMX *amx,SyString &string);

  void SetCutscene() {mCutscene = true;}
  void RemoveCutscene() { if (mCutscene) mCompleted = true; }

  void PackRemoteScriptData(char* pSource, cNetGlobalScriptStatePacket* pPacket);
  void UnpackRemoteScriptData(char* pSource, const cNetGlobalScriptStatePacket::ScriptInstanceData* pInfo);

  void ReadScriptData(cScriptInstanceDataProxy* pProxy);
  void WriteScriptData(cScriptInstanceDataProxy* pProxy);

protected:
  friend class cScriptSys;

  bool _GetFunctionIndex(int *ans, ePawnEventType type, const cGameObject *obj);
  bool _Execute(int index, int *result = NULL);
  void _CopyDataFromParent();  // copies global data that may have changed
  void _CopyDataToParent(); 
  void _ReportFunctionCall(const char *str);// 

  void* GetDataAddr();
  int GetDataSize();
  void* GetHeapAddr();
  int GetHeapSize();
  void* GetStackAddr();
  int GetStackSize();

  // mAMX has to stay here; oh, and no virtual funcs allowed in this class.
  AMX                 mAMX;     // virtual machine

  cScriptSys          *mpScriptSys;
  tGameObjectID       mThis;
  tGameObjectID       mOther;

  void *              mProgram; // program it's running
  size_t              mMemSize;
  void *              mData;    // only for clones...
  int                 mDataSize;
  cScriptInstance    *mParent;  // of a cloned script
  // for sleeping scripts...
  eScriptNotificationType mSleepNotify;
  float               mSleepTime; // for sleep time...
  tGameObjectID       mSleepActor;
  bool                mCompleted;
  AMX_DBG             *mpAMX_DBG;  // symbolic information for debugging
  bool                mCutscene;
};


//-------------------------------------------------------- local function declarations
int    srun_LoadProgram(AMX *amx,const char *filename, void *memblock);
bool   srun_LoadDebugInfo(AMX_DBG *amx,const char *filename);

size_t srun_ProgramSize(const char *filename);
cScriptInstance *Script_GetInstance(AMX *amx);

static int l_DebugFunctionCalls = 0;
//-------------------------------------------------------------------- Local Variables

#if DEBUG_SCRIPTS
static const char *SCRIPT_PATHNAME = "data\\level\\";
static const char *SCRIPT_SRC_PATHNAME = "\\source\\scripts\\";
#endif

namespace
{
  const char *entry_names[] = 
  {
    //"main", // PET_DIALOG,	// when the player tries to talk to someone
    "@onActivate", // PET_DIALOG,	// when the player tries to talk to someone
    "@onEnter", //PET_ENTER,	// for trigger areas, when entered
    "@onExit", //PET_EXIT,	    // for trigger areas, when exited
    "@onStartup", //PET_STARTUP,	// for trigger areas, when someone is inside
    "@onCollide", //PET_COLLIDE, 	// when destroyed or killed
    "@onHit", // PET_HIT // when object successfully hits a target
    "@onDeath", // PET_DEATH  // when object kills another object
    "@onInit", // PET_INIT  // when object is created
    "@onSpellCast", // PET_SPELL_CAST // object cast a spell
    "@onSpellAreaEnter", // PET_SPELL_AREA_ENTER // object entered a spell area
    "@onSpellAreaExit", // PET_SPELL_AREA_EXIT // object left a spell area
  };

#define NUM_ENTRIES (sizeof(entry_names)/sizeof(entry_names[0]))
}

//---------------------------------------------------- Member Function Definitions
//----------------------------------------------------------- cScriptInstance
cScriptInstance::cScriptInstance(cScriptSys *scriptSys) :
mpScriptSys(scriptSys),
mThis(ID_NONE),
mOther(ID_NONE),
mProgram(NULL),
mMemSize(0),
mData(NULL),
mDataSize(0),
mParent(NULL),
mSleepNotify(SNT_NONE),
mSleepTime(0.0f),
mSleepActor(ID_NONE),
mCompleted(false),
mpAMX_DBG(NULL),
mCutscene(false)

{
  mAMX.base = NULL;
}

cScriptInstance::~cScriptInstance()
{
  if (mParent == NULL) // this is a non-cloned script
  {
    if (mAMX.base != NULL)
    {
      amx_Cleanup(&mAMX);
    }
  }

  delete [] ((char*)mData); 
  delete [] ((char*)mProgram);

  mProgram = NULL;
  mData = NULL;
  if (mpAMX_DBG != NULL)
  {
    dbg_FreeInfo(mpAMX_DBG);
    delete mpAMX_DBG;
  }
}

void* cScriptInstance::GetDataAddr()
{ 
  AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
//  unsigned char* data = (mAMX.data!=NULL) ? mAMX.data : mAMX.base+hdr->dat;
  return (void *)(mAMX.base+hdr->dat);
}

int cScriptInstance::GetDataSize()
{
  AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
  return hdr->hea - hdr->dat;
}

void* cScriptInstance::GetHeapAddr()
{
  AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
  unsigned char* data = (mAMX.data!=NULL) ? mAMX.data : mAMX.base+hdr->dat;
  return (void *)(data + hdr->hea);
}

int cScriptInstance::GetHeapSize()
{
  return mAMX.hlw - mAMX.hea;
}

void* cScriptInstance::GetStackAddr()
{
  AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
  unsigned char* data = (mAMX.data!=NULL) ? mAMX.data : mAMX.base+hdr->dat;
  return (void *)(data + mAMX.stk);
}

int cScriptInstance::GetStackSize()
{
  return mAMX.stp - mAMX.stk;
}

bool cScriptInstance::Run(const char* scriptName, tGameObjectID thisID, tGameObjectID otherID)
{
  bool ret = false;
  int index = 0;
  int err = amx_FindPublic(&mAMX, scriptName, &index);
  if ( err == 0 )
  {
    mThis = thisID;
    mOther = otherID;
    ret = _Execute(index);
  }

  return ret;
}

bool cScriptInstance::Run(const cGameObject* thisObj, tGameObjectID otherID, ePawnEventType type, int *result)
{
  bool ret = false;
  int index;

  if (_GetFunctionIndex(&index, type, thisObj))
  {
    mThis = thisObj ? thisObj->GetID() : ID_NONE;
    mOther = otherID;
    ret = _Execute(index,result);
  }

  return ret;
}

bool 
cScriptInstance::Notify(eScriptNotificationType type,tGameObjectID id)
{
  SyAssertf(type != SNT_NONE,"Bad Pawn Script Notification Type");
  if (id == mSleepActor && type == mSleepNotify)
  {
    mSleepNotify = SNT_NONE;
    _Execute(AMX_EXEC_CONT);
    return true;
  }
  return false;

}

void 
cScriptInstance::Update(float time)
{
  if (mCompleted) return;

  if (mSleepNotify == SNT_WAIT)
  {
    mSleepTime -= time;
    if (mSleepTime < 0.0f)
    {
      mSleepNotify = SNT_NONE;
      _Execute(AMX_EXEC_CONT);
    }
  }
}

bool 
cScriptInstance::Load(const char *originafilename)
{
  char filename[512];
  static const char *path = "game_assets\\design\\amx";
  sprintf(filename,"%s\\%s",path,originafilename);
  mMemSize = srun_ProgramSize(filename);
  if (mMemSize == 0)
  {
    return false;
  }
  int err;

  mProgram = new char[mMemSize];
  if (mProgram == NULL)
  {
    SyAssertf(0,"Bad Alloc");
    return false;
  }
  err = srun_LoadProgram(&mAMX, filename, mProgram);
  if (err)
  {
    SyAssertf(0,"Unable to load program");
    return false;
  }

  // TODO
  script_RegisterNatives(&mAMX);

  return true;
}

void 
cScriptInstance::Clone(cScriptInstance *other, bool bSetActive)
{
// duplicate preloaded script...
  long program_size,datasize,heapsize;
  amx_MemInfo(&other->mAMX,&program_size,&datasize,&heapsize);
  mMemSize = 0;
  mProgram = NULL; // uses parent
  // use same data as parent
  mDataSize = datasize; 
  mData = new char[datasize+heapsize];

  if (other->mParent == NULL)
  {
    mParent = other;
  }
  else
  {
    mParent = other->mParent;
  }

  SyAssertf(mParent->mAMX.data == NULL, "Parent is a clone!  *gasp*!  I knew it!");

  mThis = other->mThis;
  mOther = other->mOther;

  memset(&mAMX,0,sizeof(AMX));
  amx_Clone(&mAMX,&other->mAMX,mData);

  mSleepTime = other->mSleepTime;
  mSleepActor = other->mSleepActor;
  mCutscene = false; // clones don't time out 

  // register self with script system
  mpScriptSys->AddClone(this, bSetActive);
}

void cScriptInstance::PackRemoteScriptData(char* pSource, cNetGlobalScriptStatePacket* pPacket)
{ 
  //
  // Packing script state to transfer from game host (us) to remote peers
  //
  AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
  unsigned char* data = (mAMX.data!=NULL) ? mAMX.data : mAMX.base+hdr->dat;

  long program_size, datasize, stackheapsize;
  amx_MemInfo(&mAMX, &program_size, &datasize, &stackheapsize);

  pPacket->AddScript(mpScriptSys->GetCloneIndex(mParent),
                     mThis,
                     mOther,
                     pSource,
                     (char*)data,
                     datasize+stackheapsize,
                     mSleepNotify,
                     mSleepTime,
                     mSleepActor,
                     mCutscene,
                     mAMX.cip,
                     mAMX.frm, 
                     mAMX.hea,
                     mAMX.hlw,
                     mAMX.stk,
                     mAMX.stp);
}

void cScriptInstance::UnpackRemoteScriptData(char* pSource, const cNetGlobalScriptStatePacket::ScriptInstanceData* pInfo)
{
  //
  // Loading script state transfered from game host 
  //
  long program_size, datasize, stackheapsize;
  amx_MemInfo(&mAMX, &program_size, &datasize, &stackheapsize);

  mThis = pInfo->mThisID;
  mOther = pInfo->mOtherID;
  mSleepNotify = (eScriptNotificationType)pInfo->mSleepNotifyType;
  mSleepTime = pInfo->mSleepTime;
  mSleepActor = pInfo->mSleepingID;
  mCutscene = pInfo->mbCutscene;

  if (pInfo->mDeltaSize > 0 && pInfo->mpScriptDelta)
  {
    AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
    unsigned char* data = (mAMX.data!=NULL) ? mAMX.data : mAMX.base+hdr->dat;

    int outLen = 0;
    if (!cDeltaCompression::ApplyDelta(pSource, datasize+stackheapsize, pInfo->mpScriptDelta, pInfo->mDeltaSize, (char*)data, datasize+stackheapsize, outLen) ||
        outLen != datasize+stackheapsize)
    {
      SyAssertf(false, "Error applying script delta");
    }

    if (mData)
    {
      memcpy(mData, data, datasize+stackheapsize);
    }
  }

  cScriptInstance* pParent = mpScriptSys->GetScriptInstance(pInfo->mParentIndex);

  if (pParent == this)
  {
    mParent = NULL;
  }
  else
  {
    mParent = pParent;
  }

  mAMX.cip = pInfo->mAMX_cip;
  mAMX.frm = pInfo->mAMX_frm;
  mAMX.hea = pInfo->mAMX_hea;
  mAMX.hlw = pInfo->mAMX_hlw;
  mAMX.stk = pInfo->mAMX_stk;
  mAMX.stp = pInfo->mAMX_stp;
}

void cScriptInstance::ReadScriptData(cScriptInstanceDataProxy* pProxy)
{
  //
  // Load script state from a save game
  //
  long programSize, dataSize, stackHeapSize;
  amx_MemInfo(&mAMX, &programSize, &dataSize, &stackHeapSize);

  SyAssert(pProxy->mDataSize == dataSize+stackHeapSize);

  mThis = pProxy->mThisID;
  mOther = pProxy->mOtherID;
  mSleepNotify = (eScriptNotificationType)pProxy->mSleepNotifyType;
  mSleepTime = pProxy->mSleepTime;
  mSleepActor = pProxy->mSleepingID;
  mCutscene = pProxy->mbCutscene;

  int index = 0;
  AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
  char* addr = (char*)((mAMX.data!=NULL) ? mAMX.data : mAMX.base+hdr->dat);

  while (addr && index < pProxy->mDataSize && index < (dataSize+stackHeapSize))
  {
    addr[index] = pProxy->mData(index);

    if (mDataSize > 0 && mData)
    {
      ((char*)mData)[index] = pProxy->mData(index);
    }

    ++index;
  }

  cScriptInstance* pParent = mpScriptSys->GetScriptInstance(pProxy->mParentIndex);

  if (pParent == this)
  {
    mParent = NULL;
  }
  else
  {
    mParent = pParent;
  }

  mAMX.cip = pProxy->mAMX_cip;
  mAMX.frm = pProxy->mAMX_frm;
  mAMX.hea = pProxy->mAMX_hea;
  mAMX.hlw = pProxy->mAMX_hlw;
  mAMX.stk = pProxy->mAMX_stk;
  mAMX.stp = pProxy->mAMX_stp;
}

void cScriptInstance::WriteScriptData(cScriptInstanceDataProxy* pProxy)
{
  //
  // Save out script state to a save game
  //
  long programSize, dataSize, stackHeapSize;
  amx_MemInfo(&mAMX, &programSize, &dataSize, &stackHeapSize);

  pProxy->mData.Clear();
  pProxy->mThisID =  mThis;
  pProxy->mOtherID = mOther;
  pProxy->mSleepNotifyType = mSleepNotify;
  pProxy->mSleepTime = mSleepTime;
  pProxy->mSleepingID = mSleepActor;
  pProxy->mbCutscene = mCutscene;
  pProxy->mDataSize = mDataSize;

  pProxy->mDataSize = dataSize+stackHeapSize;
  pProxy->mData.Init(pProxy->mDataSize);

  int index = 0;
  AMX_HEADER *hdr = (AMX_HEADER*)(mAMX.base);
  char* addr = (char*)((mAMX.data!=NULL) ? mAMX.data : mAMX.base+hdr->dat);

  while (addr && index < pProxy->mDataSize)
  {
    pProxy->mData(index) = addr[index];
    ++index;
  }

  pProxy->mParentIndex = mpScriptSys->GetCloneIndex(mParent);

  pProxy->mAMX_cip = mAMX.cip;
  pProxy->mAMX_frm = mAMX.frm;
  pProxy->mAMX_hea = mAMX.hea;
  pProxy->mAMX_hlw = mAMX.hlw;
  pProxy->mAMX_stk = mAMX.stk;
  pProxy->mAMX_stp = mAMX.stp;
}

void cScriptInstance::_ReportFunctionCall(const char *str)
{
  if (l_DebugFunctionCalls!=0)
  {
    mpScriptSys->DebugPrint(str);
  }
}

bool cScriptInstance::_GetFunctionIndex(int *ans,ePawnEventType type,const cGameObject* actor)
{
  static const int BUFFER_LEN = 512;
  char buffer[BUFFER_LEN]; 
  char debug_output[BUFFER_LEN]; 
  const char *entry_name = entry_names[static_cast< int >( type )];

  // first, try name specific function
  if (actor != NULL)
  {
    const char *object_name = actor->GetName();
    if (object_name != NULL)
    {
      sprintf(buffer,"%s_%s",entry_name,object_name);
      sprintf(debug_output,"%s...",buffer);
      _ReportFunctionCall(debug_output);
      int err = amx_FindPublic(&mAMX,buffer,ans);

      if (!err)
      {
        _ReportFunctionCall("Found!\n");
        return true;
      }
      _ReportFunctionCall("Not found.\n");

    }

    // next try master name 
    const char *db_name = actor->GetStats()->GetScriptName();
    if (db_name) // PB-added this check because the code below was crashing on PS3.
    {
      sprintf(buffer,"%s_DB_%s",entry_name,db_name);
      sprintf(debug_output,"%s...",buffer);
      _ReportFunctionCall(debug_output);
      int err = amx_FindPublic(&mAMX,buffer,ans);

      if (!err)
      {
        _ReportFunctionCall("Found!\n");
        return true;
      }
    }
    _ReportFunctionCall("Not found.\n");

  }
  // ok, just use the general handler (if there is one)
  sprintf(debug_output,"%s...",entry_name);
  _ReportFunctionCall(debug_output);
  int err = amx_FindPublic(&mAMX,entry_name,ans);

  if (!err)
  {
    _ReportFunctionCall("Found!\n");
    return true;
  }
  _ReportFunctionCall("Not found.\n");
  return false;
}

bool 
cScriptInstance::_Execute(int entry, int *retval)
{
  _CopyDataFromParent(); 
  cell ret = 0;
  int err = amx_Exec(&mAMX, &ret,entry);
  switch (err)
  {
  case AMX_ERR_SLEEP:
    mSleepNotify = (eScriptNotificationType)ret;
    // clone script if we're not already running a clone
    if (IsActive())
    {
      cScriptInstance *clone = SyNew cScriptInstance(mpScriptSys);
      clone->Clone(this, true);
    }
    SyAssertf(retval == NULL, "Query event sleeping in pawn script!  Bad Scripter!");
    break;
  case AMX_ERR_NONE:
    if (!IsActive())
    {
      mCompleted = true;
    }
    if (retval != NULL)
    {
      *retval = (int) ret;
    }
    break;
  default:
    SyAssertf(0,"Unknown AMX error: %d",err);
  }
  _CopyDataToParent(); 
  mpScriptSys->DebugRestore();
  return true;
}

void
cScriptInstance::_CopyDataFromParent()
{
  if (mParent == this)
  {
    return;
  }

  memcpy(mAMX.data, mParent->GetDataAddr(), mDataSize);
}

void
cScriptInstance::_CopyDataToParent()
{
  if (mParent == this)
  {
    return;
  }

  memcpy(mParent->GetDataAddr(), mAMX.data, mDataSize);
}

int
AMXAPI DebugCallbackFunction(AMX *amx)
{
  Titan *titan = Script_GetTitan(amx);

  titan->GetScriptSys()->DebugCallback(amx);
  return 0;
}

void 
cScriptInstance::DebugEnable()
{
  amx_SetDebugHook(&mAMX,DebugCallbackFunction);
}

void 
cScriptInstance::DebugDisable()
{
  amx_SetDebugHook(&mAMX,NULL);
}

void 
cScriptInstance::DebugLoadInfo(const char *originafilename)
{
#if DEBUG_SCRIPTS

  char filename[512];
  static const char *path = "game_assets/design/amx";
  sprintf(filename,"%s/%s",path,originafilename);

  mpAMX_DBG = new AMX_DBG;
  if (!srun_LoadDebugInfo(mpAMX_DBG,filename))
  {
    delete mpAMX_DBG;
  }
#endif 
}

const char *
cScriptInstance::DebugGetFilename(AMX *amx)
{
  if (mpAMX_DBG == NULL)
  {
    SyAssertf(0,"No debugging info");
    return NULL;
  }
  ucell address = (ucell) amx->cip;
  const char *ans;
  dbg_LookupFile(mpAMX_DBG,address,&ans);
  return ans;
}

int         
cScriptInstance::DebugGetLinenum(AMX *amx)
{
  if (mpAMX_DBG == NULL)
  {
    //SyAssertf(0,"No debugging info");
    return 0;
  }
  ucell address = (ucell) amx->cip;
  long ans;
  dbg_LookupLine(mpAMX_DBG,address,&ans);
  return ans;
}

void
cScriptInstance::DebugOutputVariables(AMX *amx,SyString &output)
{
  output.Concat("This: ");
  tGameObjectID thisID = Script_GetThisID(amx);
  tGameObjectID otherID = Script_GetOtherID(amx);

  Titan *titan = Script_GetTitan(amx);
  cGameObjectRegistry *registry = titan->GetRegistry();
  if (thisID == ID_NONE)
  {
    output.Concat("none\n");
  }
  else
  {
    cGameObject *obj = registry->Fetch(thisID);
    if (obj!=NULL)
    {
      output.Concat(obj->GetName());
    }
    output.Concat("\n");
  }
  output.Concat("Other: ");
  if (otherID == ID_NONE)
  {
    output.Concat("none\n");
  }
  else
  {
    cGameObject *obj = registry->Fetch(otherID);
    if (obj!=NULL)
    {
      output.Concat(obj->GetName());
    }
    output.Concat("\n");
  }

#if 0
  // this code is supposed to check the symbol data, find the symbols in scope, and add them to the window.
  // However, the symbol table doesn't seem to make sense.  The ident member, for example, is always 0,
  // which isn't a valid value for the enum.  Interestingly enough, it looks like the name for the variable
  // is at least initialized, to the string "(null)"...
  // Skipping for now.
  ucell codestart,codeend;
  int index;

  const AMX_DBG_SYMBOL *sym = NULL;
  ucell scopeaddr = amx->cip;
  codestart = codeend = 0;
  index = 0;
  char str_buffer[512];
  for ( ;; ) {
    /* find (next) matching variable */
    while (index < mpAMX_DBG->hdr->symbols
      && ((mpAMX_DBG->symboltbl[index]->ident == iFUNCTN )
      || (mpAMX_DBG->symboltbl[index]->codestart > scopeaddr || mpAMX_DBG->symboltbl[index]->codeend < scopeaddr)))
    {
      index++;
    }
    if (index >= mpAMX_DBG->hdr->symbols)
      break;
    sym = mpAMX_DBG->symboltbl[index];
    int value;
    AMX_HEADER *hdr=(AMX_HEADER *)amx->base;
    unsigned char *data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat;
    value =(int)( * (cell *)(data+(int)sym->address));
    sprintf (str_buffer,"%s : %d\n",sym->name,value);
    output.Concat(str_buffer);
    index++;
  } /* for */

#else  
  // just accessing public variables
  int num_pub_vars;
  amx_NumPubVars(amx,&num_pub_vars);


  char str_buffer[256];
  cell amx_addr;
  str_buffer[0] = '\0';

  AMX_HEADER *hdr=(AMX_HEADER *)amx->base;
  unsigned char *data=(amx->data!=NULL) ? amx->data : amx->base+(int)hdr->dat;

  for (int ii=0;ii<num_pub_vars;++ii)
  {
    amx_GetPubVar(amx, ii,str_buffer,&amx_addr);
    int value =(int)( * (cell *)(data+(int)amx_addr));
    sprintf (str_buffer,"%s : %d\n",str_buffer,value);
    output.Concat(str_buffer);
  }
#endif

}

//----------------------------------------------------------- cScriptSys

cScriptSys::cScriptSys(Titan *titan) :
mSource(NULL),
mActive(NULL),
mRunning(NULL),
mpTitan(titan),
mFilename(NULL),
mpSourceScriptData(NULL),
mbDebugging(false),
mSourceFile(NULL),
mSourceFilename(NULL),
mLastLinenum(-1),
mbRun(false),
mbStartup(false),
mRemoteUpdateTimer(0.0f),
mCutsceneSkipTime(0.0f),
mCutsceneSkipping(false),
mCutsceneEndFadeIn(false)
{
  SyAssertf(NUM_ENTRIES == NUM_EVENT_TYPES,"Missing event type function name");

#ifdef WRITE_SCRIPT_LOG
  FILE* pScriptDebugLogFile = fopen(SCRIPT_LOG_FILE_NAME, "wt");
  if (pScriptDebugLogFile)
  {
    fclose(pScriptDebugLogFile);
  }
#endif
}


cScriptSys::~cScriptSys()
{
  Clear();
  delete [] mFilename;
  delete [] mSourceFile;
  SyStr::FreeString(mSourceFilename);
}


cScriptInstance *cScriptSys::GetScriptInstance(int index) const
{
  if (index >= 0 && index < mClones.Size())
  {
    return mClones(index);
  }

  return mSource;
}

int cScriptSys::GetCloneIndex(cScriptInstance *pClone) const
{  
  for (int i=0; i<mClones.Size(); ++i)
  {
    if (mClones(i) == pClone)
    {
      return i;
    }
  }

  return -1;
}

void
cScriptSys::Clear()
{
  delete [] mpSourceScriptData;
  mpSourceScriptData = NULL;

  delete mSource;
  delete mActive;
  mSource = NULL;
  mActive = NULL;

  for (int ii=0;ii<mClones.Size();++ii)
  {
    delete mClones(ii);
  }

  mClones.Clear();
}

void cScriptSys::LoadLevelScript(const char *filename, bool bInProgess)
{
  SyPathname path(filename);

  path.Extension("amx");

#if defined(_DEBUG)
  Compile(path.Full().AsChar());
#endif 

  if (!LoadScript(path.Full().AsChar()))
  {
    GAME_ASSERT(ERROR_CODE, false, "Could not load script %s", path.Full().AsChar());
  }

  if (!bInProgess)
  {
    Startup();
  }
}

bool
cScriptSys::LoadScript(const char *filename)
{
  scriptFuncs_Init(mpTitan);

  Clear();

  if (mFilename == NULL || strcmp(mFilename,filename)!=0)
  {
    delete [] mFilename;
    mFilename = new char [strlen(filename)+1];
    strcpy(mFilename,filename);
  }

  mSource = SyNew cScriptInstance(this);
  if (!mSource->Load(filename))
  {
    delete mActive;
    mActive = NULL;
    return false;
  }

  long programSize, dataSize, stackHeapSize;
  amx_MemInfo(&(mSource->mAMX), &programSize, &dataSize, &stackHeapSize);

  delete [] mpSourceScriptData;
  mpSourceScriptData = new char [dataSize+stackHeapSize];
  memcpy(mpSourceScriptData, mSource->GetDataAddr(), dataSize+stackHeapSize);

  mSource->DebugLoadInfo(filename);


  mActive = SyNew cScriptInstance(this);
  mActive->Clone(mSource, true);


  if (mbDebugging)
  {
    mActive->DebugEnable();
  }

  return true;
}

void      
cScriptSys::ScriptEvent(ePawnEventType type, tGameObjectID actor, tGameObjectID target)
{
  // don't run script if we're not in control of the level object
  cLevelObject *leveobj =mpTitan->GetRegistry()->GetLevelObject();
  if (leveobj && leveobj->IsRemote())
  {
    return;
  }

  if (mActive != NULL)
  {
    cScriptInstance* pPrevRunning = mRunning;
    mRunning = mActive;
    cGameObject *actorObject = mpTitan->GetRegistry()->Fetch(actor);
    mActive->Run(actorObject,target,type);

    // custom callbacks:
    if ( actorObject )
    {
      const char* callback = 0;
      if ( PET_HIT == type )
      {
        callback = actorObject->GetStats()->GetHitScriptCallback();
      }
      else if ( PET_DEATH == type )
      {
        callback = actorObject->GetStats()->GetDeathScriptCallback();
      }

      if ( callback && callback[0] != '\0' )
      {
        mActive->Run(callback, actor, target);
      }
    }

    mRunning = pPrevRunning;
  }
}

//bool      
//cScriptSys::ScriptQuery(ePawnEventType type, tGameObjectID actor, tGameObjectID target,int *result)
//{
//  if (mActive != NULL)
//  {
//    cScriptInstance* pPrevRunning = mRunning;
//    mRunning = mActive;
//    mActive->Run(actor,target,type,result);
//    mRunning = pPrevRunning;
//  }
//  return false;
//}

void
cScriptSys::Update(float time)
{
  if (mpTitan->GetRegistry()->GetLevelObject() && mpTitan->GetRegistry()->GetLevelObject()->IsRemote())
  {
    return;
  }

  if (mbStartup)
  {
    mbStartup = false;
    ScriptEvent(PET_STARTUP,0,0);
  }

  for (int ii=0;ii<mClones.Size();++ii)
  {
    mRunning = mClones(ii);
    mClones(ii)->Update(time);
  }

  mRunning=NULL;

  // clean up deleted script

  for (int ii=mClones.Begin();ii!=mClones.End();)
  {
    if (mClones(ii)->IsCompleted())
    {
      delete mClones(ii);

      for (int jj = ii; mClones.Next(jj) != mClones.End(); jj=mClones.Next(jj))
      {
        mClones(jj) = mClones(mClones.Next(jj));
      }

      mClones.Erase();

      if (ii >= mClones.Size())
      {
        ii = mClones.End();
      }
    }
    else
    {
      ii = mClones.Next(ii);
    }
  }

  if (mCutsceneSkipping)
  {
    mCutsceneSkipTime += time;
    if (mCutsceneSkipTime > CUTSCENE_SKIP_FADE_OUT_TIME)
    {
      mpTitan->GetTitanUI()->FadeIn(CUTSCENE_FADE_IN_TIME);
      // kill latent cutscene scripts
      for (int ii=0;ii<mClones.Size();++ii)
      {               
        mClones(ii)->RemoveCutscene();
      }
      EndCutscene();
    }
  }

  if (mpTitan->IsNetworkGame())
  {
    mRemoteUpdateTimer += time;

    static const float REMOTE_SCRIPT_HEARTBEAT = 1.0f;

    if (mRemoteUpdateTimer > REMOTE_SCRIPT_HEARTBEAT)
    {
      cNetGlobalScriptStatePacket packet;

      packet.mbStartup = mbStartup;
      packet.mCutsceneSkipTime = mCutsceneSkipTime;
      packet.mCutsceneSkipping = mCutsceneSkipping;
      packet.mCutsceneEndFadeIn = mCutsceneEndFadeIn;
      GAME_ASSERT(ERROR_DESIGN, mCutsceneEndHandler.Length() < cNetGlobalScriptStatePacket::MAX_CUTSCENEENDHANDLER_NAME_LEN, "Cutscene end handler script function %s name greater than %d characters long", mCutsceneEndHandler.AsChar(), cNetGlobalScriptStatePacket::MAX_CUTSCENEENDHANDLER_NAME_LEN);
      strncpy(packet.mCutsceneEndHandler, mCutsceneEndHandler.AsChar(), cNetGlobalScriptStatePacket::MAX_CUTSCENEENDHANDLER_NAME_LEN-1);
      packet.mCutsceneEndHandler[cNetGlobalScriptStatePacket::MAX_CUTSCENEENDHANDLER_NAME_LEN-1] = 0;

      mSource->PackRemoteScriptData(mpSourceScriptData, &packet);
      mActive->PackRemoteScriptData(mpSourceScriptData, &packet);

      for (int ii=mClones.Begin(); ii!=mClones.End(); ii=mClones.Next(ii))
      {
        mClones(ii)->PackRemoteScriptData(mpSourceScriptData, &packet);
      }

      static char s_scriptBuf[16384];

      int len = packet.PackBuffer(s_scriptBuf, sizeof(s_scriptBuf));
      SyAssert((size_t)len <= sizeof(s_scriptBuf));

      mpTitan->GetPeeringNetwork()->ObjectBroadcast(cLevelObject::LEVEL_OBJECT_GAME_ID, s_scriptBuf, len);

      mRemoteUpdateTimer = 0.0f;
    } 
  }
}

//void      
//cScriptSys::NotifyDestinationReached(tGameObjectID actor) // notify script that an actor has reached his destination.
//{
//  for (int ii=0;ii<mClones.Size();++ii)
//  {
//    mRunning = mClones(ii);
//    mClones(ii)->Notify(SNT_PATH,actor);
//  }
//  mRunning = NULL;
//}

//void      
//cScriptSys::NotifyAnimationCompleted(tGameObjectID actor)
//{
//  for (int ii=0;ii<mClones.Size();++ii)
//  {
//    mRunning = mClones(ii);
//    mClones(ii)->Notify(SNT_ANIMATION,actor);
//  }
//  mRunning = NULL;
//}

void      
cScriptSys::AddClone(cScriptInstance *instance, bool bSetActive)
{
  if (bSetActive)
  {
    if (instance == mActive)
    {
      return;
    }

    mClones.Add(mActive);
    mActive = instance;
  }
  else
  {
    mClones.Add(instance);
  }
}

bool      
cScriptSys::Recompile() // recompiles current script
{
  Compile(mFilename);
  LoadScript(mFilename);
  Startup();
  return false;
}


bool
cScriptSys::Compile(const char *filename)
{
  if (filename == NULL)
  {
    return false;
  }
  // recompile script

#if DEBUG_SCRIPTS 

  static const char *ERRFILENAME = "pawn_error.txt";
  char dest_name[256];
  char src_name[256];

  static const char *dest_path = "game_assets\\design\\amx";
  sprintf(dest_name,"%s\\%s",dest_path,filename);

  static const char *src_path = "game_assets\\design\\script";
  sprintf(src_name,"%s\\%s",src_path,filename);
  char *extension = &src_name[strlen(src_name)];

  while (*extension != '.')
  {
    if (extension == src_path)
    {
      SyAssert(0);
      return false;
    }
    extension--;
  }
  strcpy(extension,".p");

  // check ages
  HANDLE src_file = CreateFile(src_name,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);

  if (src_file == INVALID_HANDLE_VALUE)
  {
    return false; // there is no script.
  }

  HANDLE dest_file = CreateFile(dest_name,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);

  if (dest_file != INVALID_HANDLE_VALUE  && srun_ProgramSize(dest_name) > 0)
  {

 
    // check age
    HANDLE inc_file = CreateFile("game_assets\\design\\script\\titan.inc",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);

    if (inc_file == INVALID_HANDLE_VALUE)
    {
      SyAssertf(0,"Include file not found?");
      return true; // no inc file?
    }

    // check age
    HANDLE prop_file = CreateFile("game_assets\\design\\script\\props.p",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);

    if (prop_file == INVALID_HANDLE_VALUE)
    {
      SyAssertf(0,"prop.p file not found?");
      return true; // no prop.p file?
    }
    // check age
    HANDLE character_file = CreateFile("game_assets\\design\\script\\characters.p",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);

    if (prop_file == INVALID_HANDLE_VALUE)
    {
      SyAssertf(0,"prop.p file not found?");
      return true; // no prop.p file?
    }

    // check file dates
    FILETIME src_time;
    FILETIME dest_time;
    FILETIME inc_time;
    FILETIME prop_time;
    FILETIME character_time;


    GetFileTime(src_file,NULL,NULL,&src_time);
    GetFileTime(dest_file,NULL,NULL,&dest_time);
    GetFileTime(inc_file,NULL,NULL,&inc_time);
    GetFileTime(prop_file,NULL,NULL,&prop_time);
    GetFileTime(character_file,NULL,NULL,&character_time);

    if (CompareFileTime(&src_time,&dest_time) < 0 &&
        CompareFileTime(&inc_time,&dest_time) < 0 &&
        CompareFileTime(&prop_time,&dest_time) < 0 &&
        CompareFileTime(&character_time,&dest_time)< 0)

    {
      CloseHandle(src_file);
      CloseHandle(dest_file);
      CloseHandle(inc_file);
      CloseHandle(prop_file);
      CloseHandle(character_file);

      return false; // no need to compile; dest is newer than source.
    }
    CloseHandle(dest_file);
    CloseHandle(inc_file);
    CloseHandle(prop_file);
    CloseHandle(character_file);
  }
  CloseHandle(src_file);
  // erase previous compile errors, if any

  char command[512];
  if (_access(ERRFILENAME,0)>=0)
  {
    sprintf(command,"del %s",ERRFILENAME);
    system(command);
  }

  // erase destination file (has old data)
  if (_access(dest_name,0)>=0)
  {
    sprintf(command,"del %s",dest_name);
    system(command);
  }        
  // compile
  sprintf(command,".\\game_assets\\design\\script\\pawncc -p.\\game_assets\\design\\script\\titan.inc -i.\\game_assets\\design\\script -e%s -d2 %s",ERRFILENAME,src_name);
  int retval = system(command);
  if (retval != 0)
  {
    sprintf(command,"notepad %s",ERRFILENAME);
    system(command);

    if (retval > 1)  // if it's just warnings, keep on going
    {
      return false;
    }
  }

  const char *end = &dest_name[strlen(dest_name)-1];

  while (end >= dest_name && *end != '\\' && *end != '/')
  {
    --end;
  }
  ++end;
  sprintf(command,"move .\\%s %s",end,dest_name);
  system(command);
  if (mSourceFilename)
  {
    mSourceFilename[0] = '\0';
  }
  return true;
#else
  return true;
#endif

}


void      
cScriptSys::EnableDebugging()
{
#if DEBUG_SCRIPTS
  if (mbDebugging == true)
  {
    return;
  }

  mbDebugging = true;
  // install debug hook
  if (mActive)
    mActive->DebugEnable();

  for (int ii=0;ii<mClones.Size();++ii)
  {
    mClones(ii)->DebugEnable();
  }
#endif
}

void      
cScriptSys::DisableDebugging()
{
#if DEBUG_SCRIPTS
  if (mbDebugging == false)
  {
    return;
  }

  mbDebugging = false;
  // install debug hook
  if (mActive)
    mActive->DebugDisable();

  for (int ii=0;ii<mClones.Size();++ii)
  {
    mClones(ii)->DebugDisable();
  }
#endif
}

bool 
cScriptSys::LoadSourceFile(const char *filename)
{

#if DEBUG_SCRIPTS
  if (mSourceFilename && strcmp(filename,mSourceFilename)== 0)
  {
    return false; // already loaded
  }
#if 0
  static const char *src_path = "game_assets/design/script";
  sprintf(src_name,"%s\\%s",src_path,mFilename);
  char *extension = &src_name[strlen(src_name)];

  while (*extension != '.')
  {
    if (extension == src_path)
    {
      SyAssert(0);
      return;
    }
    extension--;
  }
  strcpy(extension,".p");
#endif

  delete mSourceFile;
  SyStr::FreeString(mSourceFilename);
  mSourceFilename = SyStr::AllocateString(filename);

  // load source file
  SyFile file;
  if ((file.Open(filename, SYFILE_RONLY )) < 0)
    return false;
  if (file.Seek(0,SyFile::SeekOrigin::End)!=0)
    return false;
  int size = (int)file.Tell();
  file.Seek(0,SyFile::SeekOrigin::Set);
  mSourceFile = new char[size + 1];
  file.Read(mSourceFile,  size);
  // trim off garbage at end of file
  while (size > 0 && mSourceFile[size-1]!='\n')
  {
    size--;
  }
  mSourceFile[size]='\0';
  file.Close();
  return true;

#else
  return false;
#endif

}

void
cScriptSys::DebugCallback(AMX *amx)
{
#if DEBUG_SCRIPTS
   // load up file
  const char *string = mSource->DebugGetFilename(amx);
  // we don't want to debug the titan.inc file
  if (strcmp(string,"game_assets/design/script/titan.inc")==0)
  {
    return;
  }
  bool newfile = LoadSourceFile(string);  
  int line_num = mSource->DebugGetLinenum(amx);

 
  // sometimes you get two callbacks in a row (usually for new functions)
  // this skips the second callback.
  if (!newfile && line_num == mLastLinenum)
  {
    return; 
  }
  mLastLinenum = line_num;
  mpTitan->SetUnfocused(true);
  SyString variables;
  variables.Concat("File: ");
  variables.Concat(string);
  variables.Concat("\n");
 
  mSource->DebugOutputVariables(amx,variables);
  switch (mpTitan->GetTitanUI()->PawnDebugUI(mSourceFile,line_num,variables.AsChar()))
  {
    // SGC todo: Move this enum (from PawnDebug.h) somewhere I can access it here.
  case 0: // run
    mbRun = true;
    amx_SetDebugHook(amx,NULL);
    DisableDebugging();
    break;
  case 1: // step
    break;
  case 2: // stop debugging
    DisableDebugging();
    break;
  }
  mpTitan->SetUnfocused(false);

#endif
}


void
cScriptSys::DebugRestore()
{
#if DEBUG_SCRIPTS
  if (mbRun)
  {
    mbRun = false;
    EnableDebugging();
  }
#endif
}

void 
cScriptSys::Startup()
{
  mbStartup = true;
}

void      
cScriptSys::DebugPrint(const char *output)
{
  mpTitan->GetTitanUI()->PawnDebugOutput(output);
  
#ifdef WRITE_SCRIPT_LOG
  FILE* pScriptDebugLogFile = fopen(SCRIPT_LOG_FILE_NAME, "at");
  if (pScriptDebugLogFile)
  {
    fprintf(pScriptDebugLogFile, output);
    fprintf(pScriptDebugLogFile, "\n");
    fclose(pScriptDebugLogFile);
  }
#endif
}


void      
cScriptSys::NetworkReceiveBroadcast(const char *state,int maxlen)
{
  cNetPacket::eNetPacketType type;

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

  switch (type)
  {
    case cNetPacket::NET_SCRIPT_FUNC:
      {
        cNetScriptFuncPacket packet;

        packet.UnpackBuffer(state, maxlen);

        packet.mpTitan = mpTitan;

        scriptFuncs_ProcessPacket(&packet);
      }
      break;

    case cNetPacket::NET_GLOBAL_SCRIPT_STATE:
      {
        cNetGlobalScriptStatePacket packet;
        packet.UnpackBuffer(state, maxlen);

        mbStartup = packet.mbStartup;
        mCutsceneEndHandler = packet.mCutsceneEndHandler;
        mCutsceneSkipping = packet.mCutsceneSkipping;
        mCutsceneSkipTime = packet.mCutsceneSkipTime;
        mCutsceneEndFadeIn = packet.mCutsceneEndFadeIn;

        SyAssertf(packet.GetNumScripts() >= 2, "Net script state packet has bad number of scripts");

        if (packet.GetNumScripts() >= 2)
        {
          mSource->UnpackRemoteScriptData(mpSourceScriptData, packet.GetScriptData(0));
          mActive->UnpackRemoteScriptData(mpSourceScriptData, packet.GetScriptData(1));

          for (int ii=2; ii<packet.GetNumScripts(); ++ii)
          {
            while (ii-2 >= mClones.Size())
            {
              cScriptInstance *pClone = SyNew cScriptInstance(this);
              pClone->Clone(mSource, false);
            }

            mClones(ii-2)->UnpackRemoteScriptData(mpSourceScriptData, packet.GetScriptData(ii));
          }
        }
      }
      break;

    default:
      SyAssertf(false, "Unknown packet in cScriptSys");
      break;
  }
}

void      
cScriptSys::NetworkReceiveMessage(const char *state,int maxlen)
{
  cNetPacket::eNetPacketType type;

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

  switch (type)
  {
    case cNetPacket::NET_SCRIPT_FUNC:
      {
        cNetScriptFuncPacket packet;

        packet.UnpackBuffer(state, maxlen);

        packet.mpTitan = mpTitan;

        scriptFuncs_ProcessPacket(&packet);
      }
      break;

    default:
      SyAssertf(false, "Unsupported packet in cScriptSys");
      break;
  }
}

void      
cScriptSys::SetCutsceneEndHandler(const char *skip)
{
  if (skip[0] != '\0')
  {
    SyAssert(mRunning != NULL);
    mRunning->SetCutscene();
  }
  mCutsceneEndHandler.Init(skip);
}

bool cScriptSys::HasCutsceneEndHandler()
{
  return mCutsceneEndHandler.Length() > 0;
}

void      
cScriptSys::EndCutscene()
{
  if (mCutsceneEndHandler.Length() > 0)
  {
    mActive->Run(mCutsceneEndHandler.AsChar());
  }

  mCutsceneSkipping = false;
  mCutsceneEndHandler.Init("");
  mpTitan->GetCameraController()->EndScriptCam();
  mpTitan->EndCutScene();

  if (mCutsceneEndFadeIn)
  {
    mpTitan->GetTitanUI()->FadeIn(CUTSCENE_FADE_IN_TIME);
    mCutsceneEndFadeIn = false;
  }

  mpTitan->BlankScreen(false);
}

void      
cScriptSys::SkipCutscene(bool bFadeOut)
{
  if (!mCutsceneSkipping)
  {
    if (mCutsceneEndHandler.Length() > 0 || !bFadeOut)
    {
      mCutsceneSkipping = true;

      mCutsceneEndFadeIn = bFadeOut;

      if (bFadeOut)
      {
        mpTitan->GetTitanUI()->FadeOut(CUTSCENE_SKIP_FADE_OUT_TIME);
        mCutsceneSkipTime = 0.0f;
      }
      else
      {
        mCutsceneSkipTime = CUTSCENE_SKIP_FADE_OUT_TIME;
      }
    }
  }
}

extern AMX_NATIVE_INFO l_natives[];

int32 cScriptSys::ProcessScriptFunction(const SyString &str)
{
  SyVector<SyString> tokens;
  ProcessScriptFunctionHelper(str, tokens);

  if (tokens.Size() <= 0)
  {
    return false;
  }

  cNetScriptFuncPacket packet;
  packet.mIDENTIFIER = cNetScriptFuncPacket::TEST_IDENTIFIER;

  SyAssert(sizeof(cell)==4);

  SyResourceID functionID = SyHashResourceID(tokens(0).AsChar());
  int numParams = tokens.Size() - 1;

  int32 retVal = 0;
  cell *pParams = new cell[numParams+1];
  pParams[0] = 0;

  packet.mNumParams = numParams;
  int numParamStrs = 0;

  for (int i=1; i<numParams+1; ++i)
  {
    SyAssert(tokens(i).Length() > 0);

    if ((tokens(i).AsChar()[0] == '!' &&
      tokens(i).Length() > 1 &&
      tokens(i).AsChar()[0] == '\"') || 
      tokens(i).AsChar()[0] == '\"')
    {
      pParams[i] = 0;
      ++numParamStrs;
    }
    else if (SyStr::Stricmp(tokens(i).AsChar(), "true") == 0)
    {
      pParams[i] = 1;
    }
    else if (SyStr::Stricmp(tokens(i).AsChar(), "false") == 0)
    {
      pParams[i] = 0;
    }
    else
    {
      pParams[i] = atoi(tokens(i).AsChar());
    }
  }

  packet.Init(functionID, reinterpret_cast<int32*>( pParams ), numParams);
  packet.mpTitan = Titan::Get();

  for (int i=1; i<numParams+1; ++i)
  {
    if ((tokens(i).AsChar()[0] == '!' &&
      tokens(i).Length() > 1 &&
      tokens(i).AsChar()[0] == '\"'))
    {
      SyString strParam;
      strParam.Init(tokens(i), 2, tokens(i).Length()-2);
      packet.AddString(i, (char*)strParam.AsChar());
    }
    else if (tokens(i).AsChar()[0] == '\"')
    {
      SyString strParam;
      strParam.Init(tokens(i), 1, tokens(i).Length()-2);
      packet.AddString(i, (char*)strParam.AsChar());
    }
  }

  AMX_NATIVE_INFO *info = l_natives;
  bool bFoundFunc = false;

  while (info->name != NULL && !bFoundFunc)
  {
    SyResourceID nameID = SyHashResourceID(info->name);
    if (nameID == functionID)
    {
      retVal = (*info->func)((AMX *)(&packet),reinterpret_cast<cell*>( &packet.mParams[0] ));
      bFoundFunc = true;
    }

    info++;
  }

  if (!bFoundFunc)
  {
    // clone the script to run the function (gets deleted by script sys)
    cScriptInstance* pClone = SyNew cScriptInstance(Titan::Get()->GetScriptSys());
    pClone->Clone(Titan::Get()->GetScriptSys()->mSource, false); 
    int funcIndex;
    int err = amx_FindPublic(&pClone->mAMX, tokens(0).AsChar(), &funcIndex);
    if ( err == 0 )
    {
      pClone->mThis = static_cast<TitanInputHandler*>(Titan::Get()->GetInputHandler(0))->GetGameObject()->GetID();
      pClone->mOther = ID_NONE;
      pClone->_Execute(funcIndex);
      bFoundFunc = true;
    }
    else
    {
      pClone->mCompleted = true;
    }
  }

  delete [] pParams;

  GAME_ASSERT(ERROR_DESIGN, bFoundFunc, "Could not find script function '%s'", tokens(0).AsChar());
  return retVal;
}

int32 cScriptSys::ProcessScriptFunctionHelper(const SyString &str, SyVector<SyString> &arStrings)
{
  arStrings.Clear();

  bool bLastCharWhiteSpace = true;
  bool bFoundFirstParen = false;
  SyString strToken;

  for(int i = 0; i < str.Length(); i++)
  {
    char8 ch = str.AsChar()[i];

    if (ch == '\"' || (ch == '!' && i+1 < str.Length() && str.AsChar()[i+1]== '\"'))
    {
      if (ch == '!')
      {
        ++i;
      }

      bool bFoundCloseQuote = false;
      strToken += str.AsChar()[i++];

      while (i < str.Length() && !bFoundCloseQuote)
      {
        strToken += str.AsChar()[i];

        if (str.AsChar()[i] == '\"')
        {
          bFoundCloseQuote = true;
        }

        ++i;
      }

      arStrings.Add(strToken);
      strToken.Clear();

      GAME_ASSERT(ERROR_DESIGN, bFoundCloseQuote, "Non matching quotes in script command - %s", strToken.AsChar());
    }
    else if (!bFoundFirstParen && ch == '(')
    {
      arStrings.Add(strToken);
      strToken.Clear();
      bFoundFirstParen = true;
    }
    else if (bFoundFirstParen && ch == '(')
    {
      // found nested function call, recurse
      bool bFoundCloseParen = false;
      while (i < str.Length() && !bFoundCloseParen)
      {
        strToken += str.AsChar()[i];

        if (str.AsChar()[i] == ')')
        {
          bFoundCloseParen = true;
        }

        ++i;
      }

      if (bFoundCloseParen)
      {
        char buf[1024];
        int retVal = ProcessScriptFunction(strToken);
        sprintf(buf, "%d", retVal);
        strToken = buf;
        arStrings.Add(strToken);
        strToken.Clear();
      }
    }
    else if (bFoundFirstParen && ch == ')')
    {
      if(strToken.Length() > 0)
      {
        arStrings.Add(strToken);
        strToken.Clear();
      }
      break;
    }
    else if (ch == ' ' || ch == '\t' || ch == ',')
    {
      if(!bLastCharWhiteSpace)
      {
        if(strToken.Length() > 0)
        {
          arStrings.Add(strToken);
          strToken.Clear();
        }
      }
      bLastCharWhiteSpace = true;
    }
    else
    {
      strToken += ch;
      bLastCharWhiteSpace = false;
    }
  }

  if(strToken.Length() > 0)
  {
    arStrings.Add(strToken);
  }

  return arStrings.Size();
}

void cScriptSys::PushScriptData(SyVector<cScriptInstanceDataProxy*>& proxies)
{
  if (proxies.Size() < 2 || !proxies(0) || !proxies(1))
  {
    return;
  }

  mSource->ReadScriptData(proxies(0));
  mActive->ReadScriptData(proxies(1));

  for (int i=2; i<proxies.Size(); ++i)
  {
    while (i-2 >= mClones.Size())
    {
      cScriptInstance *pClone = SyNew cScriptInstance(this);
      pClone->Clone(mSource, false);
    }

    mClones(i-2)->ReadScriptData(proxies(i));
  }
}

void cScriptSys::PullScriptData(SyVector<cScriptInstanceDataProxy*>& proxies)
{
  SyAssert(proxies.Size() == 0);
  
  cScriptInstanceDataProxy *pProxy = SyNew cScriptInstanceDataProxy();
  mSource->WriteScriptData(pProxy);
  proxies.Add(pProxy);

  pProxy = SyNew cScriptInstanceDataProxy();
  mActive->WriteScriptData(pProxy);
  proxies.Add(pProxy);

  for (int i=mClones.Begin(); i!=mClones.End(); i=mClones.Next(i))
  {
    pProxy = SyNew cScriptInstanceDataProxy();
    mClones(i)->WriteScriptData(pProxy);
    proxies.Add(pProxy);
  }
}


//-------------------------------------------------------- private cScriptInstanceDataProxy
cScriptInstanceDataProxy::cScriptInstanceDataProxy()
: mThisID(ID_NONE),
  mOtherID(ID_NONE),
  mDataSize(0),
  mSleepNotifyType(0),
  mSleepTime(0.0f),
  mSleepingID(ID_NONE),
  mbCutscene(false),
  mParentIndex(-1)
{
  InitPropObject(mCLASSID);
}

cScriptInstanceDataProxy::~cScriptInstanceDataProxy()
{
  mData.Clear();
}

int cScriptInstanceDataProxy::InitPropClass()
{
  AddClass( mCLASSID, 
            "cScriptInstanceDataProxy", 
            Creator,     
            mCLASSID, 
            0 ); 

  AddInt32Property(mCLASSID,PropId_ThisID,SyMemberOffset(cScriptInstanceDataProxy, mThisID), "mThisID");
  AddInt32Property(mCLASSID,PropId_OtherID,SyMemberOffset(cScriptInstanceDataProxy, mOtherID), "mOtherID");
  AddArrayProperty(mCLASSID,PropId_Data,SyMemberOffset(cScriptInstanceDataProxy, mData), "mData", SYPROPTYPE_UINT32);
  AddInt32Property(mCLASSID,PropId_DataSize,SyMemberOffset(cScriptInstanceDataProxy, mDataSize), "mDataSize");
  AddInt32Property(mCLASSID,PropId_SleepNotifyType,SyMemberOffset(cScriptInstanceDataProxy, mSleepNotifyType), "mSleepNotifyType");
  AddFloat32Property(mCLASSID,PropId_SleepTime,SyMemberOffset(cScriptInstanceDataProxy, mSleepTime), "mSleepTime");
  AddInt32Property(mCLASSID,PropId_SleepingID,SyMemberOffset(cScriptInstanceDataProxy, mSleepingID), "mSleepingID");
  AddBoolProperty(mCLASSID,PropId_Cutscene,SyMemberOffset(cScriptInstanceDataProxy, mbCutscene), "mbCutscene");
  AddBoolProperty(mCLASSID,PropId_ParentIndex,SyMemberOffset(cScriptInstanceDataProxy, mParentIndex), "mParentIndex");

  AddInt32Property(mCLASSID,PropId_AMX_cip,SyMemberOffset(cScriptInstanceDataProxy, mAMX_cip), "mAMX_cip");
  AddInt32Property(mCLASSID,PropId_AMX_frm,SyMemberOffset(cScriptInstanceDataProxy, mAMX_frm), "mAMX_frm");
  AddInt32Property(mCLASSID,PropId_AMX_hea,SyMemberOffset(cScriptInstanceDataProxy, mAMX_hea), "mAMX_hea");
  AddInt32Property(mCLASSID,PropId_AMX_hlw,SyMemberOffset(cScriptInstanceDataProxy, mAMX_hlw), "mAMX_hlw");
  AddInt32Property(mCLASSID,PropId_AMX_stk,SyMemberOffset(cScriptInstanceDataProxy, mAMX_stk), "mAMX_stk");
  AddInt32Property(mCLASSID,PropId_AMX_stp,SyMemberOffset(cScriptInstanceDataProxy, mAMX_stp), "mAMX_stp");

  return 0;
}

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

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

  return(pObject);
}

void RegPropClasses_ScriptSys()
{  
  cScriptInstanceDataProxy::InitPropClass();
}


//-------------------------------------------------------- private functions


size_t srun_ProgramSize(const char *filename)
{
  SyFile file;
  AMX_HEADER hdr;

  if ((file.Open(filename,SYFILE_RONLY | SYFILE_BINARY)) < 0)
    return 0;
  file.Read(&hdr, sizeof(hdr));
  file.Close();

  amx_Align16(&hdr.magic);
  amx_Align32((uint32_t*)&hdr.stp);
  return(hdr.magic==AMX_MAGIC) ? (size_t)hdr.stp : 0;
}

int srun_LoadProgram(AMX *amx,const char *filename, void *memblock)
{
  SyFile file;
  AMX_HEADER hdr;

  if ((file.Open(filename, SYFILE_RONLY | SYFILE_BINARY)) < 0)
    return AMX_ERR_NOTFOUND;
  file.Read(&hdr, sizeof hdr);
  file.Seek(0,SyFile::Set);
  amx_Align32((uint32_t*)&hdr.size);
  file.Read(memblock, (int)hdr.size);
  file.Close();

  memset(amx, 0, sizeof(*amx));
  return amx_Init(amx, memblock);
}

bool srun_LoadDebugInfo(AMX_DBG *amx,const char *filename)
{
  FILE *fp;

  // sgc: Note debug info doesn't work on PS3 'cuz it uses fopen.
  if ((fp = fopen(filename, "rb")) < 0)
    return false;

  bool retval;
  retval = (dbg_LoadInfo(amx,fp)==AMX_ERR_NONE);
  fclose(fp);
  return retval;
}

//-------------------------------------------------------------------------------------- Public Functions
cScriptInstance *
Script_GetInstance(AMX *amx)
{
  SyAssert(!scriptFuncs_IsRemote(amx));
  return (cScriptInstance*)(amx);
}
tGameObjectID 
Script_GetThisID(AMX *amx)
{
  SyAssert(!scriptFuncs_IsRemote(amx));
  return Script_GetInstance(amx)->GetThis();
}
tGameObjectID 
Script_GetOtherID(AMX *amx)
{
  SyAssert(!scriptFuncs_IsRemote(amx));
  return Script_GetInstance(amx)->GetOther();
}

Titan *       
Script_GetTitan(AMX *amx)
{
  if (scriptFuncs_IsRemote(amx) || scriptFuncs_IsTest(amx))
  {
    cNetScriptFuncPacket *packet = (cNetScriptFuncPacket *)amx;
    return packet->mpTitan;
  }

  return Script_GetInstance(amx)->GetScriptSys()->GetTitan();
}

void 
Script_SetWaitTime(AMX *amx,int time_msecs)
{
  SyAssert(!scriptFuncs_IsRemote(amx));
  Script_GetInstance(amx)->SetSleepTime((float)time_msecs/1000.0f);
}

void
Script_RegisterTuningVariables()
{
  gTuningSys.AddInt(&l_DebugFunctionCalls,"Script_DebugFunctionCalls");
}

// EOF
