//---------------------------------------------------------------------------
// File: svr_disaster.cs
//
// This file is used to set up the disaster datablocks per disaster type.
// It is also used to handle any disaster-specific functionality related
// to the disaster manager.
//
// Author: Michael Felice
//---------------------------------------------------------------------------
$DISASTER_PLAGUE_CTR = 0;
$DISASTER_DROUGHT_CTR = 0;

// the datablock used to set up the animal attack disaster
datablock SLAnimalAttackData(AnimalAttackData)
{
   chance = 10;
   duration = 5;
   
   fadeTime = 1;
   foodLow = 20;
   foodHigh = 20;
   addAnimal = "10 Sasquatch Saloon";
   
   happinessCount = -30;
   happinessIncrease = 0;
   happinessPlateau = 10;
   happinessDecrease = 0;
};

// the datablock used to set up the bandit attack disaster
datablock SLBanditAttackData(BanditAttackData)
{
   chance = 20;
   duration = 1000;
   
   fadeTime = 1;
   goldMin = 0;
   goldMax = 100;
   goldLow = 5;
   goldHigh = 10;
   lowCount = 1;
   highCount = 3;
   
   happinessCount = -25;
   happinessIncrease = 0;
   happinessPlateau = 10;
   happinessDecrease = 0;
};

// the datablock used to set up the drought disaster
datablock SLDroughtData(DroughtData)
{
   chance = 10;
   addRequirement = well;
   duration = 45;
   
   cost = 0.5;
};

// the datablock used to set up the earthquake disaster
datablock SLEarthquakeData(EarthquakeData)
{
   chance = 10;
   duration = 5;
   
   shakeDistance = 0.4;
   damageLow = 25;
   damageHigh = 50;
};

// the datablock used to set up the famine disaster
datablock SLFamineData(FamineData)
{
   chance = 10;
   //addRequirement = Granary;
   duration = 0;
   
   resource = $Resource::Food;
   cost = 0.75;
};

// the datablock used to set up the fire disaster
datablock SLFireData(FireData)
{
   chance = 50;
   duration = -1;
   
   startStrength = 2;
   
   spreadCount = 2;
   spreadTime = 15;
   spreadChance = 35;
   spreadRadius = 25;
   spreadStrength = 1;
   
   burnTime = 15;
   burnStrength = 1;
   burnMax = 4;
   
   damageTime = 0.5;
   damageStrength = 1;
};

// the datablock used to set up the plague disaster
datablock SLPlagueData(PlagueData)
{
   chance = 40;
   addRequirement = Farm;
   addRequirement = Ranch;
   duration = 30;
   
   produceRate = 0.5;
   addProduce = "wheat";
   addProduce = "corn";
   addProduce = "pumpkin";
   addProduce = "chicken";
   addProduce = "pig";
   addProduce = "cow";
};

// the datablock used to set up the tornado disaster
datablock SLTornadoData(TornadoData)
{
   object = "tornado";

   chance = 10;
   duration = 60;

   travelAngle = 35;
   travelRandom = 25;
   travelTimeLow = 3;
   travelTimeHigh = 6;
   travelSpeedLow = 3;
   travelSpeedHigh = 5;
   radius = 8;
   damage = 1;
};

// this function is used to create a disaster manager on the server
// (this should only be done when a game is first started or loaded)
function CreateDisasterManager()
{
   if (isObject($DisasterManager) == true)
   {
      return;
   }

   // create the disaster manager with the disaster datablocks
   // created above and the base disaster settings
   $DisasterManager = new SLDisasterManager(DisasterManager)
   {
      plagueData = PlagueData;
      famineData = FamineData;
      droughtData = DroughtData;
      banditData = BanditAttackData;
      tornadoData = TornadoData;
      fireData = FireData;
      earthquakeData = EarthquakeData;

      // experience marker, time, max disasters
      addDisasterNode = "100 120 1";
      
      // frequency pool, respawning time
      //famine = "15 100";
      //drought = "15 100";
      //fire = "15 120";
      //earthquake = "15 160";
      //tornado = "15 100";
      //plague = "15 60";
      //animal = "10 60";
      //bandit = "20 100";
   };

   MissionCleanup.remove($DisasterManager);
}

