//------------------------------------------------------------
// Copyright Sandlot Games, 2007
// author: Michael Felice
// file: svr_saveobject.cs
// brief:
//    This handles the saving and loading of objects through
//    script, ensuring that the dynamic fields of objects and
//    timers are appropriately saved and initialized on load.
//    This is also used to access source saving and loading.
//    This is more complicated than the other script saving
//    and loading files.  After the mission file is saved,
//    several server objects are saved out in source and in
//    script.  These objects must be ordered to maintain the
//    loading of the file, which means the client-side save
//    has to synch with the server.  There is a limited packet
//    size that can be sent, so to guarantee the order, the
//    packet is filled, sent to the client, then sent back to
//    the server to continue loading, back-and-forth, until
//    the objects have been loaded completely.
//------------------------------------------------------------

$MaxSaveObjects = 50;
$DoneLoading = true;

// SAVING OBJECTS

function SaveScenario(%file, %function)
{
   $DoneLoading = false;
   
   // before doing anything, delete the old save file and dso files
   slgDeleteFile(%file @ "*");
   
   // load the saving gui and pause the game to prevent anything from
   // changing while the game is being saved
   $popDialogList = $popDialogList @ "GameSavingGui ";
   Canvas.pushDialog(GameSavingGui);
   slgStackPause();
   
   // open the save file
   slgStartSaveGame(%file @ ".sav");
   
   // save any datablocks that need to be saved out
   $DisasterManager.saveDatablocksToFile();
   
   // save the mission file
   MissionGroup.save(%file);
   compile(%file);
   slgDeleteFile(%file);

   // start saving to the save file
   %client = ClientGroup.getObject(0);
   serverCmdSaveObjectData(%client);
   
   // set up the monitoring function that will check when the save
   // is complete, which will unpause the game and remove any guis
   // and call the return save function
   onSaveGameDone(%function);
}

// this function should be called every time the game should be
// saved to a particular file in a profile slot
// %file = the file to save to
// %slot = the profile slot to save to
// %function = the function to call when save is complete
// %resources = if true, resources will be saved to the file
function SaveGame(%slot, %file, %function)
{
   $DoneLoading = false;
   
   %file = filePath(%file) @ "/" @ fileBase(%file);
   
   // before doing anything, delete the old save file and dso files
   slgDeleteFile(%file @ "*");
   
   // copy temporary files to the save slot
   if (%slot >= 0)
   {
      %destination = slgGetProfileSaveGameLocation() @ "/Slot " @ %slot;

      slgCopyFile(slgGetProfileSaveGameLocation() @ "/mission.slg.temp.dso", %destination);
      slgCopyFile(slgGetProfileSaveGameLocation() @ "/mission.slg.temp.sav", %destination);
      slgCopyFile(slgGetProfileSaveGameLocation() @ "/mission.slg.restart.dso", %destination);
      slgCopyFile(slgGetProfileSaveGameLocation() @ "/mission.slg.restart.sav", %destination);
   }
   
   // load the saving gui and pause the game to prevent anything from
   // changing while the game is being saved
   $popDialogList = $popDialogList @ "GameSavingGui ";
   Canvas.pushDialog(GameSavingGui);
   slgStackPause();
   
   // open the save file
   slgStartSaveGame(%file @ ".sav", %slot);
   
   // save any datablocks that need to be saved out
   $DisasterManager.saveDatablocksToFile();
   
   // save the mission file
   MissionGroup.save(%file);
   compile(%file);
   slgDeleteFile(%file);

   // start saving to the save file
   %client = ClientGroup.getObject(0);
   serverCmdSaveObjectData(%client);
   
   // set up the monitoring function that will check when the save
   // is complete, which will unpause the game and remove any guis
   // and call the return save function
   onSaveGameDone(%function);
}

function serverCmdSaveGame(%client, %slot, %file, %function)
{
   SaveGame(%slot, %file, %function);
}

function onSaveGameDone(%function)
{
   if ($DoneLoading == true)
   {
      // close the save game file
      slgStopSaveGame();

      // unpause the game
      slgStackUnpause();
      
      // close the guis
      slgCloseSaveGui();
      
      // call the closing function
      if (%function !$= "")
      {
         %count = getWordCount(%function);
         if (%count == 0)
         {
            call(%function);
            return;
         }
         
         %param = getWord(%function, 1);
         %function = getWord(%function, 0);
         call(%function, %param);
      }
      
      return;
   }
   
   // continue checking for the saving file to finish
   schedule(100, 0, "onSaveGameDone", %function);
}








