/*##
# UO Style Spawn System v2.2
# 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.
#  -Spawn a random amount of creatures between 1 and 99.
#  -Spawn a group of creatures.
#  -Spawn random creatures from a group.
#  -Create your own groups or customize existing ones.
#  -Use a delay of up to 99 hours between each spawn.
#  -Use random delay of up to 99 hours.
#  -Spawn just at night or during the day.
#  -Spawn only at a specific hour or between certain hours.
#  -Spawn only on a specific day or between certain days.
#  -Spawn only if a PC is within up to 99 meters.
#  -Spawn creatures in random locations within a specified radius
#   of the spawn point.
#  -Spawn creatures in random locations around the players.
#  -Tell spawn point not to spawn if a player enters the radius
#   and are already in combat.
#  -Spawn treasure chests or any other valid Placeable.
#  -Use Effects when it spawns or kills the creatures.
#  -Setup radius based damage (traps) and have effects applied
#   to simulate poisonous fogs or other effect.
#  -Easily create "magic" amulets that can protect players
#   from the "traps".
#  -Have spawned creatures walk around randomly without editing
#   their OnSpawn scripts.
#  -Generate treasure on spawned creatures without editing
#   their OnSpawn scripts.
#  -Have the spawner wait until all creatures are killed before
#   spawning again.
#  -Tell the spawner to stop spawning after all the creatures
#   it spawns are killed.
#  -One script :)
#
##*
# 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 : TagOfCreatureYouWantToSpawn
#
# The above would make a single spawn point which would
# always keep one of your creatures spawned there.
#
# NOTE:
# Waypoint and Post works with this script.
# Example:
#  Name: SP_SX01
#  Tag : POST_NW_DOG
#
# The above would keep one dog spawned at the waypoint and
# the creature would return to it after combat.
#
##*
# 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.
#
# NP        When used with SR, it spawns in random locations near
#           the closest player to the spawn point.
#
# RDnn      Only spawn creatures if a Player is within "nn" meters.
#
# IC        Do not spawn when a player enters the radius and
#           is already in combat.
#
# 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.
#
# SHnnTyy   Spawn only at nn hour of the day. This is in 23 hour
#           format. If T is specified it will spawn between the
#           hours of nn and yy.
#           Example:
#            SH23    - Spawn only while it is 11pm.
#            SH00    - Spawn only while it is midnight.
#            SH01    - Spawn only while it is 1am.
#            SH01T03 - Spawn only between 1am and 3am.
#
# SDnnTyy   Spawn only on nn day of the month. If T is specified
#           it will only spawn between the days of nn and yy.
#           Example:
#            SD01    - Spawn only the first day of the month.
#            SD01T03 - Spawn only between the first and third
#                      days of the month.
#
# 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.
#
# RW        If specified, the creature will walk randomly around
#           after spawning. If the creature moves to another zone
#           it will cease to randm walk unless you uncomment
#           the HeartBeat in the creatures OnSpawn script.
#
# SS        Stops Spawn when all creatures are killed at spawn
#           point. NOTE: ALL creatures must be killed. The
#           spawn point will continue to spawn until it finds
#           no creatures at the spawn point.
#
# 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.
#
# TMxxRyy   Delay spawns by "xx" seconds. Specify Minutes by adding
#           the M on the end or specify Hours by adding a H.
#           Use R to generate a random number between xx and yy.
#           Examples:
#            TM02     - Delay spawn for 2 seconds.
#            TM02M    - Delay spawn for 2 minutes.
#            TM02H    - Delay spawn for 2 hours.
#            TM10R30  - Random between 10 and 30 seconds.
#            TM10R30M - Random between 10 and 30 minutes.
#            TM10R30H - Random between 10 and 30 hours.
#
# DS        Wait until all creatures are killed before spawning
#           any more.
#
# NF        When used with the timers it tells the spawn system
#           to NOT bypass the timers on the very first spawn.
#
# 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
#
# VKnn      Same as above, except it happens when the spawner
#           kills a creature due to it not being a valid
#           spawn time. Example would be if it turned night
#           and the creature is suppose to only spawn during
#           the day :)
#
# TPnnAyy   Use trap nn at the spawn point. This works with
#           creature spawns. If A is specified and an amulet is
#           listed in the amulet section, whoever is wearing it
#           will not be affected.
#           Examples:
#            TP01    - Use trap 01.
#            TP01A01 - Amulet 01 protects against the trap.
#            TP01A02 - Amulet 02 protects against the trap.
#           Traps are "Damage Effects" with a visual effect
#           applied to the target.
#
# GTnn      Generate treasure. If specified it will generate
#           treasure on the creatures that are spawned. This
#           uses the GenerateTreasure function found in the
#           default NW_O2_CONINCLUDE file.
#           Valid types are:
#           GT01 - Generate low treasure.
#           GT02 - Generate medium treasure.
#           GT03 - Generate high treasure.
#           GT04 - Generate boss treasure.
#           GT05 - Generate book treasure.
#
# CPnn      Spawn a creature camp.
#           Example:
#            CP01 - Will place the objects listed in camp01
#                   in random locations around the site.
#
# GP        Spawn a group of creatures using the default
#           group templates. These can be customized if you
#           so desire :)            NOTE: The SX tag is not used when
#           spawning groups without the GR switch.
#           Example:
#            Name: SP_GP
#            Tag : Group01
#
# GR        Spawn a random creature from the specified group.
#           When used with the SX switch it will spawn up
#           to "nn" random creatures from the specified
#           group.
#           Example:
#            Name: SP_SX03_GP_GR
#            Tag : Group01
#
##*
# Default Groups
#
# Group 01
#  Creature 1 - NW_SKELETON
#  Creature 2 - NW_LICH003
#  Creature 3 - NW_GHOUL
#  Creature 4 - NW_MUMMY
#  Creature 5 - NW_WRAITH
#
# Group 02
#  Creature 1 - NW_ORCA
#  Creature 2 - NW_ORCB
#  Creature 3 - NW_OrcChiefA
#  Creature 4 - NW_ORCCHIEFB
#  Creature 5 - NW_ORCWIZA
#
##*
# Default Camps
#
# Object 1 - Corpse wagon.
#
##*

# Changes
#
# 2.2.2b
#  - Fixed bug with Traps. - Reported by keet
#
# 2.2.2
#  - Modified minor functions to lessen CPU usage.
#  - Finished code structure support for Champions and Area Effects
#
# 2.2.1b
#  - Added RF feature which tells the creature to face a
#    random direction after spawning. - Requested by jwvanderbeck
#  - Added AE which is used to add an Area Effect to a location.
#    - Requested by Master_of_Magic
#
# 2.2.1a
#  - Added random spawn for Champions
#
# 2.2.1
#  - Fixed bug with deleting creatures spawned using random
#    option. - Reported by jwvanderbeck
#  - Added support for Champion Spawns using CHnn.
#
# 2.2
#  - Added DS for delaying spawn until are creatures are killed.
#    - Suggested by Michael L. Hoehne
#  - Added SH and SD for spawning only at a certain hour(s) of
#    the day or on a certain day(s) of the month.
#    - Suggested by MokahTGS
#  - Added TP for trap effects. - Suggested by Moose_Dude
#  - Added CP for spawning camp items.
#
# 2.1.9e
#  - Modified a few functions to use less CPU.
#  - Changed how the spawn points keep track of objects
#    due to a new feature being implemented in 2.2
#
# 2.1.9d
#  - Fixed bug with default orc group. - Reported by Qobra
#  - Changed GP to spawn whole group and added GR for using
#    random creatures from the group.
#
# 2.1.9a - c
#  - Modified some code structure to lessen load on CPU.
#  - Fixed bug with RD. - Reported by Qobra
#  - Added GT function to generate treasure on creatures.
#  - Added default Orc group (Group02).
#
# 2.1.9
#  - More code cleanup :)
#  - Added GP for use with spawning groups.
#  - Added default Undead group (Group01).
#
# 2.1.8
#  - Major code changes to reduce CPU usage. Also made
#    modifications that will be used by version 2.2
#
# 2.1.7b
#  - Fixed bug with Random when it was only 2 max :)
#    - Reported by Karthal
#  - Added S switch for SXnn. When used it spawns each
#    create seperate. It will use a delay if specified
#    with TM, but it will not work with random number
#    of creatures.
#    Examples:
#     SX05S      - Will spawn 5 creatures, one at a time.
#     SX05S_TM10 - Will spawn 5 creatures, one at a time
#                  and with a 10 second delay.
#
# 2.1.7a
#  - Fixed a bug with delay not working with radius.
#    - Reported by SebastianCain
#
# 2.1.7
#  - Added NP, which when used with SR, spawns in random
#    locations around the nearest player.
#    - Suggested by Karthal
#  - Added NF, which tells the spawned to not spawn the first
#    batch bypassing the timers. - Suggested by Karthal
#  - Added IC, which when used with RD, tells the spawner
#    not to spawn when a player enters the radius and
#    is already in combat. - Suggested by Karthal
#  - Added SS, which stops the spawn after all creatures
#    are killed. - Suggested by -Mange-
#
# 2.1.6a
#  - Modified RW so that every check it will have the
#    creature random walk if it is not in combat or
#    in a conversation. - Suggested by Qobra
#
# 2.1.6
#  - Modified random counter. - Reported by Dhoff
#  - Added Hours for delay. - Requested by multiple people
#  - Added Effect for when spawner kills creatures.
#  - Added RW (RandomWalk)
#
# 2.1.5b
#  - Fixed a bug with MIN not counting correctly.
#    - Reported by EVERYONE :)
#
# 2.1.5a
#  - Removed where it took 1 off the total of spawns when
#    used with the random option. - Reported by stefulia
#
# 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)
#
##*/
#include "NW_O2_CONINCLUDE"
int iDebug = 0;
void sp_DestroyObject(object oNearest, string sTagType);
void sp_CheckVars(object oNearest, string sCurrentName);
void sp_Spawn(object oNearest, string sTemplate, int iSpawnX);
void sp_TagObject(object oNearest, string sCurrentTag, string sTagType);
void sp_SpawnCamp(object oNearest, location lCurrentWP);
void sp_CheckTrap(object oNearest, object oPC);
void sp_AreaEffect(object oNearest);
string sp_SpawnChamp(object oNearest);
string sp_CleanString(object oNearest, string sType);
string sp_GetGroupCreature(object oNearest, int iGroupTotal);
int sp_GetValue(object oNearest, string sSwitch, string sCurrentName);
int sp_CreaturesToSpawn(object oNearest, int iAlive);
int sp_CheckDistance(object oNearest);
int sp_CheckTimeOfDay(object oNearest);
int sp_CreaturesAlive(object oNearest);
int sp_GetGroupTotal(object oNearest, string sTagType);
location sp_RandomLocation(object oNearest, int iRandArea);
void main()
{
/*
# Groups
#
# Undead 1
*/
SetLocalString(OBJECT_SELF, "group01_1", "NW_SKELETON");
SetLocalString(OBJECT_SELF, "group01_2", "NW_LICH003");
SetLocalString(OBJECT_SELF, "group01_3", "NW_GHOUL");
SetLocalString(OBJECT_SELF, "group01_4", "NW_MUMMY");
SetLocalString(OBJECT_SELF, "group01_5", "NW_WRAITH");
/*
# Orcs 1
*/
SetLocalString(OBJECT_SELF, "group02_1", "NW_ORCA");
SetLocalString(OBJECT_SELF, "group02_2", "NW_ORCB");
SetLocalString(OBJECT_SELF, "group02_3", "NW_OrcChiefA");
SetLocalString(OBJECT_SELF, "group02_4", "NW_ORCCHIEFB");
SetLocalString(OBJECT_SELF, "group02_5", "NW_ORCWIZA");
SetLocalString(OBJECT_SELF, "group02_6", "NW_ORCWIZB");
/*
# Camp Equipment
#
# Corpse Wagon
*/
SetLocalString(OBJECT_SELF, "camp01_tag_1",    "FlyCladCorpseWagon");
SetLocalString(OBJECT_SELF, "camp01_resref_1", "plc_fcrpsewagon");
/*
# Champions
#
*/
SetLocalString(OBJECT_SELF, "champ01", "NW_ORCWIZB");
/*
#
##*/
  // Check if spawn sites are set and if not store their locations.
  int nNth = 1;
  object oNearest;
  if (!GetIsObjectValid(GetLocalObject(OBJECT_SELF, "SpawnSite1"))) {
    nNth = 1;
    int iSpawnSite = 1;
    oNearest = GetNearestObject( 32, OBJECT_SELF, nNth );
    while (GetIsObjectValid(oNearest))
    {
      if (GetStringLeft(GetName(oNearest), 3) == "SP_") {
        SetLocalObject(OBJECT_SELF, "SpawnSite" +
                       IntToString(iSpawnSite), oNearest);
        iSpawnSite++;
      }
      nNth++;
      oNearest = GetNearestObject(32, OBJECT_SELF, nNth);
    }
  }
  // If spawn sites exist, start the process.
  nNth = 1;
  oNearest = GetLocalObject(OBJECT_SELF, "SpawnSite" + IntToString(nNth));
  while (GetIsObjectValid(oNearest))
  {
    // Check spawner and see if a timer is already running and if
    // the spawn site has been destroyed or not.
    if (!GetLocalInt(oNearest, "IsSpawning") &&
        !GetLocalInt(oNearest, "SpawnDestroyed")) {
      // Get the Tag.
      if (GetLocalString(oNearest, "Tag") == "") {
        SetLocalString(oNearest, "Tag", sp_CleanString(oNearest, "Tag"));
      }
      // Get the Blueprint.
      string sBluePrint;
      if (GetLocalString(oNearest, "Blueprint") == "") {
        SetLocalString(oNearest, "Blueprint",
                       sp_CleanString(oNearest, "Blueprint"));
        sBluePrint = GetLocalString(oNearest, "Blueprint");
        if (iDebug) // Debug message
         SendMessageToAllDMs("Blueprint = " + sBluePrint);
      }
      else {
        sBluePrint = GetLocalString(oNearest, "Blueprint");
      }
      // Check if variables are already set.
      if (!GetLocalInt(oNearest, "IsSpawner")) {
        sp_CheckVars(oNearest, GetName(oNearest));
      }
      // Start of Main Procedure.
      //
      // We will use this to see if we should spawn or not.
      int iIsValid = 1;
      int iDontSpawn;
      if (sBluePrint == "" || sBluePrint == "effect") {
        iDontSpawn = 1;
      }
      // Check how many creatures are at the spawn point.
      int iAlive;
      if (!iDontSpawn) {
        iAlive = sp_CreaturesAlive(oNearest);
      }
      // Check for time of day.
      if (GetLocalInt(oNearest, "SpawnDay") ||
          GetLocalInt(oNearest, "SpawnNight") ||
          GetLocalInt(oNearest, "DaySpawn") ||
          GetLocalInt(oNearest, "HourSpawn")) {
        if (sp_CheckTimeOfDay(oNearest) != 1) {
          iIsValid = 0;
          if (iAlive) {
            sp_DestroyObject(oNearest, "Creature");
            if (GetLocalInt(oNearest, "Camp")) {
              sp_DestroyObject(oNearest, "Camp");
              SetLocalInt(oNearest, "CampExists", 0);
            }
          }
        }
      }
      // Check for PC in radius (if a radius is used).
      if (GetLocalInt(oNearest, "PC_Radius")) {
        int iRD = sp_CheckDistance(oNearest);
        if (iRD != 1) {
          iIsValid = 0;
          if (iRD != 1 && iAlive) {
            sp_DestroyObject(oNearest, "Creature");
            if (GetLocalInt(oNearest, "Camp")) {
              sp_DestroyObject(oNearest, "Camp");
              SetLocalInt(oNearest, "CampExists", 0);
            }
          }
        }
      }
      //
      if (iIsValid && GetLocalInt(oNearest, "AreaEffect")) {
        sp_AreaEffect(oNearest);
      }
      // Check if the spawn is suppose to wait till all
      // creatures are dead.
      int iToSpawn;
      if (GetLocalInt(oNearest, "DelaySpawn") && iAlive >= 1) {
        iIsValid = 0;
      }
      // Check for Champion
      else if (iIsValid && !iDontSpawn &&
               GetLocalInt(oNearest, "ChampSpawn") &&
               GetLocalInt(oNearest, "HasSpawned") &&
              !GetLocalInt(oNearest, "ChampHasSpawned")) {
        string sChamp = sp_SpawnChamp(oNearest);
        if (sChamp != "") {
          iToSpawn = 1;
          sBluePrint = sChamp;
        }
      }
      else if (GetLocalInt(oNearest, "ChampHasSpawned")) {
        DeleteLocalInt(oNearest, "ChampHasSpawned");
      }
      // Check for missing creatures.
      if (!GetLocalInt(oNearest, "SpawnRandom") && !iDontSpawn) {
        if (iAlive == GetLocalInt(oNearest, "SpawnAmount")) {
          iIsValid = 0;
        }
      }
      if (iIsValid && !GetLocalInt(oNearest, "SpawnChamp") && !iDontSpawn) {
        iToSpawn = sp_CreaturesToSpawn(oNearest, iAlive);
        if (iToSpawn == 0) {
          iIsValid = 0;
        }
        else if (iToSpawn == GetLocalInt(oNearest, "SpawnAmount") &&
                             GetLocalInt(oNearest, "HasSpawned") &&
                             GetLocalInt(oNearest, "StopSpawn")) {
          SetLocalInt(oNearest, "SpawnDestroyed", 1);
          iIsValid = 0;
        }
        if (iDebug) { // Debug
         SendMessageToAllDMs("Missing " + IntToString(iToSpawn) +
                              " at " + GetTag(oNearest));
        }
      }
      // If everything is ok, spawn.
      if (iIsValid && iToSpawn && !iDontSpawn) {
        SetLocalInt(oNearest, "IsSpawning", 1);
        int iSpawnTime;
        float fSpawnTime;
        string sSpawnTimer;
        string sTimerRandom;
        if (GetLocalInt(oNearest, "SpawnTimer")) {
          sSpawnTimer = IntToString(GetLocalInt(oNearest, "SpawnTimer"));
        }
        if (GetLocalInt(oNearest, "TimerIsRandom")) {
          sTimerRandom = IntToString(GetLocalInt(oNearest, "TimerIsRandom"));
        }
        if (GetLocalInt(oNearest, "HasSpawned")) {
          if (iDebug) { // Debug message
            SendMessageToAllDMs("Timer " + sSpawnTimer +
                                 " Random " +
                                  sTimerRandom);
          }
          if (GetLocalInt(oNearest, "TimerIsRandom")) {
            iSpawnTime = StringToInt(sSpawnTimer) +
                                     Random(StringToInt(sTimerRandom));
            fSpawnTime = IntToFloat(iSpawnTime);
          }
          else {
            fSpawnTime = StringToFloat(sSpawnTimer);
          }
        }
        else {
          SetLocalInt(oNearest, "HasSpawned", 1);
          fSpawnTime = 0.0;
        }
        if (iDebug) { // Debug message
          SendMessageToAllDMs("Spawning " +
            sBluePrint + " delayed for " + IntToString(FloatToInt(fSpawnTime)));
        }
        AssignCommand(OBJECT_SELF,
          DelayCommand(
          fSpawnTime,
          sp_Spawn(oNearest, sBluePrint, iToSpawn)));
        AssignCommand(OBJECT_SELF,
          DelayCommand(
          fSpawnTime,
          SetLocalInt(oNearest, "IsSpawning", 0)));
      }
      //
      // End of Main Procedure
    }
    nNth++;
    oNearest = GetLocalObject(OBJECT_SELF, "SpawnSite" + IntToString(nNth));
  }
}
// No comments beyond this point :)
void sp_DestroyObject(object oNearest, string sTagType)
{
  if (iDebug) { // Debug
    SendMessageToAllDMs("Killing Creatures...");
  }
  int iCurObject;
  object oCreatureToKill;
  int iNumObjects;
  if (sTagType == "Camp") {
    iNumObjects = sp_GetGroupTotal(oNearest, "Camp");
  }
  else {
    iNumObjects = GetLocalInt(oNearest, "SpawnAmount") +
                  GetLocalInt(oNearest, "SpawnRandomNumber");
  }
  for (iCurObject = iNumObjects; iCurObject > 0; iCurObject--) {
    oCreatureToKill = GetLocalObject(oNearest, sTagType +
                                               IntToString(iCurObject));
    if (GetIsObjectValid(oCreatureToKill)) {
      if (GetLocalObject(oCreatureToKill, "SpawnedBy") == oNearest) {
        if (!GetIsInCombat(oCreatureToKill) || sTagType == "Camp") {
          if (iDebug) { // Debug
            SendMessageToAllDMs("Killing " + sTagType);
            SendMessageToAllDMs("Tag - " + GetTag(oCreatureToKill));
          }
          if (GetLocalInt(oNearest, "DeathEffect") && sTagType == "Creature") {
            effect eDeath;
            switch (GetLocalInt(oNearest, "DeathEffect")) {
              case 01: eDeath = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_1); break;
              case 02: eDeath = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_2); break;
              case 03: eDeath = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_3); break;
              case 04: eDeath = EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD); break;
              case 05: eDeath = EffectVisualEffect(VFX_FNF_SUNBEAM); break;
              case 06: eDeath = EffectVisualEffect(VFX_FNF_WAIL_O_BANSHEES); break;
              case 07: eDeath = EffectVisualEffect(VFX_FNF_STORM); break;
              case 08: eDeath = EffectVisualEffect(VFX_FNF_STRIKE_HOLY); break;
              case 09: eDeath = EffectVisualEffect(VFX_FNF_IMPLOSION); break;
                default: eDeath = EffectVisualEffect(VFX_NONE); break;
            }
            ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eDeath,
                                  GetLocation(oCreatureToKill));
          }
          DestroyObject(oCreatureToKill, 0.0);
        }
      }
    }
  }
}
void sp_CheckVars(object oNearest, string sCurrentName)
{
  if (iDebug)
    SendMessageToAllDMs("Checking Variables..."); // Debug
  SetLocalInt(oNearest, "GroupRandom",  sp_GetValue(oNearest, "GR", sCurrentName));
  SetLocalInt(oNearest, "GroupSpawn",   sp_GetValue(oNearest, "GP", 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, "DeathEffect",  sp_GetValue(oNearest, "VK", sCurrentName));
  SetLocalInt(oNearest, "RandomWalk",   sp_GetValue(oNearest, "RW", sCurrentName));
  SetLocalInt(oNearest, "NearPlayer",   sp_GetValue(oNearest, "NP", sCurrentName));
  SetLocalInt(oNearest, "InCombat",     sp_GetValue(oNearest, "IC", sCurrentName));
  SetLocalInt(oNearest, "StopSpawn",    sp_GetValue(oNearest, "SS", sCurrentName));
  SetLocalInt(oNearest, "GenTreasure",  sp_GetValue(oNearest, "GT", sCurrentName));
  SetLocalInt(oNearest, "DelaySpawn",   sp_GetValue(oNearest, "DS", sCurrentName));
  SetLocalInt(oNearest, "Camp",         sp_GetValue(oNearest, "CP", sCurrentName));
  SetLocalInt(oNearest, "DaySpawn",     sp_GetValue(oNearest, "SD", sCurrentName));
  SetLocalInt(oNearest, "HourSpawn",    sp_GetValue(oNearest, "SH", sCurrentName));
  SetLocalInt(oNearest, "TrapType",     sp_GetValue(oNearest, "TP", sCurrentName));
  SetLocalInt(oNearest, "ChampSpawn",   sp_GetValue(oNearest, "CH", sCurrentName));
  SetLocalInt(oNearest, "RandomFacing", sp_GetValue(oNearest, "RF", sCurrentName));
  SetLocalInt(oNearest, "AreaEffect",   sp_GetValue(oNearest, "AE", sCurrentName));
  SetLocalInt(oNearest, "DontZone",     sp_GetValue(oNearest, "DZ", sCurrentName));
  SetLocalInt(oNearest, "NoFirst",      sp_GetValue(oNearest, "NF", sCurrentName));
  if (GetLocalInt(oNearest, "NoFirst")) {
    SetLocalInt(oNearest, "HasSpawned", 1);
  }
  SetLocalInt(oNearest, "IsSpawner",   1);
}
void sp_Spawn(object oNearest, string sTemplate, int iSpawnX)
{
  if (iDebug)
    SendMessageToAllDMs("Spawning..."); // Debug
  location lCurrentWP = GetLocation(oNearest);
  string sCurrentTag;
  if (GetLocalInt(oNearest, "SpawnChamp")) {
    sCurrentTag = GetLocalString(OBJECT_SELF, sTemplate);
    sTemplate = GetStringLowerCase(GetLocalString(OBJECT_SELF, sTemplate));
    SetLocalInt(oNearest, "ChampHasSpawned", 1);
    DeleteLocalInt(oNearest, "SpawnChamp");
  }
  else {
    sCurrentTag = GetLocalString(oNearest, "Tag");
  }
  int iGroupTotal;
  int iValid;
  int iTotalX;
  for (iTotalX = 0; iTotalX < iSpawnX; iTotalX++) {
    iValid = 1;
    if (GetLocalInt(oNearest, "GroupSpawn")) {
      iGroupTotal = sp_GetGroupTotal(oNearest, "Creature");
      sCurrentTag = sp_GetGroupCreature(oNearest, iGroupTotal);
      sTemplate = GetStringLowerCase(sCurrentTag);
    }
    if (GetLocalInt(oNearest, "NPC_Radius") && iValid) {
      if (GetLocalInt(oNearest, "NearPlayer")) {
        object oPlayer = GetNearestCreature(
                            CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC,
                            oNearest);
        if (GetIsObjectValid(oPlayer)) {
          lCurrentWP = sp_RandomLocation(oPlayer,
                                         GetLocalInt(oNearest, "NPC_Radius"));
        }
        else {
          iValid = 0;
          if (iDebug)
            SendMessageToAllDMs("Player is not valid..."); // Debug
        }
      }
      else {
        lCurrentWP = sp_RandomLocation(oNearest, GetLocalInt(oNearest,
                                                             "NPC_Radius"));
      }
    }
    if (GetLocalInt(oNearest, "IsTreasure") && iValid)
    {
      object oTreasure = GetNearestObjectByTag(sTemplate, oNearest);
      if (GetIsObjectValid(oTreasure))
      {
          if (iDebug) SendMessageToAllDMs("Found " + sTemplate + " ... respawning.");
              SetLocalInt(oTreasure, "NW_DO_ONCE",0);
      }
      else
      {
        if (iDebug) SendMessageToAllDMs("Found " + sTemplate + " not found ... creating");
        CreateObject(OBJECT_TYPE_PLACEABLE, sTemplate, lCurrentWP, FALSE);
      }
    }
    else {
      if (GetLocalInt(oNearest, "SpawnEffect") && iValid) {
        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);
    }
    if (GetLocalInt(oNearest, "Camp") && !GetLocalInt(oNearest, "CampExists")) {
      sp_SpawnCamp(oNearest, lCurrentWP);
    }
    sp_TagObject(oNearest, sCurrentTag, "Creature");
  }
}
string sp_CleanString(object oNearest, string sType)
{
  if (iDebug)
    SendMessageToAllDMs("Cleaning String..."); // Debug
  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)
{
  if (iDebug) { // Debug
    SendMessageToAllDMs("Checking Distance...");
  }
  int iRD = 0;
  int iSpawnDistance = GetLocalInt(oNearest, "PC_Radius");
  if (iSpawnDistance >= 1) {
    object oArea = GetArea(oNearest);
    object oPC = GetFirstPC();
    if (GetIsObjectValid(oPC)) {
      while (GetIsObjectValid(oPC)) {
        if (GetArea(oPC) == oArea) {
          if (GetDistanceBetween(oNearest, oPC) <= IntToFloat(iSpawnDistance)) {
            if (GetLocalInt(oNearest, "TrapType")) {
              sp_CheckTrap(oNearest, oPC);
            }
            if (GetLocalInt(oNearest, "InCombat") && GetIsInCombat(oPC)) {
              iRD = 2;
            }
            else {
              iRD = 1;
            }
          }
        }
        oPC = GetNextPC();
      }
    }
  }
  else {
    iRD = 1;
  }
  return iRD;
}
int sp_CheckTimeOfDay(object oNearest)
{
  if (iDebug)
    SendMessageToAllDMs("Checking Time of Day..."); // Debug
  int iValidTime = 0;
  if (GetIsDay() && GetLocalInt(oNearest, "SpawnDay") == 1) {
    iValidTime = 1;
  }
  else if (GetIsNight() && GetLocalInt(oNearest, "SpawnNight") == 1) {
    iValidTime = 1;
  }
  else if (GetLocalInt(oNearest, "DaySpawn")) {
    int iCalDay = GetCalendarDay();
    int iStartDay = GetLocalInt(oNearest, "DaySpawn");
    if (GetLocalInt(oNearest, "TimeThrough")) {
      int iStopDay = GetLocalInt(oNearest, "TimeThrough");
      if (iStopDay > iStartDay) {
        int i;
        for (i = iStartDay; i <= iStopDay; i++) {
          if (i == iCalDay) {
            iValidTime = 1;
          }
        }
      }
    }
    else if (iCalDay == iStartDay) {
      iValidTime = 1;
    }
  }
  else if (GetLocalInt(oNearest, "HourSpawn")) {
    int iHour = GetTimeHour();
    int iStartHour = GetLocalInt(oNearest, "HourSpawn");
    if (GetLocalInt(oNearest, "TimeThrough")) {
      int iStopHour = GetLocalInt(oNearest, "TimeThrough");
      if (iStopHour > iStartHour) {
        int i;
        for (i = iStartHour; i <= iStopHour; i++) {
          if (i == iHour) {
            iValidTime = 1;
          }
        }
      }
    }
    else if (iHour == iStartHour) {
      iValidTime = 1;
    }
  }
  return iValidTime;
}
int sp_CreaturesAlive(object oNearest)
{
  if (iDebug)
    SendMessageToAllDMs("Counting Creatures..."); // Debug
  int iMin = GetLocalInt(oNearest, "SpawnAmount");
  int iMaxXX = iMin;
  if (GetLocalInt(oNearest, "SpawnRandomNumber") > 0) {
    iMaxXX += GetLocalInt(oNearest, "SpawnRandomNumber");
  }
  int iAlive = 0;
  int iCountSX_Dead = 0;
  int iCreature;
  object oCurrent;
  for (iCreature = iMaxXX; iCreature > 0; iCreature--) {
    oCurrent = GetLocalObject(oNearest, "Creature" + IntToString(iCreature));
    if (GetIsObjectValid(oCurrent)) {
      if (GetLocalInt(oNearest, "IsTreasure"))
      {
        if (GetIsObjectValid(GetFirstItemInInventory(oCurrent)))
            iAlive++;
      }
      else
      {
          iAlive++;
          if (GetLocalInt(oNearest, "RandomWalk")) {
            if (GetCurrentAction(oCurrent) != 36) {
              if (!GetIsInCombat(oCurrent) && !IsInConversation(oCurrent)) {
                AssignCommand(oCurrent, ActionRandomWalk());
              }
            }
          }
      }
    }
  }
  if (iDebug) SendMessageToAllDMs("Found " + IntToString(iAlive) + " " + GetLocalString(oNearest, "Tag"));
  return iAlive;
}
int sp_CreaturesToSpawn(object oNearest, int iAlive)
{
  if (iDebug)
    SendMessageToAllDMs("Determining Number To Spawn..."); // Debug
  int iSpawnRandom = GetLocalInt(oNearest, "SpawnRandomNumber");
  int iMin = GetLocalInt(oNearest, "SpawnAmount");
  int iMaxXX = iMin;
  if (iSpawnRandom > 0) {
    iMaxXX += iSpawnRandom;
  }
  int iCountSX_Dead = iMaxXX - iAlive;
  if (iCountSX_Dead > 0 && iSpawnRandom > 0) {
    int iAlive = iMaxXX - iCountSX_Dead;
    if (iAlive < iMin) {
      int iRand = iMaxXX - iAlive;
      if (iRand == 2) {
        iRand = Random(30);
        if (iRand >= 16) {
          iCountSX_Dead = 2;
        }
        else {
          iCountSX_Dead = 1;
        }
      }
      else {
        iCountSX_Dead = Random(iRand);
      }
      if (iCountSX_Dead < iMin) {
        iCountSX_Dead = iMin + Random(iMaxXX - iMin);
      }
    }
    else {
      iCountSX_Dead = 0;
    }
  }
  if (GetLocalInt(oNearest, "SpawnSingle") && iCountSX_Dead > 1) {
    return 1;
  }
  else {
    return iCountSX_Dead;
  }
}
int sp_GetValue(object oNearest, string sSwitch, string sCurrentName)
{
  if (iDebug)
    SendMessageToAllDMs("Getting Variable Values..."); // Debug
  int iSwitchValue = 0;
  int iIsMinutes = 0;
  int iIsHours = 0;
  if (FindSubString(sCurrentName, sSwitch) > 0) {
    if (sSwitch == "DT" ||
        sSwitch == "NT" ||
        sSwitch == "TR" ||
        sSwitch == "RW" ||
        sSwitch == "NP" ||
        sSwitch == "NF" ||
        sSwitch == "IC" ||
        sSwitch == "SS" ||
        sSwitch == "GP" ||
        sSwitch == "GR" ||
        sSwitch == "RF" ||
        sSwitch == "DZ" ||
        sSwitch == "DS") {
      iSwitchValue = 1;
    }
    else {
      int iNumberPos = FindSubString(sCurrentName, sSwitch);
      if (sSwitch == "TM") {
        if (GetSubString(sCurrentName, (iNumberPos + 4), 1) == "M") {
          iIsMinutes = 1;
        }
        else if (GetSubString(sCurrentName, (iNumberPos + 4), 1) == "H") {
          iIsHours = 1;
        }
      }
      else if (sSwitch == "SX") {
        if (GetSubString(sCurrentName, (iNumberPos + 4), 1) == "S") {
          SetLocalInt(oNearest, "SpawnSingle", 1);
        }
      }
      int iNumberLength = GetStringLength(sCurrentName) - iNumberPos;
      string sSwitchValue = GetSubString(sCurrentName, (iNumberPos + 2), 2);
      iSwitchValue = StringToInt(sSwitchValue);
      if (iIsMinutes == 1) {
        iSwitchValue = iSwitchValue * 60;
      }
      else if (iIsHours == 1) {
        iSwitchValue = (iSwitchValue * 60) * 60;
      }
      if ((sSwitch == "SD" || sSwitch == "SH") &&
           GetSubString(sCurrentName, (iNumberPos + 4), 1) == "T") {
        SetLocalInt(oNearest, "TimeThrough", StringToInt(GetSubString(
                                   sCurrentName, (iNumberPos + 5), 2)));
      }
      if (sSwitch == "TP" && GetSubString(sCurrentName,
                                         (iNumberPos + 4), 1) == "A") {
        SetLocalInt(oNearest, "TrapAmulet", StringToInt(GetSubString(
                                   sCurrentName, (iNumberPos + 5), 2)));
      }
      if (sSwitch == "CH") {
        if (GetSubString(sCurrentName, (iNumberPos + 4), 1) == "R") {
          string sFirstPass = GetStringLowerCase(GetSubString(
                                                 sCurrentName,(iNumberPos + 5),
                                                (GetStringLength(sCurrentName) -
                                                 iNumberPos + 5)));
          string sSecondPass = GetStringLeft(sFirstPass,
                                             FindSubString(sFirstPass, "_"));
          SetLocalInt(oNearest, "ChampNumDie",
                      StringToInt(GetStringLeft(
                                  sSecondPass, FindSubString(sSecondPass, "d"))));
          SetLocalInt(oNearest, "ChampDie",
                      StringToInt(GetStringRight(
                                  sSecondPass, (GetStringLength(sSecondPass) -
                                  FindSubString(sSecondPass, "d")) - 1)));
        }
      }
      else 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 < 1) {
            iMaxRand = 1;
          }
          SetLocalInt(oNearest, "SpawnRandomNumber", iMaxRand - iSwitchValue);
        }
        else if (sSwitch == "AE") {
          SetLocalInt(oNearest, "AreaEffectRadius", iMaxRand);
        }
        else {
          if (GetSubString(sCurrentName, (iNumberPos + 7), 1) == "M") {
            iIsMinutes = 1;
            iMaxRand = iMaxRand * 60;
            iSwitchValue = iSwitchValue * 60;
          }
          else if (GetSubString(sCurrentName, (iNumberPos + 7), 1) == "H") {
            iMaxRand = (iMaxRand * 60) * 60;
            iSwitchValue = (iSwitchValue * 60) * 60;
          }
          SetLocalInt(oNearest, "TimerIsRandom", iMaxRand - iSwitchValue);
        }
      }
      if (sSwitch == "SX" && GetLocalInt(oNearest, "GroupSpawn") &&
          !GetLocalInt(oNearest, "GroupRandom")) {
          iSwitchValue = sp_GetGroupTotal(oNearest, "Creature");
      }
    }
  }
  else if (sSwitch == "SX") {
    if (GetLocalInt(oNearest, "GroupSpawn") &&
        !GetLocalInt(oNearest, "GroupRandom")) {
      iSwitchValue = sp_GetGroupTotal(oNearest, "Creature");
    }
    else {
      iSwitchValue = 1;
    }
  }
  return iSwitchValue;
}
location sp_RandomLocation(object oNearest, int iRandArea)
{
  if (iDebug)
    SendMessageToAllDMs("Choosing Random Location..."); // Debug
  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;
}
int sp_GetGroupTotal(object oNearest, string sTagType) {
  if (iDebug)
    SendMessageToAllDMs("Checking group total..."); // Debug
  int iGroupValid = 0;
  int iGroupTotal = 0;
  string sTemplate;
  if (sTagType == "Creature") {
    if (iDebug)
      SendMessageToAllDMs("Checking group Creature..."); // Debug
    sTemplate = GetLocalString(oNearest, "Tag");
  }
  else if (sTagType == "Camp") {
    sTemplate = "camp";
    if (iDebug)
      SendMessageToAllDMs("Checking group Camp..."); // Debug
    if (GetLocalInt(oNearest, "Camp") < 10) {
      sTemplate += "0" + IntToString(GetLocalInt(oNearest, "Camp"));
    }
    else {
      sTemplate += IntToString(GetLocalInt(oNearest, "Camp"));
    }
  }
  string sGroup;
  while (iGroupValid != 1) {
    iGroupTotal ++;
    if (sTagType == "Creature") {
      sGroup = GetStringLowerCase(sTemplate) + "_" + IntToString(iGroupTotal);
    }
    else if (sTagType == "Camp") {
      sGroup = GetStringLowerCase(sTemplate) + "_resref_" +
                                  IntToString(iGroupTotal);
    }
    if (GetLocalString(OBJECT_SELF, sGroup) == "") {
      iGroupValid = 1;
      iGroupTotal--;
    }
  }
  return iGroupTotal;
}
string sp_SpawnChamp(object oNearest) {
  int iChampValid;
  int iChampNumDie = GetLocalInt(oNearest, "ChampNumDie");
  int iChampDie = GetLocalInt(oNearest, "ChampDie");
  int iDieResult;
  if (!iChampNumDie) {
    iChampValid = 1;
  }
  else {
    switch (iChampDie) {
      case 2:   iDieResult = d2(iChampNumDie);   break;
      case 3:   iDieResult = d3(iChampNumDie);   break;
      case 4:   iDieResult = d4(iChampNumDie);   break;
      case 6:   iDieResult = d6(iChampNumDie);   break;
      case 8:   iDieResult = d8(iChampNumDie);   break;
      case 10:  iDieResult = d10(iChampNumDie);  break;
      case 12:  iDieResult = d12(iChampNumDie);  break;
      case 20:  iDieResult = d20(iChampNumDie);  break;
      case 100: iDieResult = d100(iChampNumDie); break;
      default:;break;
    }
    if (iDieResult == iChampDie / 2) {
      iChampValid = 1;
    }
  }
  string sChamp;
  if (iChampValid) {
    SetLocalInt(oNearest, "SpawnChamp", 1);
    int iChampNum = GetLocalInt(oNearest, "ChampSpawn");
    sChamp = "champ";
    if (iChampNum >= 10) {
      sChamp += IntToString(iChampNum);
    }
    else {
      sChamp += "0" + IntToString(iChampNum);
    }
  }
  return sChamp;
}
string sp_GetGroupCreature(object oNearest, int iGroupTotal) {
  int iSpawnRand;
  string sGroup;
  string sCurrentTag;
  string sTemplate;
  if (GetLocalInt(oNearest, "GroupRandom")) {
    iSpawnRand = Random(iGroupTotal);
    if (iSpawnRand == 0) { iSpawnRand = 1; }
    sGroup = GetStringLowerCase(GetLocalString(oNearest, "Tag")) +
                    "_" + IntToString(iSpawnRand);
    sCurrentTag = GetLocalString(OBJECT_SELF, sGroup);
    sTemplate = sCurrentTag;
    return sTemplate;
  }
  else {
    object oCurrent;
    int iCreature = iGroupTotal;
    int iGroup;
    iGroup = iGroupTotal;
    while (iGroup > 0) {
      sGroup = GetStringLowerCase(GetLocalString(oNearest, "Tag")) +
                      "_" + IntToString(iGroup);
      sCurrentTag = GetLocalString(OBJECT_SELF, sGroup);
      int iValid = 0;
      iCreature = iGroupTotal;
      while (iCreature > 0) {
        oCurrent = GetLocalObject(oNearest, "Creature" +
                                  IntToString(iCreature));
        if (GetIsObjectValid(oCurrent)) {
          if (sCurrentTag == GetTag(oCurrent)) {
            iValid = 1;
          }
        }
        iCreature--;
      }
      if (iValid != 1) {
        sTemplate = sCurrentTag;
        return sTemplate;
      }
      iGroup--;
    }
  }
  if (sTemplate == "") {
    if (iDebug)
      SendMessageToAllDMs("Template is blank"); // Debug
  }
  else if (iDebug) {
    SendMessageToAllDMs("Template - " + sTemplate); // Debug
  }
  return sTemplate;
}
void sp_TagObject(object oNearest, string sCurrentTag, string sTagType) {
    int iNearest = 1;
    int iCountNearest = 0;
    int iCreature;
    object oCreature;
    object oLastSpawned = GetNearestObjectByTag(
                          sCurrentTag, oNearest, iNearest);
    while (iCountNearest != 1) {
      if (!GetIsObjectValid(GetLocalObject(oLastSpawned, "SpawnedBy")) &&
           GetIsObjectValid(oLastSpawned)) {
        if (iDebug) { // Debug
          SendMessageToAllDMs(GetTag(oLastSpawned) +
                              " SpawnedBy is blank...");
        }
        SetLocalObject(oLastSpawned, "SpawnedBy", oNearest);
        iCreature = 1;
        oCreature = GetLocalObject(oNearest, sTagType +
                                   IntToString(iCreature));
        while (GetIsObjectValid(oCreature)) {
          iCreature++;
          oCreature = GetLocalObject(oNearest, sTagType +
                                     IntToString(iCreature));
        }
        SetLocalObject(oNearest, sTagType +
                       IntToString(iCreature),
                       oLastSpawned);
        if (GetLocalInt(oNearest, "RandomFacing")) {
          int iRandomDir = Random(270);
          AssignCommand(oLastSpawned, SetFacing(IntToFloat(iRandomDir)));
        }
        if (GetLocalInt(oNearest, "RandomWalk") && sTagType == "Creature") {
          AssignCommand(oLastSpawned, ActionRandomWalk());
        }
        if (GetLocalInt(oNearest, "GenTreasure") && sTagType == "Creature") {
          switch (GetLocalInt(oNearest, "GenTreasure")) {
            case 01:GenerateLowTreasure(oLastSpawned);break;
            case 02:GenerateMediumTreasure(oLastSpawned);break;
            case 03:GenerateHighTreasure(oLastSpawned);break;
            case 04:GenerateBossTreasure(oLastSpawned);break;
            case 05:GenerateBookTreasure(oLastSpawned);break;
          }
        }
        if (iDebug) { // Debug
          SendMessageToAllDMs("Owner of " + GetTag(oLastSpawned) +
                              " is " +
                              GetName(GetLocalObject(
                                             oLastSpawned, "SpawnedBy")));
        }
        iCountNearest = 1;
      }
      iNearest++;
      oLastSpawned = GetNearestObjectByTag(
                       sCurrentTag, oNearest, iNearest);
      if (!GetIsObjectValid(oLastSpawned)) {
        iCountNearest = 1;
      }
    }
}
void sp_SpawnCamp(object oNearest, location lCurrentWP) {
  location lMyLoc;
  location lObjectLoc;
  int iCampEquip = 0;
  int iItem = 1;
  int iCamp = GetLocalInt(oNearest, "Camp");
  string sCamp;
  if (iCamp < 10) {
    sCamp = "camp0" + IntToString(iCamp);
  }
  else {
    sCamp = "camp" + IntToString(iCamp);
  }
  while (iCampEquip != 1) {
    lMyLoc = lCurrentWP;
    string sItemResRef = GetLocalString(OBJECT_SELF, sCamp + "_resref_" +
                                        IntToString(iItem));
    string sItemTag = GetLocalString(OBJECT_SELF, sCamp + "_tag_" +
                                     IntToString(iItem));
    if (sItemResRef != "") {
      lObjectLoc = GetLocation(GetNearestObjectToLocation(
                               OBJECT_TYPE_PLACEABLE, lCurrentWP));
      if (GetDistanceBetweenLocations(lMyLoc, lObjectLoc) < 2.0 ) {
        int iLocValid ;
        for (iLocValid = 0; iLocValid != 1; iLocValid++) {
          lMyLoc = sp_RandomLocation(oNearest, 1);
          lObjectLoc = GetLocation(GetNearestObjectToLocation(
                                   OBJECT_TYPE_PLACEABLE, lMyLoc));
          if (GetDistanceBetweenLocations(lMyLoc, lObjectLoc) > 2.0 ) {
            iLocValid = 1;
          }
        }
      }
      CreateObject(OBJECT_TYPE_PLACEABLE, sItemResRef, lMyLoc, TRUE);
      sp_TagObject(oNearest, sItemTag, "Camp");
      iItem++;
    }
    else {
      iCampEquip = 1;
      SetLocalInt(oNearest, "CampExists", 1);
    }
  }
}
void sp_CheckTrap(object oNearest, object oPC)
{
  if (iDebug)
    SendMessageToAllDMs("Checking Trap..."); // Debug
    effect eDamage;
    effect eEffect;
    string sDamageCause;
    string sAmulet = "blank";
    if (GetIsObjectValid(oPC)) {
      if (iDebug)
        SendMessageToAllDMs("PC is valid..."); // Debug
      switch (GetLocalInt(oNearest, "TrapType")) {
        case 01:
          eDamage = EffectDamage(d4(),
                    DAMAGE_TYPE_MAGICAL, DAMAGE_POWER_PLUS_FIVE);
          eEffect = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_NATURE);
          sDamageCause = "You have stumbled into a poisonous fog.";
          break;
        case 02:
          eDamage = EffectDamage(d4(),
                    DAMAGE_TYPE_MAGICAL, DAMAGE_POWER_PLUS_FIVE);
          eEffect = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_EVIL);
          sDamageCause = "You have stumbled into a poisonous fog.";
          break;
        default:
          eDamage = EffectDamage(d4(),
                    DAMAGE_TYPE_MAGICAL, DAMAGE_POWER_PLUS_FIVE);
          eEffect = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_NATURE);
          sDamageCause = "You have stumbled into a poisonous fog.";
          break;
      }
      if (GetLocalInt(oNearest, "TrapAmulet")) {
        if (iDebug)
          SendMessageToAllDMs("Checking for Amulet..."); // Debug
        switch (GetLocalInt(oNearest, "TrapAmulet")) {
          case 01: sAmulet = "PalorsAmulet"; break;
          case 02: sAmulet = "PalorsOtherAmulet"; break;
        }
      }
      if (GetTag(GetItemInSlot(INVENTORY_SLOT_NECK, oPC)) != sAmulet) {
        if (iDebug)
          SendMessageToAllDMs("PC does not have Amulet..."); // Debug
        SendMessageToPC(oPC, sDamageCause);
        ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eEffect,
                              GetLocation(oPC));
        ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oPC);
      }
    }
    else {
      if (iDebug)
        SendMessageToAllDMs("PC is not valid..."); // Debug
    }
}
void sp_AreaEffect(object oNearest)
{
  if (iDebug)
    SendMessageToAllDMs("Setting Area Effect..."); // Debug
  int iEffectRadius = GetLocalInt(oNearest, "AreaEffectRadius");
  if (!iEffectRadius) {
    iEffectRadius = 5;
  }
  effect eAreaEffect;
  switch (GetLocalInt(oNearest, "AreaEffect")) {
    case 1: eAreaEffect = EffectVisualEffect(VFX_FNF_SMOKE_PUFF);break;
    case 2: eAreaEffect = EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_NATURE);break;
    default: eAreaEffect = EffectVisualEffect(VFX_FNF_SMOKE_PUFF);break;
  }
  location lAreaLoc = sp_RandomLocation(oNearest, iEffectRadius);
  ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eAreaEffect, lAreaLoc);
}