// this function shuts down the disaster manager
function serverCmdDestroyDisasterManager(%client)
{
   if (isObject($DisasterManager) == true)
   {
      $DisasterManager.delete();
      $DisasterManager = 0;
   }
}

/*
function cheatCreateDisaster(%type)
{
   %disaster = %this.createDisaster(%type);
   if (isObject(%disaster) == false)
   {
      return;
   }
}
*/

// when the disaster manager determines that a disaster could be
// created, call the create disaster function
function SLDisasterManager::OnCreateDisaster(%this)
{
   %disaster = %this.createDisaster();
   if (isObject(%disaster) == false)
   {
      return 0;
   }
   
   // get disaster message
   %msg = disasterGetCreateMsg(%disaster);
   
   // notify alert system
   if (alertSvrDisasterHasAlert(%disaster.getType()))
   {
      alertSvrAddDisaster(%disaster, %msg);
   }
   
   // notify clients
   else if(%msg !$= "")
   {
      for (%i = 0; %i < ClientGroup.getCount(); %i++)
      {
         %client = ClientGroup.getObject(%i);
         commandToClient(%client, 'SendAlertMessage', %msg);
      }   
   }
   
   //Increment the plague and drought counters if applicable
   if($Disaster::Drought == %disaster.getType())
      $DISASTER_DROUGHT_CTR++;
   else if($Disaster::Plague == %disaster.getType())
      $DISASTER_PLAGUE_CTR++;
   else if($Disaster::Famine == %disaster.getType())
   {
      playSFXSound("audio_famine");
      %citizen = slgGetRandomPlayerCharacterObj();
      if(%citizen)
         %citizen.playVocal("SFA");
   }
   else if($Disaster::Earthquake == %disaster.getType())
   {
      playSFXSound("audio_quake");
   }
   
   // add status of objects under plague
   if (%disaster.getType() == $Disaster::Plague)
   {
      %size = %disaster.getObjectCount();
      for (%i = 0; %i < %size; %i++)
      {
         %component = slgQueryInterface(%disaster.getObject(%i), $CID_BUILDING); 
         %component.setStatus($BuildingStatus::Plague);
      }
   }
   
   return %disaster;
}

// this function is called when a disaster is done and needs to
// be destroyed
function SLDisaster::OnDestroyDisaster(%this)
{
   // get destroy message
   %msg = disasterGetDestroyMsg(%this.getType());
   
   // notify alert system
   if (alertSvrDisasterHasAlert(%this.getType()))
   {
      alertSvrRemoveDisaster(%this, %msg);
   }
   // notify clients
   else if(%msg !$= "")
   {
      for (%i = 0; %i < ClientGroup.getCount(); %i++)
      {
         %client = ClientGroup.getObject(%i);
         commandToClient(%client, 'SendAlertMessage', %msg);
      }
   }
   
   //Decrement the plague and drought counters if applicable
   if($Disaster::Drought == %this.getType())
      $DISASTER_DROUGHT_CTR--;
   else if($Disaster::Plague == %this.getType())
      $DISASTER_PLAGUE_CTR--;
   
   // remove status of objects under plague
   if (%this.getType() == $Disaster::Plague)
   {
      %size = %this.getObjectCount();
      for (%i = 0; %i < %size; %i++)
      {
         %component = slgQueryInterface(%this.getObject(%i), $CID_BUILDING); 
         %component.clearStatus($BuildingStatus::Plague);
      }
   }
   
   // Destroy disaster
   $DisasterManager.destroyDisaster(%this);
}

// this function is called when the disaster has a new object
// that is influences
function SLDisaster::onAddObject(%disaster, %object)
{
   %type = %disaster.getType();
   
   // add object to alert system
   if (alertSvrDisasterHasAlert(%type))
   {  
      %alert = alertSvrDisasterGetAlertType(%type);
      alertSvrAddObject(%alert, %object);
   }
   
   // set building status if it is plague
   if (%type == $Disaster::Plague)
   {
      %component = slgQueryInterface(%object, $CID_BUILDING);
      %component.setStatus($BuildingStatus::Plague);
   }
}