$MissionGroupIndex = 0;
$MissionGroupCount = 4;
$MissionGroupList[0] = MissionGroup;
$MissionGroupList[1] = BuildingGroup;
$MissionGroupList[2] = CharacterGroup;
$MissionGroupList[3] = PropGroup;

// this function is called from the client to initialize saving
// the game on the server
function serverCmdSaveObjectData(%client)
{
   // save the mission group count
   %count = MissionGroup.getCount();
   slgSaveInt(%count);
   
   // save the building group count
   %count = BuildingGroup.getCount();
   slgSaveInt(%count);
   
   // save the character group count
   %count = CharacterGroup.getCount();
   slgSaveInt(%count);
   
   // save the prop group count
   %count = PropGroup.getCount();
   slgSaveInt(%count);
   
   $MissionGroupIndex = 0;
   serverCmdSaveObjectGroup(%client, 0);
}

// this function saves a chunk of server-side objects
function serverCmdSaveObjectGroup(%client, %start)
{
   %missionGroup = $MissionGroupList[$MissionGroupIndex];
   %count = %missionGroup.getCount();
   
   // before sending the objects, make sure all of the objects
   // that will be sent in this packet have been ghosted and
   // are available for saving on the client
   %objectCount = 0;
   for (%index = %start; %index < %count; %index++)
   {
      // only save game object objects
      %object = %missionGroup.getObject(%index);
      if (%object.isMemberOfClass("SLGameObj") == false)
      {
         continue;
      }
 
      // check if the object has not been ghosted yet     
      %objectCount++;
      %ghostID = %client.getGhostId(%object);
      if (%ghostID == -1)
      {
         schedule(100, 0, serverCmdSaveObjectGroup, %client, %start);
         return;
      }
      
      // check if this packet is full
      if (%objectCount == $MaxSaveObjects)
      {
         break;
      }
   }
   
   // save all of the game objects
   %data = "";
   %objectCount = 0;
   for (%index = %start; %index < %count; %index++)
   {
      // if this object is not a game object, we are not saving it
      %object = %missionGroup.getObject(%index);
      if (%object.isMemberOfClass("SLGameObj") == false)
      {
         continue;
      }
      
      // save the game object
      if (%object.saveToFile() == false)
      {
         error("Problem saving object data: failed to save game object data");
         return;
      }
      
      // save the destroy state of the object
      %hasDestroy = isObject(%object.destroyTimer);
      slgSaveBool(%hasDestroy);
      if (%hasDestroy == true)
      {
         %totalTime = %object.destroyTimer.getTotalTime();
         %elapsedTime = %object.destroyTimer.getElapsedTime();
         
         slgSaveFloat(%totalTime);
         slgSaveFloat(%elapsedTime);
         slgSaveString(%object.destroyFunc);
      }
      
      // save explosion timer for dynamite
      %hasExplosion = isObject(%object.explodeTimer);
      slgSaveBool(%hasExplosion);
      if (%hasExplosion == true)
      {
         %totalTime = %object.explodeTimer.getTotalTime();
         %elapsedTime = %object.explodeTimer.getElapsedTime();
         
         slgSaveFloat(%totalTime);
         slgSaveFloat(%elapsedTime);
      }
      
      %objectCount++;
      %ghostID = %client.getGhostId(%object);
      %data = %data @ %ghostID @ " ";
      
      if (%objectCount == $MaxSaveObjects)
      {
         commandToClient(%client, 'SaveObjectData', %data, %index + 1);
         return;
      }
   }
   
   // check if we will be done loading objects after this
   $MissionGroupIndex++;
   if ($MissionGroupIndex == $MissionGroupCount)
   {
      commandToClient(%client, 'SaveObjectData', %data, -1);
      return;
   }
   
   // if not, we will start a new group after this load
   commandToClient(%client, 'SaveObjectData', %data, 0);
}

