//*****************************************************************
// UO Style Spawn System v2.1.5
// Author: Palor <palor@truefear.net>
// Description:
//  This script enables you to place "spawners" that can make
//  a location keep X number of creatures spawned, with delays
//  and even just at night or during the day.
//
// Features:
//  -Spawn up to 99 creatures. - optional
//  -Spawn a random amount of creatures between 1 and 99. - optional
//  -Use a delay of up to 99 minutes between each spawn. - optional
//  -Use random delay between 0 to 99 minutes. - optional
//  -Spawn just at night or during the day. - optional
//  -Spawn only if a PC is within up to 99 meters. - optional
//  -Spawn creatures in random locations within a specified radius
//   of the spawn point. - optional
//  -Spawn treasure chests or any other valid Placeable.
//  -Use Effects during spawning
//
//###############################################################
// Installation.
//
// 1. Create an invisible object and attach this script to
//    it's Heartbeat. (You only need the one object for all
//    your spawn points in the area).
// 2. Decide what creature you want to spawn and make a note
//    of it's Tag or create a custom creature.
// 3. Place a Waypoint and edit the properties to look like:
//
// Name: SP_SX01
// Tag: WP_TagOfCreatureYouWantToSpawn
//
// The above would make a single spawn point which would
// always keep one of your creatures spawned there.
//
//###############################################################
// Switches
//
// Switches are used in the Name of the Spawn Point (Waypoint)
// and control how the Spawn Point spawns.
// Example:
//   Name: SP_SX05_TM10_NT_RD05
//
//    Will keep 5 creatures spawned only at night, only if
//    a player is within 5 meters and will wait 10 seconds
//    before spawning and will wait 10 seconds after each
//    creature is killed before spawning a new one.
//
// Current Switches:
//
// SP_       Tells the Spawn Control Object that this Waypoint is
//           a Spawn Point.
//
// SRnn      Spawn creatures in a random location within "nn" meters.
//
// RDnn      Only spawn creatures if a Player is within "nn" meters.
//
// NT        Only spawn at night time and kill any creatures left
//           when it turns day.
//
// DT        Only spawn during the day and kill any creatures left
//           when it turns night.
//
// TR        Spawns any valid Placeable (used for treasure chests).
//           Please read the section in the orginal post for this
//           release on how to make a treasure chest if you are
//           not familiar with creating treasure chests.
//
// SXnnRyy   Keep "nn" creatures spawned at this Spawn Point.
//           If R is specified, it spawns a random number of
//           creatures between nn and yy.
//           Examples:
//           SX02      - Keep 2 creatures spawned.
//           SX01R10   - Spawn a random amount between 1 and 10.
//           NOTE: nn is the MINIMUM you want. If you specify
//           01 as the minimum, it will not spawn anymore until
//           they are ALL dead.
//
// TMxxRyyM  Delay spawns by "xx" seconds. Specify Minutes by adding
//           the M on the end. If R is used, it chooses a random
//           number between xx and yy.
//           Examples:
//            TM02     - Delay spawn for 2 seconds.
//            TM02M    - Delay spawn for 2 minutes.
//            TM10R30  - Random between 10 and 30 seconds.
//            TM10R30M - Random between 10 and 30 minutes.
//
// VSnn      Tells the spawner to use effect nn on the location
//           before spawning. Valid effects are:
//           01 - VFX_FNF_SUMMON_MONSTER_1
//           02 - VFX_FNF_SUMMON_MONSTER_2
//           03 - VFX_FNF_SUMMON_MONSTER_3
//           04 - VFX_FNF_SUMMON_UNDEAD
//           05 - VFX_FNF_SUNBEAM
//           06 - VFX_FNF_WAIL_O_BANSHEES
//           07 - VFX_FNF_STORM
//           08 - VFX_FNF_STRIKE_HOLY
//           09 - VFX_FNF_IMPLOSION
//
//###############################################################
// Changes
//
// 2.1.5
//  - Added effects for spawning.
//
// 2.1.4b
//  - Fixed a problem with WP_ tag where it would chop off
//    the last 2 numbers. - Reported by Noggy
//
// 2.1.4a
//  - Added Spawn Radius (SR), which will spawn in a random
//    location within specified radius. This is now
//    seperate from RD. - Requested by TooKool
//
// 2.1.4
//  - Added ability to spawn any Placeable object. This
//    implemented in order to have it spawn treasure
//    chests, but could be used for any Placeable.
//  - Added random number of creature spawns. (SXnnRyy)
//    Examples:
//     SX02      - Keep 2 creatures spawned.
//     SX01R10   - Spawn a random amount between 1 and 10.
//    Requested by stefulia
//  - Fixed timer bug where minimum would stay seconds
//    even if Minutes was specified. - Reported by jRaskell
//  - Modified timer so that it is not used when a player
//    first enters an area IF a Radius is set. This means
//    that if you have a timer set for 20 minutes, there
//    will always be a creature when the player FIRST
//    enters the Radius. Timer takes effect AFTER they
//    enter the area. - Requested by someone??
//
// 2.1.3
//  - Creatures will now spawn at once the first time
//    it is a VALID time to spawn. This means that
//    when you first load the module, any spawn point
//    that is valid will spawn without a delay the first
//    time. If there is a delay, it will delay each time
//    thereafter. This was requested by multiple people :)
//  - Added Random Spawn Points (RDnnX) within a radius.
//    Example:
//     RD10  = Check if PC is within 10 meters and spawn
//             creatures at spawn point.
//     RD10X = Check if PC is within 10 meters and spawn
//             creatures at random places within that 10
//             meter area.
//    **THE X WAS REMOVED 2.1.5**
// 2.1.2
//  - Added fix for POST_ so that creatures can return
//    back to spawn point. - Reported by stefulia
//  - Added Random option for timer - Suggested by stefulia
//
// 2.1.1
//  - Modified code structure to account for future changes.
//  - Changed how it spawns and how it checks for creatures
//    to account for creatures it spawns. Meaning it will
//    only CHECK for creatures it spawns and it will only
//    kill creatures it spawns :)
//
// 2.1 (The real 2.0)
//  - Rewrote code
//  - Changed DY to DT - Suggested by Iceyflame
//  - Added an optional M for TMnn so you can specify Minutes
//    Example:
//      TM05M = 5 minutes
//      TM05  = 5 seconds
//  - Creatures who only spawn during the day/night will now not
//    be killed by the spawner if they are in combat and the time
//    of day changes.
//  - Changed Radius so that if one is specified and there are
//    no players left in area after a spawn, creatures are killed.
//
// 2.0
//  - Added RD switch (Radius) - Suggested by Iceyflame
//
// 1.2
//  - Added DY & NT switches (Day or Night) - Suggested by Iceyflame
//  - Fixed a bug with it taking of NW_ - Reported by Spike-
//  - Fixed other stuff
//
// 1.1
//  - Added SX (Spawn multiple)
//  - Added TM (Timer)
//
//###############################################################
int iDebug = 0;
void sp_KillCreatures(object oNearest, string sCurrentTag);
void sp_CheckVars(object oNearest, string sCurrentName);
void sp_Spawn(object oNearest, string sTemplate, int iSpawnX);
string sp_CleanString(object oNearest, string sType);
int sp_GetValue(object oNearest, string sSwitch, string sCurrentName);
int sp_CheckCreatures(object oNearest, string sCurrentTag);
int sp_CheckDistance(object oNearest, int iSpawnDistance);
int sp_CheckTimeOfDay(object oNearest);
location sp_RandomLocation(object oNearest, int iRandArea);
void main()
{
  int nNth = 1;
  object oNearest = GetNearestObject( 32, OBJECT_SELF, nNth );
  while (GetIsObjectValid(oNearest) )
  {
    // Check this is a spawner and if a timer is already running.
    if (!GetLocalInt(oNearest, "IsSpawning") && GetStringLeft(GetName(oNearest), 3) == "SP_") {
      // Check if variables are already set.
      if (!GetLocalInt(oNearest, "IsSpawner")) {
        sp_CheckVars(oNearest, GetName(oNearest));
      }
      // Start of Main Procedure.
      ////////////////////////////////////////////////////////////
      // Get the Tag.
      if (GetLocalString(oNearest, "Tag") == "") {
        SetLocalString(oNearest, "Tag", sp_CleanString(oNearest, "Tag"));
      }
      if (GetLocalString(oNearest, "Blueprint") == "") {
        SetLocalString(oNearest, "Blueprint", sp_CleanString(oNearest, "Blueprint"));
        if (iDebug) // Debug message
          SendMessageToAllDMs("Blueprint = " + GetLocalString(oNearest, "Blueprint"));
      }
      // We will use this to see if we should spawn or not.
      int iIsValid = 1;
      // Check for time of day.
      if (sp_CheckTimeOfDay(oNearest) != 1) {
        if (iDebug) // Debug message
          SendMessageToAllDMs("Time of day is not valid for " + GetTag(oNearest));
          iIsValid = 0;
          sp_KillCreatures(oNearest, GetLocalString(oNearest, "Tag"));
      }
      // Check for missing creatures and kill if Time of Day is wrong.
      int iToSpawn = sp_CheckCreatures(oNearest, GetLocalString(oNearest, "Tag"));
      if (iToSpawn == 0) {
        iIsValid = 0;
      }
      else if (iDebug) {
        SendMessageToAllDMs("Missing " + IntToString(iToSpawn) +
                            " at " + GetTag(oNearest));
      }
      // Check for PC in radius (if a radius is used).
      if (sp_CheckDistance(oNearest, GetLocalInt(oNearest, "PC_Radius")) != 1) {
        iIsValid = 0;
        sp_KillCreatures(oNearest, GetLocalString(oNearest, "Tag"));
      }
      // Setup spawn.
      if (iIsValid) {
        SetLocalInt(oNearest, "IsSpawning", 1);
        int iSpawnTime;
        float fSpawnTime;
        if (GetLocalInt(oNearest, "HasSpawned")) {
          if (iDebug) { // Debug message
            SendMessageToAllDMs("Timer " +
                                 IntToString(GetLocalInt(oNearest, "SpawnTimer")) +
                                 " Random " +
                                 IntToString(GetLocalInt(oNearest, "TimerIsRandom")));
          }
          iSpawnTime = GetLocalInt(oNearest, "SpawnTimer") +
                                   Random(GetLocalInt(oNearest, "TimerIsRandom"));
          fSpawnTime = IntToFloat(iSpawnTime);
        }
        else {
          SetLocalInt(oNearest, "HasSpawned", 1);
          fSpawnTime = 0.0;
        }
        if (GetLocalInt(oNearest, "PC_Entered") == 1) {
          SetLocalInt(oNearest, "PC_Entered", 2);
          fSpawnTime = 0.0;
        }
        if (iDebug) // Debug message
          SendMessageToAllDMs("Spawning " +
            GetLocalString(oNearest, "Blueprint") +
            " delayed for " + IntToString(iSpawnTime));
          AssignCommand(OBJECT_SELF,
            DelayCommand(
            fSpawnTime,
            sp_Spawn(oNearest, GetLocalString(oNearest, "Blueprint"), iToSpawn)));
          AssignCommand(OBJECT_SELF,
            DelayCommand(
            fSpawnTime,
            SetLocalInt(oNearest, "IsSpawning", 0)));
        }
      ////////////////////////////////////////////////////////////
      // End of Main Procedure
    }
    nNth++;
    oNearest = GetNearestObject( 32, OBJECT_SELF, nNth );
  }
}
// No comments beyond this point :)
void sp_KillCreatures(object oNearest, string sCurrentTag)
{
  int iCurObject = 1;
  object oCreatureToKill = GetNearestObjectByTag(sCurrentTag, OBJECT_SELF, iCurObject);
  while (GetIsObjectValid(oCreatureToKill)) {
    if (!GetIsInCombat(oCreatureToKill) &&
         GetObjectType(oCreatureToKill) != 32 &&
         GetLocalString(oCreatureToKill, "SpawnedBy") == ObjectToString(oNearest)) {
      if (iDebug) { // Debug message
        SendMessageToAllDMs("Killing - " + GetTag(oCreatureToKill));
      }
      // Comment the following two lines and uncomment the 3rd
      // to get rid of the death effect when it kills a creature
      // it spawned.
      effect eDeath = EffectDeath(TRUE, TRUE);
      ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, oCreatureToKill);
      //DestroyObject(oCreatureToKill, 0.0);
    }
    iCurObject++;
    oCreatureToKill = GetNearestObjectByTag(sCurrentTag, OBJECT_SELF, iCurObject);
  }
}
void sp_CheckVars(object oNearest, string sCurrentName)
{
  SetLocalInt(oNearest, "SpawnTimer",  sp_GetValue(oNearest, "TM", sCurrentName));
  SetLocalInt(oNearest, "SpawnAmount", sp_GetValue(oNearest, "SX", sCurrentName));
  SetLocalInt(oNearest, "PC_Radius",   sp_GetValue(oNearest, "RD", sCurrentName));
  SetLocalInt(oNearest, "NPC_Radius",  sp_GetValue(oNearest, "SR", sCurrentName));
  SetLocalInt(oNearest, "SpawnDay",    sp_GetValue(oNearest, "DT", sCurrentName));
  SetLocalInt(oNearest, "SpawnNight",  sp_GetValue(oNearest, "NT", sCurrentName));
  SetLocalInt(oNearest, "IsTreasure",  sp_GetValue(oNearest, "TR", sCurrentName));
  SetLocalInt(oNearest, "SpawnEffect", sp_GetValue(oNearest, "VS", sCurrentName));
  SetLocalInt(oNearest, "IsSpawner",   1);
}
void sp_Spawn(object oNearest, string sTemplate, int iSpawnX)
{
  location lCurrentWP = GetLocation(oNearest);
  int iTotalX = 0;
  if (GetLocalInt(oNearest, "SpawnRandomNumber")) {
    iSpawnX = Random(GetLocalInt(oNearest, "SpawnRandomNumber")) + iSpawnX - 1;
  }
  while (iTotalX < iSpawnX) {
    if (iDebug) { // Debug message
      SendMessageToAllDMs("Using template - " + sTemplate);
    }
    if (GetLocalInt(oNearest, "NPC_Radius")) {
      lCurrentWP = sp_RandomLocation(oNearest, GetLocalInt(oNearest, "NPC_Radius"));
    }
    if (GetLocalInt(oNearest, "IsTreasure")) {
      CreateObject(OBJECT_TYPE_PLACEABLE, sTemplate, lCurrentWP, TRUE);
    }
    else {
      if (GetLocalInt(oNearest, "SpawnEffect")) {
        effect eSpawn;
        switch (GetLocalInt(oNearest, "SpawnEffect")) {
          case 01: eSpawn = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_1); break;
          case 02: eSpawn = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_2); break;
          case 03: eSpawn = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_3); break;
          case 04: eSpawn = EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD); break;
          case 05: eSpawn = EffectVisualEffect(VFX_FNF_SUNBEAM); break;
          case 06: eSpawn = EffectVisualEffect(VFX_FNF_WAIL_O_BANSHEES); break;
          case 07: eSpawn = EffectVisualEffect(VFX_FNF_STORM); break;
          case 08: eSpawn = EffectVisualEffect(VFX_FNF_STRIKE_HOLY); break;
          case 09: eSpawn = EffectVisualEffect(VFX_FNF_IMPLOSION); break;
          default: eSpawn = EffectVisualEffect(VFX_NONE); break;
        }
        ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eSpawn, lCurrentWP);
      }
      CreateObject(OBJECT_TYPE_CREATURE, sTemplate, lCurrentWP, TRUE);
    }
    int iNearest = 1;
    int iCountNearest = 0;
    object oLastSpawned = GetNearestObjectByTag(
                            GetLocalString(oNearest, "Tag"), oNearest, iNearest);
    while (iCountNearest != 1) {
      if (GetLocalString(oLastSpawned, "SpawnedBy") == "") {
        SetLocalString(oLastSpawned, "SpawnedBy", ObjectToString(oNearest));
        if (iDebug) { // Debug message
          SendMessageToAllDMs("Object string - " + GetLocalString(
                               oLastSpawned, "SpawnedBy"));
        }
        iCountNearest = 1;
      }
      iNearest++;
      oLastSpawned = GetNearestObjectByTag(
                       GetLocalString(oNearest, "Tag"), oNearest, iNearest);
      if (!GetIsObjectValid(oLastSpawned)) {
        iCountNearest = 1;
      }
    }
    iTotalX++;
  }
}
string sp_CleanString(object oNearest, string sType)
{
  string sCurrentTag = GetTag(oNearest);
  if (GetStringLowerCase(GetStringLeft(sCurrentTag, 3)) == "wp_" && sType == "Tag") {
    sCurrentTag = GetStringRight(sCurrentTag, GetStringLength(sCurrentTag) - 3);
    if (StringToInt(GetStringRight(sCurrentTag, 2)) &&
        GetSubString(sCurrentTag, GetStringLength(sCurrentTag) - 3, 1) == "_") {
      sCurrentTag = GetStringLeft(sCurrentTag, GetStringLength(sCurrentTag) - 3);
    }
  }
  else if (GetStringLowerCase(
           GetStringLeft(sCurrentTag, 5)) == "post_" && sType == "Tag") {
    sCurrentTag = GetStringRight(sCurrentTag, GetStringLength(sCurrentTag) - 5);
  }
  else if (sType == "Blueprint") {
    sCurrentTag = GetLocalString(oNearest, "Tag");
    sCurrentTag = GetStringLowerCase(sCurrentTag);
  }
  return sCurrentTag;
}
int sp_CheckDistance(object oNearest, int iSpawnDistance)
{
  int iRD = 0;
  if (iSpawnDistance >= 1) {
    object oPC = GetFirstPC();
    if (GetIsObjectValid(oPC)) {
      while (GetIsObjectValid(oPC)) {
        if (GetDistanceBetween(oNearest, oPC) <= (IntToFloat(iSpawnDistance))) {
          if (!GetLocalInt(oNearest, "PC_Entered")) {
            SetLocalInt(oNearest, "PC_Entered", 1);
          }
          iRD = 1;
        }
        oPC = GetNextPC();
      }
    }
    if (iRD != 1) {
      SetLocalInt(oNearest, "PC_Entered", 0);
    }
  }
  else {
    iRD = 1;
  }
  return iRD;
}
int sp_CheckTimeOfDay(object oNearest)
{
  int iValidTime = 0;
  if (GetIsDay() && GetLocalInt(oNearest, "SpawnDay") == 1) {
    iValidTime = 1;
  }
  else if (GetIsNight() && GetLocalInt(oNearest, "SpawnNight") == 1) {
    iValidTime = 1;
  }
  else if (GetLocalInt(oNearest, "SpawnNight") == 0 &&
           GetLocalInt(oNearest, "SpawnDay") == 0) {
    iValidTime = 1;
  }
  return iValidTime;
}
int sp_CheckCreatures(object oNearest, string sCurrentTag)
{
  int iMaxXX = GetLocalInt(oNearest, "SpawnAmount");
  int iCountSX = 1;
  int iCountSX_Dead = 0;
  object oCurrent = GetNearestObjectByTag(sCurrentTag, OBJECT_SELF, iCountSX);
  while (iCountSX <= iMaxXX) {
    if (GetIsObjectValid(oCurrent) == FALSE) {
      iCountSX_Dead++;
    }
    else if (GetObjectType(oCurrent) == 32) {
      iMaxXX++;
    }
    else if (GetLocalString(oCurrent, "SpawnedBy") != ObjectToString(oNearest) &&
             iDebug) { // Debug message
      SendMessageToAllDMs("Creature " + GetTag(oCurrent) + " = "+
                           GetLocalString(oCurrent, "SpawnedBy"));
      SendMessageToAllDMs("Spawn Point " + GetTag(oNearest) + " = " +
                           ObjectToString(oNearest));
      iMaxXX++;
    }
    iCountSX++;
    oCurrent = GetNearestObjectByTag(sCurrentTag, OBJECT_SELF, iCountSX);
  }
  return iCountSX_Dead;
}
int sp_GetValue(object oNearest, string sSwitch, string sCurrentName)
{
  int iSwitchValue = 0;
  int iIsMinutes = 0;
  if (FindSubString(sCurrentName, sSwitch) > 0) {
    if (sSwitch == "DT") {
      iSwitchValue = 1;
    }
    else if (sSwitch == "NT") {
      iSwitchValue = 1;
    }
    else if (sSwitch == "TR") {
      iSwitchValue = 1;
    }
    else {
      int iNumberPos = FindSubString(sCurrentName, sSwitch);
      if (sSwitch == "TM") {
        if (GetSubString(sCurrentName, (iNumberPos + 4), 1) == "M") {
          iIsMinutes = 1;
        }
      }
      int iNumberLength = GetStringLength(sCurrentName) - iNumberPos;
      string sSwitchValue = GetSubString(sCurrentName, (iNumberPos + 2), 2);
      iSwitchValue = StringToInt(sSwitchValue);
      if (iIsMinutes == 1) {
        iSwitchValue = iSwitchValue * 60;
      }
      if (GetSubString(sCurrentName, (iNumberPos + 4), 1) == "R") {
        int iMaxRand = StringToInt(GetSubString(sCurrentName, (iNumberPos + 5), 2));
        if (sSwitch == "SX") {
          if (iSwitchValue < 1) {
            iSwitchValue = 1;
          }
          else if (iMaxRand < 2) {
            iMaxRand = 2;
          }
          SetLocalInt(oNearest, "SpawnRandomNumber", iMaxRand - iSwitchValue);
        }
        else {
          if (GetSubString(sCurrentName, (iNumberPos + 7), 1) == "M") {
            iIsMinutes = 1;
            iMaxRand = iMaxRand * 60;
            iSwitchValue = iSwitchValue * 60;
          }
          SetLocalInt(oNearest, "TimerIsRandom", iMaxRand - iSwitchValue);
        }
      }
    }
  }
  else if (sSwitch == "SX") {
    iSwitchValue = 1;
  }
  return iSwitchValue;
}
location sp_RandomLocation(object oNearest, int iRandArea)
{
  vector vNewPos = GetPosition(oNearest);
  object oArea = GetArea(oNearest);
  float fX, fY;
  float fRadius = IntToFloat(iRandArea);
  int iValid = FALSE;
  while (iValid != TRUE)
  {
    fX = (Random(200)/100.0 - 1.0) * fRadius;
    fY = (Random(200)/100.0 - 1.0) * fRadius;
    if (fX * fX + fY * fY <= fRadius * fRadius) {
      iValid = TRUE;
    }
  }
  vNewPos += Vector(fX, fY, 0.0);
  location lNewLoc = Location(oArea, vNewPos, VectorToAngle(-1.0 * vNewPos));
  return lNewLoc;
}