// this function is called when the disaster loses an object
// either because the object is going to be destroyed or the
// disaster no longer affects it
function SLDisaster::onRemoveObject(%disaster, %object)
{
   %type = %disaster.getType();
   
   // remove object from alert system
   if (alertSvrDisasterHasAlert(%type))
   {
      // get destroy message
      %msg = disasterGetDestroyMsg(%type);
      
      %alert = alertSvrDisasterGetAlertType(%type);
      if(%alert != $ALERT_BANDIT) {
         alertSvrRemoveObject(%alert, %object, %msg);
      }
   }
   
   // set building status if it is plague
   if (%type == $Disaster::Plague)
   {
      %component = slgQueryInterface(%object, $CID_BUILDING);
      %component.clearStatus($BuildingStatus::Plague);
   }
}

// this function is called when an animal attack is started
function SLDisaster::onAnimalAttack(%disaster, %input)
{
   %animal = getWord(%input, 0);
   %target = getWord(%input, 1);



   // IF AN ANIMAL ATTACK IS SUCCESSFUL:
   
   // when the animal attack steals from a ranch or smokehouse
   %foodLoss = %disaster.steal();
   SendProductionToClient(%target, -%foodLoss @ " food");

   // when food is stolen, animal happiness takes effect
   startHappinessAttack(%disaster.getDataBlock(), %target);
}

// this function is called when a bandit attack is started
function SLDisaster::onBanditAttack(%disaster)
{
   slgAIManAddDisaster(%disaster);
}

// Returns the message to be used when the disaster is created
function disasterGetCreateMsg(%disaster)
{
   %msg = "";
   
   // Get a message for disaster creation based on type of disaster
   switch(%disaster.getType())
   {
   case $Disaster::Plague:
      %msg = slgGetUIString("id_plague_create");
      %msg = slgFormatUIString(%msg, %disaster.getProduce());
   case $Disaster::Famine:
      %msg = slgGetUIString("id_famine_create");
   case $Disaster::Drought:
      %msg = slgGetUIString("id_drought_create");
   case $Disaster::Bandit:
      %msg = slgGetUIString("id_bandit_create");
   case $Disaster::Animal:
      %msg = slgGetUIString("id_animal_create");
      %msg = slgFormatUIString(%msg, %disaster.getAnimal());
   case $Disaster::Tornado:
      %msg = slgGetUIString("id_tornado_create");
   case $Disaster::Fire:
      %msg = slgGetUIString("id_fire_create");
   case $Disaster::Earthquake:
      %msg = slgGetUIString("id_quake_create");
   }
   
   // Return the message
   return %msg;
}

// Returns the message to be used when the disaster is destroyed
function disasterGetDestroyMsg(%disasterType)
{
   %msg = "";
   
   // Get the destroy message for this disaster type
   switch(%disasterType)
   {
   case $Disaster::Plague:
      %msg = slgGetUIString("id_plague_destroy");
   case $Disaster::Drought:
      %msg = slgGetUIString("id_drought_destroy");
   case $Disaster::Bandit:
   case $Disaster::Animal:
   case $Disaster::Tornado:
      %msg = slgGetUIString("id_tornado_destroy");
   case $Disaster::Fire:
      %msg = slgGetUIString("id_fire_destroy");
   case $Disaster::Earthquake:
      %msg = slgGetUIString("id_quake_destroy");
   }
   
   // Return the message
   return %msg;
}

$HappinessAttackList = "";
$HappinessAttackCount = 0;