// when the client is done loading the final ghosted object, the
// game is saved (object manager, ai, task manager, etc.) and the
// current gui controls are closed
function serverCmdSaveObjectDataDone(%client)
{
   // save the sheriff data
   %sheriff = SheriffsOfficeCmpData.recruitedSheriff;
   %hasSheriff = isObject(%sheriff);
   slgSaveBool(%hasSheriff);
   if (%hasSheriff == true)
   {
      %sheriff = %sheriff.getServerId();
      %saveID = %sheriff.getSaveID();
      slgSaveInt(%saveID);
   }
   
   // save flags and camera speed
   slgSaveBool($AI_MONITORJOB);
   slgSaveBool($AI_MONITORHOME);
   slgSaveBool($AI_MONITORHUNGER);
   slgSaveFloat(GameCamera.FlySpeed);
   
   // save the tech manager
   GameTechManager.saveToFile();
   
   // save the tools (dynamite, health kits) client-side
   slgInventorySaveToFile(ClientGroup.getObject(0));
   
   // save the tools (dynamite, health kits) esrver-side
   slgInventorySaveToFile(ServerConnection.getId());
   
   // save the disabled command states
   csSaveToFile();
   
   // save the resource stack
   GameResourceStack.saveToFile();
   
   // save the experience page
   ExperienceManager.saveToFile();
   
   // save the ai script
   AISaveToFile();
   
   // save the happiness timers
   HappinessAttackSaveToFile();
   
   // save scenarios
   CarryScenarioSaveToFile();
   
   // save unique produce count
   slgSaveInt($LastProduceCount);
   for (%index = 0; %index < 7; %index++)
   {
      slgSaveInt($ProduceBadges[%index]);
   }

   // save the game
   slgSaveGame();
   
   // save the alerts (THIS MUST BE THE LAST FUNCTION CALL)
   AlertSaveToFile();
}














// LOADING OBJECTS (mimics the server)

function LoadScenarioGame(%file, %function, %resources)
{
   $LoadMapEarly = false;
   %client = ClientGroup.getObject(0);
   commandToClient(%client, 'UnloadGameActions');

   $DoneLoading = false;
   
   if ($PlayingGame == false)
   {
	   initializeGame();
   }
   
   slgUnpause();
   
   $LoadMission = %file @ ".sav";
   slgStartLoadGame($LoadMission);
   
   // this ensures that the disaster manager exists when loading
   // (this is a fresh load of the disaster manager)
   %client = ClientGroup.getObject(0);
   serverCmdDestroyDisasterManager(%client);
   CreateDisasterManager();
   $DisasterManager.loadDatablocksFromFile();
   
   $CreateResources = true;
   $LoadFunction = %function;
   $LoadResources = %resources;
   $ReloadExperience = true;
   $UnhaltTaskRunner = false;
   serverChangeMission(%file, "onLoadGameMissionDone", true);
}

// this function should be called every time the game should be
// loaded from a particular file in a profile slot
// %file = the file to save to
// %slot = the profile slot to save to
// %function = the function to call when save is complete
// %resources = if true, resources will be saved to the file
$LoadMapEarly = true;
function LoadGame(%slot, %file, %function, %resources)
{
   %file = filePath(%file) @ "/" @ fileBase(%file);
   if (exec(%file, true) == false)
   {
      notificationWindow("The current save game uses a downloadable map that could not be found.  Please reinstall the downloadable maps before loading this save game.", "Canvas.popDialog(NotificationGui);");
      return;
   }
   
   $LoadMission = %file @ ".sav";
   $LoadSlot = %slot;
   
   $LoadMapEarly = false;
   $LoadingGameFromFile = true;
   %client = ClientGroup.getObject(0);
   commandToClient(%client, 'UnloadGameActions');

   $DoneLoading = false;
   
   if ($PlayingGame == false)
   {
	   initializeGame();
   }
   
   slgUnpause();
   
   // copy temporary files from the save slot
   if (%slot >= 0)
   {
      $LoadCarryBackData = true;
      
      slgDeleteFile(slgGetProfileSaveGameLocation() @ "/mission.slg.temp.dso");
      slgDeleteFile(slgGetProfileSaveGameLocation() @ "/mission.slg.temp.sav");
      slgDeleteFile(slgGetProfileSaveGameLocation() @ "/mission.slg.restart.dso");
      slgDeleteFile(slgGetProfileSaveGameLocation() @ "/mission.slg.restart.sav");
      
      %destination = slgGetProfileSaveGameLocation();
      slgCopyFile(slgGetProfileSaveGameLocation() @ "/Slot " @ %slot @ "/mission.slg.temp.dso", %destination);
      slgCopyFile(slgGetProfileSaveGameLocation() @ "/Slot " @ %slot @ "/mission.slg.temp.sav", %destination);
      slgCopyFile(slgGetProfileSaveGameLocation() @ "/Slot " @ %slot @ "/mission.slg.restart.dso", %destination);
      slgCopyFile(slgGetProfileSaveGameLocation() @ "/Slot " @ %slot @ "/mission.slg.restart.sav", %destination);
   }
   else
   {
      $LoadCarryBackData = false;
   }

   if ($LoadSlot > -1) slgStartLoadGame($LoadMission, $LoadSlot);
   else slgStartLoadGame($LoadMission);
   
   // this ensures that the disaster manager exists when loading
   // (this is a fresh load of the disaster manager)
   %client = ClientGroup.getObject(0);
   serverCmdDestroyDisasterManager(%client);
   CreateDisasterManager();
   $DisasterManager.loadDatablocksFromFile();
   
   $CreateResources = true;
   $LoadFunction = %function;
   $LoadResources = %resources;
   $UnhaltTaskRunner = false;
   serverChangeMission(%file, "onLoadGameMissionDone", true);
}