// this saves the happiness attack data
function HappinessAttackSaveToFile()
{
   // save the number of active happiness attacks
   slgSaveInt($HappinessAttackCount);
   
   // save each happiness attack timer
   for (%index = 0; %index < $HappinessAttackCount; %index++)
   {
      %timer = $HappinessAttackList[%index];
      %totalTime = %timer.getTotalTime();
      %elapsedTime = %timer.getElapsedTime();
      
      // save the timer data
      slgSaveFloat(%totalTime);
      slgSaveFloat(%elapsedTime);
      slgSaveFloat(%timer.happiness);
      slgSaveString(%timer.dataBlock.getName());
      slgSaveString(%timer.onUpdate);
      slgSaveString(%timer.onFire);
   }
}

// this loads the happiness attack data
function HappinessAttackLoadFromFile()
{
   // delete any current happiness attack timers
   for (%index = 0; %index < $HappinessAttackCount; %index++)
   {
      // if this timer is active, delete it
      %timer = $HappinessAttackList[%index];
      if (isObject(%timer))
      {
         %timer.delete();
      }
   }
   
   // load the number of active happiness timers there are
   $HappinessAttackCount = slgLoadInt();
   
   // load each happiness attack timer
   for (%index = 0; %index < $HappinessAttackCount; %index++)
   {
      // load time data
      %totalTime = slgLoadFloat();
      %elapsedTime = slgLoadFloat();
      
      // create the timer
      %timer = new SLTimer()
      {
         time = %totalTime;
      };
      %timer.setElapsedTime(%elapsedTime);
      
      // load additional timer data
      %timer.happiness = slgLoadFloat();
      %timer.dataBlock = nameToId(slgLoadString());
      %timer.onUpdate = slgLoadString();
      %timer.onFire = slgLoadString();
      
      // set the function notifications for the timer
      if (%timer.onUpdate !$= "")
      {
         %timer.notifyOnUpdate(%timer.onUpdate, %timer);
      }
      if (%timer.onFire !$= "")
      {
         %timer.notifyOnFire(%timer.onFire, %timer);
      }
      
      // add the timer to the list
      $HappinessAttackList[%index] = %timer;
   }
}

// this function should be called when an animal attack or bandit
// attack succeeds, initializing the happiness changes that the
// disaster will cause for the town (also, this functionality is
// used for parades with reverse modifiers)
function startHappinessAttack(%datablock, %object)
{
   // create the timer
   %timer = new SLTimer()
   {
      time = %datablock.happinessIncrease;
   };
   %timer.happiness = 0;
   %timer.dataBlock = %datablock;
   %timer.notifyOnUpdate(onHappinessIncrease, %timer);
   %timer.notifyOnFire(onHappinessPlateauStart, %timer);
   %timer.onUpdate = "onHappinessIncrease";
   %timer.onFire = "onHappinessPlateauStart";
   
   // add the timer to the happiness attack list
   $HappinessAttackList[$HappinessAttackCount] = %timer;
   $HappinessAttackCount++;
   
   // if the object has been defined, then set up the happiness
   // resources update that will be displayed above the object   
   if (isObject(%object) == true)
   {
      %happiness = %timer.dataBlock.happinessCount;
      SendProductionToClient(%object, %happiness @ " happiness");
      %timer.happinessObject = %object;
   }
}

// this function is called every time the timer is updated for
// happiness to increase
function SLTimer::onHappinessIncrease(%timer)
{
   if (%timer.dataBlock.happinessIncrease <= 0)
   {
      return;
   }
   
   %time = %timer.getElapsedTime();
   %fraction = %time / %timer.dataBlock.happinessIncrease;
   %amount = %timer.dataBlock.happinessCount * %fraction;
   %increase = %amount - %timer.happiness;
   %timer.happiness = %amount;
   
   %resource = GameResourceStack.getResource();
   %happiness = %resource.getHappiness();
   %happiness.increaseCount(%increase);
   
   // set happiness modifiers on the server
   setDatablockHappyModValueSvr(%timer.dataBlock, %amount);
}