function serverCmdLoadGame(%client, %slot, %file, %function, %resources)
{
   LoadGame(%slot, %file, %function, %resources);
}

function onLoadGameMissionDone()
{
   if ($InGame == false)
   {
      $InGame = true;
      profilerEnable(true);
      slgBuildClientGameTaskSystem();
   }
   
   Canvas.pushDialog(GameLOadingGui);     
   slgStackPause();
   
   %client = ClientGroup.getObject(0);
   serverCmdLoadObjectData(%client);

   metrics("fps");
   onLoadGameDone($LoadFunction);
}

function onLoadGameDone(%function)
{
   if ($DoneLoading == true)
   {
      // close the save game file
      slgStopLoadGame();

      // unpause the game
      slgStackUnpause();
      
      // close the guis
      slgCloseLoadGui();
      
      StopWaiting();
      
      // stop the task system from being halted
      TaskRunner.haltTasks(false);
      $UnhaltTaskRunner = true;
      
      %client = ClientGroup.getObject(0);
      commandToClient(%client, 'LoadGameActions');
      
      // reset tech tree
      GameTechManager.resetTech(%client);
   
      // call the closing function
      if (%function !$= "")
      {
         $LoadingGameFromFile = false;
         call(%function);
      }
      
      return;
   }
   
   // continue checking for the saving file to finish
   schedule(100, 0, onLoadGameDone, %function);
}












// this function is called from the client to initialize loading
// the game on the server
function serverCmdLoadObjectData(%client)
{
   // clear out any task buttons
   commandToClient(%client, 'TaskButtonsInitialize');
   
   // load the mission group count
   %count = MissionGroup.getCount();
   %savedCount = slgLoadInt();
   if (%count != %savedCount)
   {
      error("Problem loading object data: object count in save file does not match the number of objects loaded (MissionGroup).");
      return;
   }
   
   // load the building group count
   %count = BuildingGroup.getCount();
   %savedCount = slgLoadInt();
   if (%count != %savedCount)
   {
      error("Problem loading object data: object count in save file does not match the number of objects loaded (BuildingGroup).");
      return;
   }
   
   // load the character group count
   %count = CharacterGroup.getCount();
   %savedCount = slgLoadInt();
   if (%count != %savedCount)
   {
      error("Problem loading object data: object count in save file does not match the number of objects loaded (CharacterGroup).");
      return;
   }
   
   // load the prop group count
   %count = PropGroup.getCount();
   %savedCount = slgLoadInt();
   if (%count != %savedCount)
   {
      error("Problem loading object data: object count in save file does not match the number of objects loaded (PropGroup).");
      return;
   }

   $MissionGroupIndex = 0;
   serverCmdLoadObjectGroup(%client, 0);
}

// this function loads a chunk of server-side objects
function serverCmdLoadObjectGroup(%client, %start)
{
   %missionGroup = $MissionGroupList[$MissionGroupIndex];
   %count = %missionGroup.getCount();

   // load all of the game objects
   %data = "";
   %client = ClientGroup.getObject(0);
   %objectCount = 0;
   for (%index = %start; %index < %count; %index++)
   {
      // if this object is not a game object, we are not loading it
      %object = %missionGroup.getObject(%index);
      if (%object.isMemberOfClass("SLGameObj") == false)
      {
         continue;
      }
      
      // load the game object
      if (%object.loadFromFile() == false)
      {
         error("Problem loading object data: failed to load game object data on server");
         return;
      }
      
      // load the destroy state of the object
      %hasDestroy = slgLoadBool();
      if (%hasDestroy == true)
      {
         %totalTime = slgLoadFloat();
         %elapsedTime = slgLoadFloat();
         
         %object.destroyTimer = new SLTimer()
         {
            time = %totalTime;
         };
         %object.destroyTimer.setElapsedTime(%elapsedTime);
         %object.destroyFunc = slgLoadString();
         %object.destroyTimer.object = %object;
         
         // update the fire callback
         if (%object.destroyFun $= "FadeOut")
         {
            %object.destroyTimer.notifyOnFire(FadeOut, %object);
         }
         else
         {
            %object.destroyTimer.notifyOnFire(DestroyObject, %object.destroyTimer);
         }
      }
      
      // save explosion timer for dynamite
      %hasExplosion = slgLoadBool();
      if (%hasExplosion == true)
      {
         %totalTime = slgLoadFloat();
         %elapsedTime = slgLoadFloat();
         
         %timer = new SLTimer()
         {
            time = %totalTime;
         };
         %timer.notifyOnFire(onExplode, %object);
         %timer.setElapsedTime(%elapsedTime);
         %object.explodeTimer = %timer;
      }
      
      %objectCount++;
      %ghostID = %client.getGhostId(%object);
      %data = %data @ %ghostID @ " ";
      
      if (%objectCount == $MaxSaveObjects)
      {
         commandToClient(%client, 'LoadObjectData', %data, %index + 1);
         return;
      }
   }
   
   // check if this is the last group that should be loaded
   $MissionGroupIndex++;
   if ($MissionGroupIndex == $MissionGroupCount)
   {
      commandToClient(%client, 'LoadObjectData', %data, -1);
      return;
   }
   
   // if not the last group, then start the new group to load
   commandToClient(%client, 'LoadObjectData', %data, 0);
}