// this function is called when happiness has peaked and is waiting
// before happiness falls again
function SLTimer::onHappinessPlateauStart(%timer)
{
   %amount = %timer.dataBlock.happinessCount;
   %increase = %amount - %timer.happiness;
   %timer.happiness = %amount;
   
   %resource = GameResourceStack.getResource();
   %happiness = %resource.getHappiness();
   %happiness.increaseCount(%increase);
   
   %newTimer = new SLTimer()
   {
      time = %timer.dataBlock.happinessPlateau;
   };
   %newTimer.notifyOnFire(onHappinessPlateauEnd, %newTimer);
   %newTimer.onFire = "onHappinessPlateauEnd";
   %newTimer.dataBlock = %timer.dataBlock;
   %newTimer.happiness = %timer.happiness;
   %newTimer.happinessObject = %timer.happinessObject;
   
   // replace the existing happiness attack timer with the new one
   for (%index = 0; %index < $HappinessAttackCount; %index++)
   {
      %check = $HappinessAttackList[%index];
      if (%check == %timer)
      {
         $HappinessAttackList[%index] = %newTimer;
         break;
      }
   }
   
   // set happiness modifiers on the server
   setDatablockHappyModValueSvr(%timer.dataBlock, %amount);
}

// when the happiness plateau has ended, this function is called,
// which initializes the happiness drop
function SLTimer::onHappinessPlateauEnd(%timer)
{
   %newTimer = new SLTimer()
   {
      time = %timer.dataBlock.happinessDecrease;
   };
   %newTimer.notifyOnUpdate(onHappinessDecrease, %newTimer);
   %newTimer.notifyOnFire(onHappinessDone, %newTimer);
   %newTimer.onUpdate = "onHappinessDecrease";
   %newTimer.onFire = "onHappinessDone";
   %newTimer.dataBlock = %timer.dataBlock;
   %newTimer.happiness = %timer.happiness;
   
   // replace the existing happiness attack timer with the new one
   for (%index = 0; %index < $HappinessAttackCount; %index++)
   {
      %check = $HappinessAttackList[%index];
      if (%check == %timer)
      {
         $HappinessAttackList[%index] = %newTimer;
         break;
      }
   }
   
   %newTimer.happinessObject = %timer.happinessObject;
}

// this function is called every time the timer is updated to drop
// the happiness back to the original happiness before the disaster
// started
function SLTimer::onHappinessDecrease(%timer)
{
   if (%timer.dataBlock.happinessDecrease <= 0)
   {
      return;
   }
   
   %time = %timer.getElapsedTime();
   %fraction = 1 - %time / %timer.dataBlock.happinessDecrease;
   %amount = %timer.dataBlock.happinessCount * %fraction;
   %increase = %amount - %timer.happiness;
   %timer.happiness = %amount;
   
   %resource = GameResourceStack.getResource();
   %happiness = %resource.getHappiness();
   %happiness.increaseCount(%increase);
   
   // set happiness modifiers on the server
   setDatablockHappyModValueSvr(%timer.dataBlock, %amount);
}

// this function is called when happiness is done being modified
function SLTimer::onHappinessDone(%timer)
{
   // remove the timer from the happiness timer list
   for (%index = 0; %index < $HappinessAttackCount; %index++)
   {
      %check = $HappinessAttackList[%index];
      if (%check == %timer)
      {
         // remove the happiness timer (replace it with the
         // last active timer and decrement the timer count)
         $HappinessAttackList[%index] =
            $HappinessAttackList[$HappinessAttackCount - 1];
         $HappinessAttackCount--;
         break;
      }
   }
   
   %resource = GameResourceStack.getResource();
   %happiness = %resource.getHappiness();
   %happiness.increaseCount(-%timer.happiness);
   
   if (isObject(%timer.happinessObject) == true)
   {
      %happiness = %timer.dataBlock.happinessCount;
      SendProductionToClient(%timer.happinessObject, -%happiness @ " happiness");
   }
   
   // set happiness modifiers on the server
   setDatablockHappyModValueSvr(%timer.dataBlock, 0);
}

//AI DISASTER FUNCTIONS//////////////////////////////////////////////////
function AIMan_UpdateBanditDisaster(%disaster)
{
   %banditCount = %disaster.getBanditCount();
   for (%index = 0; %index < %banditCount; %index++)
   {
      %bandit = %disaster.getBandit(%index);
      if(!IsObject(%bandit))
         %disaster.removeBandit(%bandit);
   }
   
   // IF A BANDIT ATTACK IS SUCCESSFUL:
   // First parameter is bandit in disaster attack
   // Second is first target of bandit attack
   //alertSvrAddBanditAttack(%bandit, %target)
}


function serverCmdLoadServerManagers(%client)
{
   slgLoadDefaultEffects();

   if ($PlayingGame == true)
   {
      if ($ReloadExperience == true)
      {
         CreateExperience();
         $ReloadExperience = false;
      }
      CreateDisasterManager();
   }
   
   commandToClient(%client, 'MissionLoaded');
}


// SAVING DISASTERS
function SaveDisasterManager()
{
   return $DisasterManager.saveToFile();
}

// LOADING DISASTERS
function LoadDisasterManager()
{
   return $DisasterManager.loadFromFile();
}

// creates a default bandit disaster scenario
function createBanditDisasterScenario(%chance)
{
    %banditDisaster = new SLBanditAttackScenario() {
      chance    = %chance;
      duration  = 1000;
      
      // Amount of time to fade in/out for bandits
      fadeTime  = 1;
      
      // Gold stolen in attack
      goldMin   = 0;
      goldMax   = 0;
      goldLow   = 0;
      goldHigh  = 0;
      
      // bandits to attack
      lowCount  = 0;
      highCount = 0;
      target    = "";
      
      // Happiness change
      happinessCount    = -25;
      happinessIncrease = 0;
      happinessPlateau  = 10;
      happinessDecrease = 0;
   };
   return %banditDisaster;
}
function createFamineDisasterScenario(%chance, %duration, %cost, %resource)
{
   %famineDisaster = new SLFamineScenario() {
      chance   = %chance;
      duration = %duration;
      resource = %resource;
      cost     = %cost;
   };
   return %famineDisaster;
}
function createDroughtDisasterScenario(%chance, %duration, %cost)
{
   %droughtDisaster = new SLDroughtScenario() {
      chance   = %chance;
      duration = %duration;
      cost     = %cost;
   };
   return %droughtDisaster;
}
function createPlagueDisasterScenario(%chance, %duration, %producerate)
{
   %plagueDisaster = new SLPlagueScenario() {
      chance      = %chance;
      duration    = %duration;
      produceRate = %producerate;
   };
   return %plagueDisaster;
}
function createQuakeDisasterScenario(%chance, %duration, %dshake, %lowdam, %highdam)
{
   %quakeDisaster = new SLEarthquakeScenario() {
      chance        = %chance;
      duration      = %duration;
      
      shakeDistance = %dshake;
      damageLow     = %lowdam;
      damageHigh    = %highdam;
   };
   return %quakeDisaster;
}
function createFireDisasterScenario(%chance)
{
   %fireDisaster = new SLFireScenario() {
      chance   = %chance;
      duration = -1;
      
      // Strength default (same as datablock)
      startStrength  = 2;
      
      // Spread variable defaults (same as datablock)
      spreadCount    = 2;
      spreadTime     = 6;
      spreadChance   = 30;
      spreadRadius   = 25;
      spreadStrength = 4;
      
      // Burn variable defaults (same as datablock)
      burnTime       = 10;
      burnStrength   = 1;
      burnMax        = 5;
      
      // Damage defaults (same as datablock)
      damageTime     = 0.1;
      damageStrength = 0.2;
   };
   return %fireDisaster;
}

function SLTornado::updateTimer(%tornado)
{
   %timer = %tornado.getUpdateTimer();
   %elapsedTime = %timer.getElapsedTime();
   %timeStep = %elapsedTime - %timer.lastTime;
   %timer.lastTime = %elapsedTime;
   %tornado.update(%timeStep);
}

function SLTornado::SaveToFile(%tornado)
{
   %timer = %tornado.getUpdateTimer();
   slgSaveFloat(%timer.lastTime);
}

function SLTornado::LoadFromFile(%tornado)
{
   %timer = %tornado.getUpdateTimer();
   %timer.lastTime = slgLoadFloat();
}