function InitializeMapSystems()
{
   // initialize maps (train and border data)
   //stopGameLoad();
   slgAIInitMapBasedSystems($WalkableBorder);
}

// when the client is done loading the final ghosted object, the
// game is loaded (object manager, ai, task manager, etc.) and the
// current gui controls are closed
function serverCmdLoadObjectDataDone(%client)
{
   $LoadMapEarly = true;
   
   // load sheriff data
   %hasSheriff = slgLoadBool();
   if (%hasSheriff == true)
   {
      %saveID  = slgLoadInt();
      %sheriff = slgGetGameObject(%saveID);
      %sheriff = %sheriff.getClientId();
      SheriffsOfficeCmpData.recruitedSheriff = %sheriff;
   }
   
   // load flags and camera speed
   $AI_MONITORJOB = slgLoadBool();
   $AI_MONITORHOME = slgLoadBool();
   $AI_MONITORHUNGER = slgLoadBool();
   GameCamera.FlySpeed = slgLoadFloat();
   
   // load the tech manager
   if (GameTechManager.loadFromFile() == false)
   {
      error("Game tech manager failed to load in script.");
      return false;
   }
   echo("Game Tech Manager loaded successfully.");
   
   // load the tools (dynamite, health kits)- client-side
   if (slgInventoryLoadFromFile(ClientGroup.getObject(0)) == false)
   {
      error("Inventory failed to load in script (client-side).");
      return false;
   }
   echo("Inventory loaded successfully.");
   
   // load the tools (dynamite, health kits)- server-side
   if (slgInventoryLoadFromFile(ServerConnection.getId()) == false)
   {
      error("Inventory failed to load in script (server-side).");
      return false;
   }
   echo("Inventory loaded successfully.");
   
   // load the disabled command states
   if (csLoadFromFile() == false)
   {
      error("Command states failed to load in script.");
      return false;
   }
   echo("Command states loaded successfully.");
   
   // load the resource stack
   if (GameResourceStack.loadFromFile($LoadResources) == false)
   {
      error("Resource stack failed to load in script.");
      return false;
   }
   echo("Resource stack loaded successfully.");
   
   // load the experience page
   if (isObject($ExperienceManager) == false)
   {
      CreateExperience();
   }
   if ($ExperienceManager.loadFromFile($LoadResources) == false)
   {
      error("Experience manager failed to load in script.");
      return false;
   }
   echo("Experience Manager loaded successfully.");
   
   // load the ai script
   AILoadFromFile();
   
   // load the happiness timers
   HappinessAttackLoadFromFile();
   
   // load scenarios
   CarryScenarioLoadFromFile();
   
   // load unique produce count
   $LastProduceCount = slgLoadInt();
   for (%index = 0; %index < 7; %index++)
   {
      $ProduceBadges[%index] = slgLoadInt();
   }
   
   // drop the unique produce count to 0 (it will naturally
   // rise back up as other objects are loaded)
   %resource = GameResourceStack.getResource();
   %happiness = %resource.getHappiness();
   for (%index = 0; %index < $LastProduceCount; %index++)
   {
      %change = GameResourceData.getProduceHappiness(%index);
      %happiness.decreaseCount(%change);
   }
   $LastProduceCount = 0;
   
   // load the game   
   if (slgLoadGame($LoadResources) == false)
   {
      // error messages should be handled in here
      return false;
   }
   echo("Source game information loaded successfully.");
   
   // load the alerts (THIS MUST BE THE LAST FUNCTION CALL)
   AlertLoadFromFile();
}

function StopWaiting()
{
   schedule(500, 0, slgStopWaiting);
}
