// --------------------------------------------------------
// sparky_inc         Sparky Spawner v2 include file
//
// original version by Sparky1479
// recode and expansion by Malishara
//
// 10/28/2009    Malishara: added SMS encounter type
// 10/29/2009    Malishara: added static encounter type
//                          added music encounter type
// 11/10/2009    Malishara: added disable encounter type
// 11/14/2009    Malishara: added equip and unequip encounter types
// 11/20/2009    Malishara: added ability to store encounters on,
//                          and spawn from, a trigger
// 2017/01/16    The Amethyst Dragon: combined all sparky include functions into
//                          a single script to streamline things for builders
// --------------------------------------------------------


#include "x0_i0_position"

// Function Prototypes (full functions defined after)

// Originally from "spark_inc":

int WhichSlot(string sSlot);
object FindSMSWidget(string sSMS_Crate, string sName);
int ProcessTrapType(string sTrapType);
void ProcessSingleAction(object oCreature, string sAction);
void ProcessActions(object oCreature, string sActionList);
location ProcessLocation(string sLoc, string sOffset, int iSpawn, int iTotalSpawns, object oArea);
int GetSpawnAmount(string sAmt);
int ProcessV2Entry(string sEnc, object oArea, object oDB, string sLocationOverride = "", string sOffsetOverride = "");
int ProcessTime(string sTime);                                  // Room for expansion in this if desired, see full function for notes
int ProcessV2Encounter(string sEnc, object oArea, object oDB);
int ProcessEncounter(string sEnc, object oArea, object oDB);
void SpawnEncounters(object oArea);
void Despawn(object oArea);

// Originally from "mali_string_fns":

// Returns the first word in sString, delimited by sDelimiter
string FirstWord(string sString, string sDelimiter = " ");
// Returns sString, starting with the second word, delimited by sDelimiter
string RestWords(string sString, string sDelimiter = " ");
// Replaces all occurences of sSearch within sString with sReplace
string SearchAndReplace(string sString, string sSearch, string sReplace);
// Return the nth word of sString, delimited by sDelimiter
string FetchWord(string sString, string sDelimiter, int n);

// Originally from "mcs_inc":

void RestoreCreature(object oCreature, string sRecord);
object FindMCSBag(string sMCS_Unit, string sName);

// Originally from "dmts_common_inc":

void DestroyContents(object oObject);
string RestoreVariables(object oObject, string sVarList, int iWriteVars = TRUE);

// Originally from "dmts_common_inc":

void Unpack(object oWidget, location lReference, object oSpawnedBy = OBJECT_INVALID, int iRelativeCoords = FALSE, int iRelativeFacing = FALSE, int iRestrictedUse = FALSE, int iTrackingType = 1);
float GetTheta(vector vAlpha, vector vBeta);


// Function Definitions
string FirstWord(string sString, string sDelimiter = " ")
{ if (FindSubString(sString, sDelimiter) == -1)
  { return sString; }
  else
  { return GetStringLeft(sString, FindSubString(sString, sDelimiter)); }
}

string RestWords(string sString, string sDelimiter = " ")
{ if (FindSubString(sString, sDelimiter) == -1)
  { return ""; }
  else
  { return GetStringRight(sString, GetStringLength(sString) - FindSubString(sString, sDelimiter) - GetStringLength(sDelimiter)); }
}

string SearchAndReplace(string sString, string sSearch, string sReplace)
{ string sNewString = "";
  int iOffset = FindSubString(sString, sSearch);
  while (iOffset != -1)
  {   sNewString += FirstWord(sString, sSearch) + sReplace;
      sString = RestWords(sString, sSearch);
      iOffset = FindSubString(sString, sSearch);
  }
  return sNewString + sString;
}

string FetchWord(string sString, string sDelimiter, int n)
{ int iLoop = 0;
  string sWord = "";
  while (iLoop < n)
  {   sWord = FirstWord(sString, sDelimiter);
      sString = RestWords(sString, sDelimiter);
      iLoop++;
  }
  return sWord;
}

int CountWords(string sString, string sDelimiter)
{ int iCount = 0;
  int iPos = 0;

  while (iPos != -1)
  { iPos = FindSubString(sString, sDelimiter, iPos);
    if (iPos != -1)
    { iCount++;
      iPos = iPos + GetStringLength(sDelimiter);
    }
  }

  return iCount;
}

int FindWord(string sString, string sSubString, string sDelimiter)
{ int iPos = FindSubString(sDelimiter + sString + sDelimiter, sDelimiter + sSubString + sDelimiter);
  if (iPos == -1)
  { return FALSE; }

  iPos = iPos - GetStringLength(sDelimiter);
  sString = GetSubString(sString, 0, iPos);

  return CountWords(sString, sDelimiter);
}

string LastWord(string sString, string sDelimiter)
{ int iDelLen = GetStringLength(sDelimiter);
  int iPos = GetStringLength(sString) - iDelLen;

  while ((iPos > 0) && (GetSubString(sString, iPos, iDelLen) != sDelimiter))
  { iPos--; }

  if (GetSubString(sString, iPos, iDelLen) == sDelimiter)
  { iPos += iDelLen; }

  return GetStringRight(sString, GetStringLength(sString) - iPos);
}

int WhichSlot(string sSlot)
{   if (sSlot == "head")
    { return INVENTORY_SLOT_HEAD; }
    if (sSlot == "lefthand")
    { return INVENTORY_SLOT_LEFTHAND; }
    if (sSlot == "righthand")
    { return INVENTORY_SLOT_RIGHTHAND; }
    if (sSlot == "cloak")
    { return INVENTORY_SLOT_CLOAK; }
    if (sSlot == "arms")
    { return INVENTORY_SLOT_ARMS; }
    if (sSlot == "belt")
    { return INVENTORY_SLOT_BELT; }
    if (sSlot == "boots")
    { return INVENTORY_SLOT_BOOTS; }
    if (sSlot == "rightring")
    { return INVENTORY_SLOT_RIGHTRING; }
    if (sSlot == "leftring")
    { return INVENTORY_SLOT_LEFTRING; }
    if (sSlot == "arrows")
    { return INVENTORY_SLOT_ARROWS; }
    if (sSlot == "bolts")
    { return INVENTORY_SLOT_BOLTS; }
    if (sSlot == "bullets")
    { return INVENTORY_SLOT_BULLETS; }
    if ((sSlot == "neck") || (sSlot == "necklace") || (sSlot == "amulet"))
    { return INVENTORY_SLOT_NECK; }
    if ((sSlot == "chest") || (sSlot == "armor") || (sSlot == "torso"))
    { return INVENTORY_SLOT_CHEST; }

    return -1;
}

object FindSMSWidget(string sSMS_Crate, string sName)
{   object oSMS_Crate = GetObjectByTag(sSMS_Crate);
    if (!GetIsObjectValid(oSMS_Crate)) { return OBJECT_INVALID; }

    object oSMS = GetFirstItemInInventory(oSMS_Crate);

    int iLoop = TRUE;

    while (iLoop)
    {   if (!GetIsObjectValid(oSMS))
        { iLoop = FALSE; }
        else if (GetStringLowerCase(sName) == GetStringLowerCase(SearchAndReplace(SearchAndReplace(GetName(oSMS), ",", ""), " ", "")))
        { iLoop = FALSE; }
        else
        { oSMS = GetNextItemInInventory(oSMS_Crate); }
    }

    return oSMS;
}

int ProcessTrapType(string sTrapType)
{   string sTrapStrength = FirstWord(sTrapType, " ");
    string sTrapDamageType = RestWords(sTrapType, " ");

    int iStrength = 0;

    if (sTrapStrength == "random")
    { switch (Random(5))
      { case 0:
          sTrapStrength = "minor"; break;
        case 1:
          sTrapStrength = "average"; break;
        case 2:
          sTrapStrength = "strong"; break;
        case 3:
          sTrapStrength = "deadly"; break;
        case 4:
          sTrapStrength = "epic"; break;
      }
    }

    if (sTrapStrength == "epic")
    { if ((sTrapDamageType == "electrical") || (sTrapDamageType == "electric") || (sTrapDamageType == "elec"))
      { return 44; }
      if (sTrapDamageType == "fire")
      { return 45; }
      if (sTrapDamageType == "frost")
      { return 46; }
      if (sTrapDamageType == "sonic")
      { return 47; }
      return Random(4) + 44;
    }

    if (sTrapStrength == "minor")
    { iStrength = 0; }
    else if (sTrapStrength == "average")
    { iStrength = 1; }
    else if (sTrapStrength == "strong")
    { iStrength = 2; }
    else if (sTrapStrength == "deadly")
    { iStrength = 3; }

    if (sTrapDamageType == "spike")
    { return iStrength + 0; }
    else if (sTrapDamageType == "holy")
    { return iStrength + 4; }
    else if (sTrapDamageType == "tangle")
    { return iStrength + 8; }
    else if (sTrapDamageType == "acid")
    { return iStrength + 12; }
    else if (sTrapDamageType == "fire")
    { return iStrength + 16; }
    else if ((sTrapDamageType == "electrical") || (sTrapDamageType == "electric") || (sTrapDamageType == "elec"))
    { return iStrength + 20; }
    else if (sTrapDamageType == "gas")
    { return iStrength + 24; }
    else if (sTrapDamageType == "frost")
    { return iStrength + 28; }
    else if (sTrapDamageType == "negative")
    { return iStrength + 32; }
    else if (sTrapDamageType == "sonic")
    { return iStrength + 36; }
    else if (sTrapDamageType == "acid splash")
    { return iStrength + 40; }
    else
    { return (Random(11) * 4) + iStrength; }

    return 0;
}

void ProcessSingleAction(object oCreature, string sAction)
{   if ((sAction == "walk") || (sAction == "1"))
    { AssignCommand(oCreature, ActionRandomWalk()); return; }

    if (sAction == "chair")
    { object oChair = GetNearestObjectByTag("Chair", oCreature, 1);
      if (GetIsObjectValid(oChair))
      { AssignCommand(oCreature, ActionSit(oChair)); }
      return;
    }

    if (sAction == "dead")
    { AssignCommand(oCreature, SetIsDestroyable(FALSE, TRUE, TRUE));
      ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), oCreature);
      return;
    }

    if (sAction == "corpse")
    { AssignCommand(oCreature, SetIsDestroyable(FALSE, FALSE, FALSE));
      ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), oCreature);
      return;
    }

    if (FirstWord(sAction, ":") == "faction")
    { object oFactionNPC = GetObjectByTag("Faction_" + RestWords(sAction, ":"));
      if (GetIsObjectValid(oFactionNPC))
      { ChangeFaction(oCreature, oFactionNPC); }
      return;
    }

    if (FirstWord(sAction, ":") == "unequip")
    { string sSlot = RestWords(sAction, ":");
      object oItem = GetItemInSlot(WhichSlot(sSlot), oCreature);
      if (GetIsObjectValid(oItem))
      { AssignCommand(oCreature, ActionUnequipItem(oItem)); }
      return;
    }

    if (FirstWord(sAction, ":") == "equip")
    { string sSlot = FirstWord(RestWords(sAction, ":"), ":");
      string sTag = RestWords(RestWords(sAction, ":"), ":");
      object oItem = GetItemPossessedBy(oCreature, sTag);
      if (GetIsObjectValid(oItem))
      { AssignCommand(oCreature, ActionEquipItem(oItem, WhichSlot(sSlot))); }
      return;
    }

    int iAnim;
    if (sAction == "spasm")
    { iAnim = ANIMATION_LOOPING_SPASM; }
    else if (sAction == "sit")
    { iAnim = ANIMATION_LOOPING_SIT_CROSS; }
    else if (sAction == "threaten")
    { iAnim = ANIMATION_LOOPING_TALK_FORCEFUL; }
    else if (sAction == "laugh")
    { iAnim = ANIMATION_LOOPING_TALK_LAUGHING; }
    else if (sAction == "talk")
    { iAnim = ANIMATION_LOOPING_TALK_NORMAL; }
    else if (sAction == "beg")
    { iAnim = ANIMATION_LOOPING_TALK_PLEADING; }
    else if (sAction == "worship")
    { iAnim = ANIMATION_LOOPING_WORSHIP; }
    else if (sAction == "pray")
    { iAnim = ANIMATION_LOOPING_MEDITATE; }
    else if (sAction == "conjure1")
    { iAnim = ANIMATION_LOOPING_CONJURE1; }
    else if (sAction == "conjure2")
    { iAnim = ANIMATION_LOOPING_CONJURE2; }
    else
    { return; }

    AssignCommand(oCreature, ActionPlayAnimation(iAnim, 1.0f, 9999.0f));
    return;
}

void ProcessActions(object oCreature, string sActionList)
{   string sAction = FirstWord(sActionList, ";");
    sActionList = RestWords(sActionList, ";");

    while (sAction != "")
    { ProcessSingleAction(oCreature, sAction);
      sAction = FirstWord(sActionList, ";");
      sActionList = RestWords(sActionList, ";");
    }
}


location ProcessLocation(string sLoc, string sOffset, int iSpawn, int iTotalSpawns, object oArea)
{   string sAngle = RestWords(sLoc, "@");
    sLoc = FirstWord(sLoc, "@");

    location lLoc;
    float fAngle;
    if (sAngle == "")
    { fAngle = IntToFloat(Random(360)); }
    else if (sAngle == "last")
    { lLoc = GetLocalLocation(oArea, "lLastSpawn");
      fAngle = GetFacingFromLocation(lLoc);
    }
    else
    { fAngle = StringToFloat(sAngle); }

    string sType = GetStringLowerCase(FirstWord(sLoc, ":"));
    string sParameter = RestWords(sLoc, ":");
    float fOffset = StringToFloat(sOffset);
    vector vVector;
    float fROffset;
    float fFacingOffset = 0.0f;

    if ((sType == "circle") || (sType == "circleflip"))
    { if (iSpawn > 0)
      { lLoc = GetLocalLocation(oArea, "lLastSpawn"); }
      else
      { lLoc = ProcessLocation(sParameter, "", 0, 0, oArea);
        SetLocalLocation(oArea, "lLastSpawn", lLoc);
      }
      vVector = GetPositionFromLocation(lLoc);
      fAngle = (360.0f / IntToFloat(iTotalSpawns)) * iSpawn;
      if (sType == "circleflip")
      { fFacingOffset = 180.0f; }
      lLoc = Location(oArea, GetChangedPosition(vVector, fOffset, fAngle), fFacingOffset + fAngle);
      return lLoc;
    }

    if ((sType == "loc") || (sType == "location"))
    { sParameter = GetStringLowerCase(sParameter);
      float fLocX = StringToFloat(FirstWord(sParameter, "x"));
      sParameter = RestWords(sParameter, "x");
      float fLocY = StringToFloat(FirstWord(sParameter, "y"));
      sParameter = RestWords(sParameter, "y");
      float fLocZ = 0.0f;
      if (sParameter != "")
      { fLocZ = StringToFloat(FirstWord(sParameter, "z")); }
      lLoc = Location(oArea, Vector(fLocX, fLocY, fLocZ), fAngle);
      SetLocalLocation(oArea, "lLastSpawn", lLoc);
      if (sOffset != "")
      { vVector = GetPositionFromLocation(lLoc);
        fROffset = IntToFloat(Random(FloatToInt(fOffset * 100.0f))) / 100.0f;
        lLoc = Location(oArea, GetChangedPosition(vVector, fROffset, fAngle), fAngle);
      }
      return lLoc;

    }

    if ((sType == "wp") || (sType == "waypoint"))
    { lLoc = GetLocation(GetWaypointByTag(sParameter));
      SetLocalLocation(oArea, "lLastSpawn", lLoc);
      if (sOffset != "")
      { vVector = GetPositionFromLocation(lLoc);
        fROffset = IntToFloat(Random(FloatToInt(fOffset * 100.0f))) / 100.0f;
        lLoc = Location(oArea, GetChangedPosition(vVector, fROffset, fAngle), fAngle);
      }
      return lLoc;
    }

    if ((sType == "object") || (sType == "obj"))
    { lLoc = GetLocation(GetNearestObjectByTag(sParameter, GetFirstObjectInArea(oArea)));
      SetLocalLocation(oArea, "lLastSpawn", lLoc);
      if (sOffset != "")
      { vVector = GetPositionFromLocation(lLoc);
        fROffset = IntToFloat(Random(FloatToInt(fOffset * 100.0f))) / 100.0f;
        lLoc = Location(oArea, GetChangedPosition(vVector, fROffset, fAngle), fAngle);
      }
      return lLoc;
    }

    if (sType == "last")
    { lLoc = GetLocalLocation(oArea, "lLastSpawn");
      return lLoc;
    }

    // Return a random location. Do not use the edges (.05 width).

    int iAreaX = GetAreaSize(AREA_WIDTH, oArea);
    int iAreaY = GetAreaSize(AREA_HEIGHT, oArea);

    float fRandX = IntToFloat(Random(iAreaX * 10)) + (IntToFloat(Random(90)) / 100) + 0.05f;
    float fRandY = IntToFloat(Random(iAreaY * 10)) + (IntToFloat(Random(90)) / 100) + 0.05f;

    lLoc = Location(oArea, Vector(fRandX, fRandY, 0.0f), fAngle);
    SetLocalLocation(oArea, "lLastSpawn", lLoc);

    return lLoc;
}

int GetSpawnAmount(string sAmt)
{   if (TestStringAgainstPattern("*n-*n", sAmt))
    { int iLowAmt = StringToInt(FirstWord(sAmt, "-"));
      int iHighAmt = StringToInt(RestWords(sAmt, "-"));
      return (Random((iHighAmt - iLowAmt) +1) + iLowAmt);
    }
    else
    { return StringToInt(sAmt); }
}

int ProcessV2Entry(string sEnc, object oArea, object oDB, string sLocationOverride = "", string sOffsetOverride = "")
{   string sResRef;
    int iAmt;
    string sLocation;
    string sOffset;
    location lLocation;
    int iLoop;
    object oSpawn;
    int iEntry;
    int iContinue;
    string sAction;
    string sNewTag;

    string sType = GetStringLowerCase(FirstWord(sEnc, ","));
    sEnc = RestWords(sEnc, ",");

    if (sType == "music")
    { string sMusic = GetStringLowerCase(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");
      int iMusicTrack = StringToInt(FirstWord(sEnc, ","));

      if (iMusicTrack == 0) { return TRUE; }

      if (sMusic == "day")
      { SetLocalInt(oArea, "iSparkyMusicDay", MusicBackgroundGetDayTrack(oArea));
        MusicBackgroundStop(oArea);
        MusicBackgroundChangeDay(oArea, iMusicTrack);
      }
      else if (sMusic == "night")
      { SetLocalInt(oArea, "iSparkyMusicNight", MusicBackgroundGetNightTrack(oArea));
        MusicBackgroundStop(oArea);
        MusicBackgroundChangeNight(oArea, iMusicTrack);
      }
      else if (sMusic == "battle")
      { SetLocalInt(oArea, "iSparkyMusicBattle", MusicBackgroundGetBattleTrack(oArea));
        MusicBattleChange(oArea, iMusicTrack);
      }

      return (StringToInt(sEnc));
    }

    if ((sType == "creature") || (sType == "npc"))
    { sResRef = FirstWord(FirstWord(sEnc, ","), ":");
      sNewTag = RestWords(FirstWord(sEnc, ","), ":");
      sEnc = RestWords(sEnc, ",");

      iAmt = GetSpawnAmount(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      sAction = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      for (iLoop = 0; iLoop < iAmt; iLoop++)
      { lLocation = ProcessLocation(sLocation, sOffset, iLoop, iAmt, oArea);
        oSpawn = CreateObject(OBJECT_TYPE_CREATURE, sResRef, lLocation, FALSE, sNewTag);
        SetLocalInt(oSpawn, "iSparkySpawn", TRUE);
        ProcessActions(oSpawn, sAction);
      }

      return (StringToInt(sEnc));
    }

    if (sType == "mcs")
    { object oMCS_Spawn;
      location lHoldingCell = GetLocation(GetWaypointByTag("MCS_CELL"));
      string sRecord;

      string sMCS_Unit = "ENC_MCS_" + FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      string sMCS_BagName = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      object oMCS = FindMCSBag(sMCS_Unit, sMCS_BagName);
      if (!GetIsObjectValid(oMCS)) { return TRUE; }

      string sMCSData = GetDescription(oMCS, FALSE, FALSE);

      iAmt = StringToInt(FirstWord(sMCSData, "|"));
      sMCSData = RestWords(sMCSData, "|");

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      sAction = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      for (iLoop = 0; iLoop < iAmt; iLoop++)
      { lLocation = ProcessLocation(sLocation, sOffset, iLoop, iAmt, oArea);
        object oMCSSpawn = CreateObject(OBJECT_TYPE_CREATURE, FirstWord(sMCSData, "|"), lHoldingCell);
        sMCSData = RestWords(sMCSData, "|");
        sRecord = FirstWord(sMCSData, "<<<EOR>>>|");
        sMCSData = RestWords(sMCSData, "<<<EOR>>>|");

        RestoreCreature(oMCSSpawn, sRecord);
        oSpawn = CopyObject(oMCSSpawn, lLocation);
        DestroyObject(oMCSSpawn);

        SetLocalInt(oSpawn, "iSparkySpawn", TRUE);
        ProcessActions(oSpawn, sAction);

        sLocation = "last";
        sOffset = "";
      }

      return (StringToInt(sEnc));
    }

    if (sType == "sms")
    { string sSMS_Crate = "ENC_SMS_" + FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      string sSMSWidgetName = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      object oSMS = FindSMSWidget(sSMS_Crate, sSMSWidgetName);
      if (!GetIsObjectValid(oSMS)) { return TRUE; }

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      if ((sLocation == "absolute") || (sLocation == "abs"))
      { lLocation = ProcessLocation("1.0x1.0y", sOffset, 1, 1, oArea);
        Unpack(oSMS, lLocation, OBJECT_INVALID, FALSE, FALSE, FALSE, 2);
      }
      else
      { lLocation = ProcessLocation(sLocation, sOffset, 1, 1, oArea);
        Unpack(oSMS, lLocation, OBJECT_INVALID, TRUE, TRUE, FALSE, 2);
      }

      return (StringToInt(sEnc));

    }

    if ((sType == "placeable") || (sType == "prop") || (sType == "static"))
    { sResRef = FirstWord(FirstWord(sEnc, ","), ":");
      sNewTag = RestWords(FirstWord(sEnc, ","), ":");
      sEnc = RestWords(sEnc, ",");

      iAmt = GetSpawnAmount(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      for (iLoop = 0; iLoop < iAmt; iLoop++)
      { lLocation = ProcessLocation(sLocation, sOffset, iLoop, iAmt, oArea);
        oSpawn = CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, lLocation, FALSE, sNewTag);
        SetLocalInt(oSpawn, "iSparkySpawn", TRUE);
        if (sType == "static")
        { AssignCommand(oSpawn, SetIsDestroyable(FALSE)); }
      }

      return (StringToInt(sEnc));
    }

    if ((sType == "waypoint") || (sType == "wp"))
    { sNewTag = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      lLocation = ProcessLocation(sLocation, sOffset, 1, 1, oArea);
      oSpawn = CreateObject(OBJECT_TYPE_WAYPOINT, "nw_waypoint001", lLocation, FALSE, sNewTag);
      SetLocalInt(oSpawn, "iSparkySpawn", TRUE);

      return (StringToInt(sEnc));
    }

    if (sType == "item")
    { sResRef = FirstWord(FirstWord(sEnc, ","), ":");
      sNewTag = RestWords(FirstWord(sEnc, ","), ":");
      sEnc = RestWords(sEnc, ",");

      iAmt = GetSpawnAmount(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      for (iLoop = 0; iLoop < iAmt; iLoop++)
      { lLocation = ProcessLocation(sLocation, sOffset, iLoop, iAmt, oArea);
        oSpawn = CreateObject(OBJECT_TYPE_ITEM, sResRef, lLocation, FALSE, sNewTag);
        SetLocalInt(oSpawn, "iSparkySpawn", TRUE);
      }

      return (StringToInt(sEnc));
    }

    if ((sType == "trap") || (sType == "traps"))
    { string sTrapStrength = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      string sTrapDmgType = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      string sTrapType = sTrapStrength + " " + sTrapDmgType;

      iAmt = GetSpawnAmount(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");

      float fTrapSize = StringToFloat(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");
      if (fTrapSize < 1.0f)
      { fTrapSize = 2.0f; }

      string sDetectDC = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      string sDisarmDC = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      int iRecoverable = StringToInt(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      for (iLoop = 0; iLoop < iAmt; iLoop++)
      { lLocation = ProcessLocation(sLocation, sOffset, iLoop, iAmt, oArea);
        oSpawn = CreateTrapAtLocation(ProcessTrapType(sTrapType), lLocation, fTrapSize);
        SetLocalInt(oSpawn, "iSparkySpawn", TRUE);

        if (sDetectDC == "-1")
        { SetTrapDetectable(oSpawn, FALSE); }
        else if (sDetectDC != "")
        { SetTrapDetectDC(oSpawn, StringToInt(sDetectDC)); }

        if (sDisarmDC == "-1")
        { SetTrapDisarmable(oSpawn, FALSE); }
        else if (sDisarmDC != "")
        { SetTrapDisarmDC(oSpawn, StringToInt(sDisarmDC)); }

        if (!iRecoverable)
        { SetTrapRecoverable(oSpawn, FALSE); }
      }

      return (StringToInt(sEnc));
    }

    if (sType == "script")
    { sResRef = FirstWord(FirstWord(sEnc, ","), ":");
      string sRunAs = RestWords(FirstWord(sEnc, ","), ":");
      object oRunAs;
      if (sRunAs != "")
      { oRunAs = GetObjectByTag(sRunAs);
        if (!GetIsObjectValid(oRunAs))
        { oRunAs = oDB; }
      }
      else
      { oRunAs = oDB; }
      ExecuteScript(sResRef, oRunAs);
      return TRUE;
    }

    if (sType == "group")
    { string sGroup = "group_" + GetStringLowerCase(FirstWord(sEnc, ","));
      iEntry = 1;

      for (iEntry = 1; sEnc != ""; iEntry++)
      { sEnc = GetLocalString(oArea, sGroup + ((iEntry < 10) ? "_0" : "_") + IntToString(iEntry));
        iContinue = ProcessV2Entry(SearchAndReplace(sEnc, " ", ""), oArea, oDB, sLocationOverride, sOffsetOverride);
      }

      return iContinue;
    }

    if (sType == "table")
    { string sTable = "table_" + GetStringLowerCase(FirstWord(sEnc, ","));
      int iDieRoll = d100();
      int iChance = 0;
      iEntry = 1;

      for (iEntry = 1; iDieRoll > iChance; iEntry++)
      { sEnc = GetLocalString(oArea, sTable + ((iEntry < 10) ? "_0" : "_") + IntToString(iEntry));

        iChance = StringToInt(FirstWord(sEnc, ","));
        sEnc = RestWords(sEnc, ",");
      }

      iContinue = ProcessV2Entry(SearchAndReplace(sEnc, " ", ""), oArea, oDB, sLocationOverride, sOffsetOverride);
      return iContinue;
    }

    if ((sType == "level") || (sType == "lvl"))
    { int iSparkyPartyLvls = GetLocalInt(oArea, "iSparkyPartyLvls");
      string sTable = "lvltable_" + GetStringLowerCase(FirstWord(sEnc, ","));
      int iEntryLvl = 10000;
      iEntry = 1;

      for (iEntry = 1; iSparkyPartyLvls < iEntryLvl; iEntry++)
      { sEnc = GetLocalString(oArea, sTable + ((iEntry < 10) ? "_0" : "_") + IntToString(iEntry));

        iEntryLvl = StringToInt(FirstWord(sEnc, ","));
        sEnc = RestWords(sEnc, ",");
      }

      iContinue = ProcessV2Entry(SearchAndReplace(sEnc, " ", ""), oArea, oDB, sLocationOverride, sOffsetOverride);
      return iContinue;
    }

    if (sType == "copy")
    { string sAreaTag = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      string sObjTag = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      sNewTag = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");

      object oSourceArea = GetObjectByTag(sAreaTag);
      object oObject = GetFirstObjectInArea(oSourceArea);
      if (GetTag(oObject) != sObjTag)
      { oObject = GetNearestObjectByTag(sObjTag, oObject); }

      iAmt = GetSpawnAmount(FirstWord(sEnc, ","));
      sEnc = RestWords(sEnc, ",");

      sLocation = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sLocationOverride !="") { sLocation = sLocationOverride; }

      sOffset = FirstWord(sEnc, ",");
      sEnc = RestWords(sEnc, ",");
      if (sOffsetOverride !="") { sOffset = sOffsetOverride; }

      if (GetObjectType(oObject) == OBJECT_TYPE_CREATURE)
      { sAction = FirstWord(sEnc, ",");
        sEnc = RestWords(sEnc, ",");
      }

      for (iLoop = 0; iLoop < iAmt; iLoop++)
      { lLocation = ProcessLocation(sLocation, sOffset, iLoop, iAmt, oArea);
        oSpawn = CopyObject(oObject, lLocation, OBJECT_INVALID, sNewTag);
        SetLocalInt(oSpawn, "iSparkySpawn", TRUE);
        if (GetObjectType(oObject) == OBJECT_TYPE_CREATURE)
        { ProcessActions(oSpawn, sAction); }
      }

      return (StringToInt(sEnc));
    }

    if (sType == "disable")
    { string sEntry = FirstWord(sEnc, ",");
      iContinue = StringToInt(RestWords(sEnc, ","));

      sEnc = GetLocalString(oDB, "encounter_" + sEntry);

      if (!TestStringAgainstPattern("OFF:**", sEnc))
      { SetLocalString(oDB, "encounter_" + sEntry, "OFF:" + sEnc);
        SetLocalString(oDB, "sSparky_Disabled_List", GetLocalString(oArea, "sSparky_Disabled_List") + sEntry + " ");
      }

      return iContinue;
    }

    return TRUE;
}

int ProcessTime(string sTime)
{   if (sTime == "always")
    { return TRUE; }

    if (sTime == "day")
    { return GetIsDay(); }

    if (sTime == "night")
    { return GetIsNight(); }

    // You can define more predefined active times for spawns if you wish.
    // As an example, perhaps some encounters only spawn in the two hours after
    // sunset (7pm-9pm, hours 19-20). If so, you could add something like this:
    /*
       if (sTime == "twilight")
          {
          int nHour = GetTimeHour();
          if (nHour > 18 && nHour < 21)
             { return TRUE; }
          else
             { return FALSE; }
          }
     */

    if (TestStringAgainstPattern("*n", sTime))
    { return (StringToInt(sTime) == GetTimeHour()); }

    if (TestStringAgainstPattern("*n-*n", sTime))
    { int iHour = GetTimeHour();
      int iHour1 = StringToInt(FirstWord(sTime, "-"));
      int iHour2 = StringToInt(FirstWord(RestWords(sTime, "-"), ","));

      if (iHour2 < iHour1)
      { return (iHour1 <= iHour) || (iHour <= iHour2); }
      else
      { return (iHour1 <= iHour) && (iHour <= iHour2); }
    }

    return FALSE;
}

int ProcessV2Encounter(string sEnc, object oArea, object oDB)
{   int iRightTime = ProcessTime(GetStringLowerCase(FirstWord(sEnc, ",")));
    sEnc = RestWords(sEnc, ",");

    int iChance = StringToInt(FirstWord(sEnc, ","));
    sEnc = RestWords(sEnc, ",");

    if ((d100() <= iChance) && iRightTime)
    { int iContinue = ProcessV2Entry(sEnc, oArea, oDB);
      return iContinue;
    }

    return TRUE;
}

int ProcessEncounter(string sEnc, object oArea, object oDB)
{   int iContinue;
    int iLoop;
    object oSpawn;

    if (TestStringAgainstPattern("OFF:**", sEnc))
    { return TRUE; }

    if (GetStringLowerCase(FirstWord(sEnc, ",")) == "v2")
    { iContinue = ProcessV2Encounter(RestWords(sEnc, ","), oArea, oDB);
      return iContinue;
    }

    if (GetStringLowerCase(FirstWord(sEnc, ":")) == "runscript")
    { ExecuteScript(RestWords(sEnc, ":"), oDB);
      return TRUE;
    }

    int iChance = StringToInt(FirstWord(sEnc, ","));
    sEnc = RestWords(sEnc, ",");

    string sResRef = FirstWord(sEnc, ",");
    sEnc = RestWords(sEnc, ",");

    int iAmt = StringToInt(FirstWord(sEnc, ","));
    sEnc = RestWords(sEnc, ",");

    object oWP = GetWaypointByTag(FirstWord(sEnc, ","));
    location lWP = GetLocation(oWP);

    iContinue = StringToInt(RestWords(sEnc, ","));

    if (iChance <= d100())
    { for (iLoop = 0; iLoop < iAmt; iLoop++)
      {   oSpawn = CreateObject(OBJECT_TYPE_CREATURE, sResRef, lWP);
          SetLocalInt(oSpawn, "iSparkySpawn", TRUE);
          AssignCommand(oSpawn, ActionRandomWalk());
      }
      return iContinue;
    }

    return TRUE;
}

void SpawnEncounters(object oArea)
{   int iDone = FALSE;
    int iEnc = 1;
    string sEnc;
    object oDB = oArea;

    if (GetObjectType(oArea) == OBJECT_TYPE_TRIGGER)
    { oArea = GetArea(oDB);
      int iSparkyTriggers = GetLocalInt(oArea, "iSparkyTriggers");
      SetLocalObject(oArea, "oSparkyTrigger" + IntToString(iSparkyTriggers), oDB);
      SetLocalInt(oArea, "iSparkyTriggers", iSparkyTriggers + 1);
    }

    while (!iDone)
    {   sEnc = GetLocalString(oDB, "encounter" + ((iEnc < 10) ? "_0" : "_") + IntToString(iEnc));
        sEnc = SearchAndReplace(sEnc, " ", "");
        if (sEnc == "")
        { iDone = TRUE;}
        else
        { iDone = ProcessEncounter(sEnc, oArea, oDB);
          iDone = abs(iDone - 1);
          iEnc++;
        }
    }

    SetLocalInt(oDB, "iSparkySpawned", TRUE);
}

void Despawn(object oArea)
{   object oObject;
    int iObjectType;
    int iSpawned;
    string sEntry;
    string sEnc;

    iSpawned = GetLocalInt(oArea, "iSparkySpawned");
    if (!iSpawned)
    { return; }

    string sSparky_Disabled_List = GetLocalString(oArea, "sSparky_Disabled_List");
    while (sSparky_Disabled_List != "")
    { sEntry = FirstWord(sSparky_Disabled_List, " ");
      sSparky_Disabled_List = RestWords(sSparky_Disabled_List, " ");

      sEnc = GetLocalString(oArea, "encounter_" + sEntry);
      if (TestStringAgainstPattern("OFF:**", sEnc))
      { SetLocalString(oArea, "encounter_" + sEntry, RestWords(sEnc, "OFF:")); }
    }

    DeleteLocalString(oArea, "sSparky_Disabled_List");

    oObject = GetFirstObjectInArea(oArea);
    while (GetIsObjectValid(oObject))
    {   iObjectType = GetObjectType(oObject);
        iSpawned = GetLocalInt(oObject, "iSparkySpawn");
        if (iSpawned)
        { if ((iObjectType == OBJECT_TYPE_PLACEABLE) && GetHasInventory(oObject))
          { DestroyContents(oObject); }
          AssignCommand(oObject, SetIsDestroyable(TRUE));
          DestroyObject(oObject);
        }

        oObject = GetNextObjectInArea(oArea);
    }

    int iMusic = GetLocalInt(oArea, "iSparkyMusicDay");
    if ( iMusic!= 0)
    { MusicBackgroundStop(oArea);
      MusicBackgroundChangeDay(oArea, iMusic);
      DeleteLocalInt(oArea, "iSparkyMusicDay");
    }

    iMusic = GetLocalInt(oArea, "iSparkyMusicNight");
    if ( iMusic!= 0)
    { MusicBackgroundStop(oArea);
      MusicBackgroundChangeNight(oArea, iMusic);
      DeleteLocalInt(oArea, "iSparkyMusicNight");
    }

    iMusic = GetLocalInt(oArea, "iSparkyMusicBattle");
    if ( iMusic!= 0)
    { MusicBattleChange(oArea, iMusic);
      DeleteLocalInt(oArea, "iSparkyMusicBattle");
    }

    MusicBackgroundStop(oArea);
    MusicBackgroundPlay(oArea);

    SetLocalInt(oArea, "iSparkySpawned", FALSE);

    int iSparkyTriggers = GetLocalInt(oArea, "iSparkyTriggers");
    int iLoop;
    object oDB;
    string sDB;

    for (iLoop = 0; iLoop < iSparkyTriggers; iLoop++)
    { sDB = "oSparkyTrigger" + IntToString(iLoop);
      oDB = GetLocalObject(oArea, sDB);
      SetLocalInt(oDB, "iSparkySpawned", FALSE);
      DeleteLocalObject(oArea, sDB);
    }

    DeleteLocalInt(oArea, "iSparkyTriggers");
}

void RestoreCreature(object oCreature, string sRecord)
{   string sField = FirstWord(sRecord, "|");
    sRecord = RestWords(sRecord, "|");
    if (GetName(oCreature) != sField)
    { SetName(oCreature, sField); }

    sField = FirstWord(sRecord, "|");
    sRecord = RestWords(sRecord, "|");
    if (GetDescription(oCreature) != sField)
    { SetDescription(oCreature, sField); }

    int iField = StringToInt(FirstWord(sRecord, "|"));
    sRecord = RestWords(sRecord, "|");
    if (iField != PORTRAIT_INVALID)
    { SetPortraitId(oCreature, iField);
      sRecord = RestWords(sRecord, "|");
    }
    else
    { sField = FirstWord(sRecord, "|");
      SetPortraitResRef(oCreature, sField);
    }

    iField = StringToInt(FirstWord(sRecord, "|"));
    sRecord = RestWords(sRecord, "|");
    if (GetAppearanceType(oCreature) != iField)
    { SetCreatureAppearanceType(oCreature, iField); }

    iField = StringToInt(FirstWord(sRecord, "|"));
    sRecord = RestWords(sRecord, "|");
    if (GetPhenoType(oCreature) != iField)
    { SetPhenoType(iField, oCreature); }

    iField = StringToInt(FirstWord(sRecord, "|"));
    sRecord = RestWords(sRecord, "|");
    if (GetCreatureTailType(oCreature) != iField)
    { SetCreatureTailType(iField, oCreature); }

    iField = StringToInt(FirstWord(sRecord, "|"));
    sRecord = RestWords(sRecord, "|");
    if (GetCreatureWingType(oCreature) != iField)
    { SetCreatureWingType(iField, oCreature); }

    iField = StringToInt(FirstWord(sRecord, "|"));
    sRecord = RestWords(sRecord, "|");
    if (GetCreatureBodyPart(CREATURE_PART_HEAD, oCreature) != iField)
    { SetCreatureBodyPart(CREATURE_PART_HEAD, iField, oCreature); }

    int iLoop = 0;
    while (iLoop < 18)
    {  iField = StringToInt(FirstWord(sRecord, "|"));
       sRecord = RestWords(sRecord, "|");
       if (GetCreatureBodyPart(iLoop, oCreature) != iField)
       { SetCreatureBodyPart(iLoop, iField, oCreature); }
       iLoop++;
    }

    iLoop = 0;
    while (iLoop < 4)
    {  iField = StringToInt(FirstWord(sRecord, "|"));
       sRecord = RestWords(sRecord, "|");
       if (GetColor(oCreature, iLoop) != iField)
       { SetColor(oCreature, iLoop, iField); }
       iLoop++;
    }

    if (GetStringLength(sRecord) == 0)  { return; }

    string sFaction = FirstWord(sRecord, "|");
    sRecord = RestWords(sRecord, "|");

    if (sFaction == "")  { return; }

    object oFactionNPC = GetObjectByTag("Faction_" + sFaction);
    if (GetIsObjectValid(oFactionNPC))
    { ChangeFaction(oCreature, oFactionNPC); }

    return;
}

object FindMCSBag(string sMCS_Unit, string sName)
{   object oMCS_Unit = GetObjectByTag(sMCS_Unit);
    if (!GetIsObjectValid(oMCS_Unit)) { return OBJECT_INVALID; }

    object oMCS = GetFirstItemInInventory(oMCS_Unit);

    string sBagName;
    int iLoop = TRUE;

    while (iLoop)
    {   if (!GetIsObjectValid(oMCS))
        { iLoop = FALSE; }
        else if (GetStringLowerCase(sName) == GetStringLowerCase(SearchAndReplace(SearchAndReplace(GetName(oMCS), ",", ""), " ", "")))
        { iLoop = FALSE; }
        else
        { oMCS = GetNextItemInInventory(oMCS_Unit); }
    }

    return oMCS;
}

void DestroyContents(object oObject)
{  object oItem = GetFirstItemInInventory(oObject);
   while (GetIsObjectValid(oItem))
   { DestroyObject(oItem);
     oItem = GetNextItemInInventory(oObject);
   }
}

float GetTheta(vector vAlpha, vector vBeta)
{
   float fLengthX = vBeta.x - vAlpha.x;
   float fLengthY = vBeta.y - vAlpha.y;

   if( (fLengthX > 0.0f) && (fLengthY >= 0.0f))
   { return atan(fLengthY/fLengthX); }
   if( (fLengthX > 0.0f) && (fLengthY < 0.0f ))
   { return atan(fLengthY/fLengthX) + 360.0f; }
   if( fLengthX < 0.0f)
   { return atan(fLengthY/fLengthX) + 180.0f; }
   if( (fLengthX == 0.0f) && (fLengthY > 0.0f))
   { return 90.0f; }
   if( (fLengthX == 0.0f) && (fLengthY < 0.0f))
   { return 270.0f; }
   return 0.0f; // error somewhere...
}

void Unpack(object oWidget, location lReference, object oSpawnedBy = OBJECT_INVALID, int iRelativeCoords = FALSE, int iRelativeFacing = FALSE, int iRestrictedUse = FALSE, int iTrackingType = 1)
{  int iCurrentSets = GetLocalInt(oWidget, "iCurrentSets");

   object oProp = OBJECT_INVALID;
   string sDesc = GetDescription(oWidget, FALSE, FALSE);
   int iVersion2 = FALSE;
   int iVersion3 = FALSE;
   int iVersion4 = FALSE;
   int iVersion5 = FALSE;

   if( (FirstWord(sDesc, "|") == "V2"))
   { iVersion2 = TRUE;
     sDesc = RestWords(sDesc, "|"); }
   else if( (FirstWord(sDesc, "|") == "V3"))
   { iVersion2 = TRUE;
     iVersion3 = TRUE;
     sDesc = RestWords(sDesc, "|"); }
   else if( (FirstWord(sDesc, "|") == "V4"))
   { iVersion2 = TRUE;
     iVersion3 = TRUE;
     iVersion4 = TRUE;
     sDesc = RestWords(sDesc, "|"); }
   else if( (FirstWord(sDesc, "|") == "V5"))
   { iVersion2 = TRUE;
     iVersion3 = TRUE;
     iVersion4 = TRUE;
     iVersion5 = TRUE;
     sDesc = RestWords(sDesc, "|"); }

   int iCounter = StringToInt(FirstWord(sDesc, "|"));
   sDesc = RestWords(sDesc, "|");

   object oMyArea = GetAreaFromLocation(lReference);
   vector vMyNewPos = GetPositionFromLocation(lReference);

   float fMyOldX = StringToFloat(FirstWord(sDesc, "|"));
   sDesc = RestWords(sDesc, "|");
   float fMyOldY = StringToFloat(FirstWord(sDesc, "|"));
   sDesc = RestWords(sDesc, "|");
   float fMyOldZ = StringToFloat(FirstWord(sDesc, "|"));
   sDesc = RestWords(sDesc, "|");
   vector vZero = Vector(fMyOldX, fMyOldY, fMyOldZ);
   float fMyOldFacing = StringToFloat(FirstWord(sDesc, "|"));
   sDesc = RestWords(sDesc, "|");
   location lMyOldLoc = Location(oMyArea, vZero, fMyOldFacing);

   float fPropX = 0.0f;
   float fPropY = 0.0f;
   float fPropZ = 0.0f;
   float fPropFacing = 0.0f;
   location lPropLocation = lReference;
   string sResRef = "";
   string sNewName = "";
   string sNewTag = "";
   string sPropList = "";
   string sPropID_VarName;
   int iFlags = 0;
   int iHasPlot = 0;
   int iHasUseable = 0;
   int iRetagged = 0;

   int iLoop = 0;

   while( iLoop < iCounter)
   {
      sResRef = FirstWord(sDesc, "|");
      sDesc = RestWords(sDesc, "|");
      if( iVersion2 == TRUE)
      { sNewName = FirstWord(sDesc, "|");
        sDesc = RestWords(sDesc, "|"); }
      if( iVersion4 == TRUE)
      { sNewTag = FirstWord(sDesc, "|");
        sDesc = RestWords(sDesc, "|"); }
      if( iVersion3 == TRUE)
      { iFlags = StringToInt(FirstWord(sDesc, "|"));
        sDesc = RestWords(sDesc, "|");
        if (iFlags != -1)
        { iHasPlot = iFlags & 1;
          iHasUseable = (iFlags & 2) / 2;
          iRetagged = (iFlags & 4) / 4;
        }
      }

      fPropX = StringToFloat(FirstWord(sDesc, "|"));
      sDesc = RestWords(sDesc, "|");
      fPropY = StringToFloat(FirstWord(sDesc, "|"));
      sDesc = RestWords(sDesc, "|");
      fPropZ = StringToFloat(FirstWord(sDesc, "|"));
      sDesc = RestWords(sDesc, "|");
      fPropFacing = StringToFloat(FirstWord(sDesc, "|"));
      sDesc = RestWords(sDesc, "|");

      lPropLocation = Location(oMyArea, Vector(fPropX, fPropY, fPropZ), fPropFacing);

      DeleteLocalLocation(oWidget, "lZero");

      if (iRelativeCoords)
      { lPropLocation = Location(oMyArea, Vector(fPropX, fPropY, fMyOldZ), fPropFacing);
        vector vNewProp = vZero;
        if (iRelativeFacing)
        { float fDistance = GetDistanceBetweenLocations(lMyOldLoc, lPropLocation);
          float fTheta = GetTheta(vZero, Vector(fPropX, fPropY, fMyOldZ));

          vNewProp = GetChangedPosition(vMyNewPos, fDistance, fTheta - fMyOldFacing + GetFacingFromLocation(lReference));
          float fRelativeZ = fPropZ - fMyOldZ + vMyNewPos.z;
          fPropFacing = fPropFacing - fMyOldFacing + GetFacingFromLocation(lReference);

          lPropLocation = Location(oMyArea, Vector(vNewProp.x, vNewProp.y, fRelativeZ), fPropFacing);

          SetLocalLocation(oWidget, "lZero", lReference);
          SetLocalInt(oWidget, "iDM_SM_SpawnType", 3);
        }
        else
        { vector vRelative = Vector(fPropX - fMyOldX, fPropY - fMyOldY, fPropZ - fMyOldZ);
          vNewProp = Vector(vMyNewPos.x + vRelative.x, vMyNewPos.y + vRelative.y, vMyNewPos.z + vRelative.z);
          lPropLocation = Location(oMyArea, vNewProp, fPropFacing);

          SetLocalLocation(oWidget, "lZero", lReference);
          SetLocalInt(oWidget, "iDM_SM_SpawnType", 2);
        }
      }
      else
      { SetLocalLocation(oWidget, "lZero", lMyOldLoc);
        SetLocalInt(oWidget, "iDM_SM_SpawnType", 1);
      }

      oProp = CreateObject(OBJECT_TYPE_PLACEABLE, sResRef, lPropLocation, FALSE, sNewTag);
      sPropList += "." + ObjectToString(oProp) + ".";

      if( (iVersion2 == TRUE) && ( sNewName != ""))
      { SetName(oProp, sNewName); }
      if ((iVersion3 == TRUE) && (iFlags != -1))
      { SetPlotFlag(oProp, iHasPlot);
        SetUseableFlag(oProp, iHasUseable);
        SetLocalInt(oProp, "iRetagged", iRetagged);
      }

      if (iVersion5)
      { SetLocalString(oProp, "sDMTS_VarList", FirstWord(sDesc, "|"));
        sDesc = RestoreVariables(oProp, sDesc); }

      switch (iTrackingType)
      { case 1:  SetLocalString(oProp, "sNameTag", GetName(oSpawnedBy) + " (" + GetPCPlayerName(oSpawnedBy) + ")");
                 sPropID_VarName = "oSet" + IntToString(iCurrentSets) + "Prop" + IntToString(iLoop);
                 SetLocalObject(oWidget, sPropID_VarName, oProp);
                 SetLocalObject(oProp, "oStageManager", oWidget);
                 SetLocalString(oProp, "sPropID_VarName", sPropID_VarName);
                 break;
        case 2:  SetLocalInt(oProp, "iSparkySpawn", TRUE);
                 break;
        default: break;
      }

      iLoop++;
   }

   if (iTrackingType == 1)
   { SetLocalInt(oWidget, "iPropsSpawned_Set" + IntToString(iCurrentSets), iLoop);
     iCurrentSets++;
     SetLocalInt(oWidget, "iCurrentSets", iCurrentSets);
     SetLocalString(oWidget, "sPropList", sPropList);
   }

   if (iRestrictedUse)
   { int iCurrentUses = GetLocalInt(oWidget, "iCurrentUses");
     int iCurrentSets = GetLocalInt(oWidget, "iCurrentSets");
     int iMaxUses = GetLocalInt(oWidget, "iMaxUses");
     int iSimUses = GetLocalInt(oWidget, "iSimUses");

     if(iMaxUses != -1)
     { iCurrentUses--;
       SetLocalInt(oWidget, "iCurrentUses", iCurrentUses);
     }
   }

}

string RestoreVariables(object oObject, string sVarList, int iWriteVars = TRUE)
{string sData = RestWords(sVarList, "|");
    sVarList = FirstWord(sVarList, "|");
    string sVarType;
    string sVarName;
    string sVariable;

    while (sVarList != "")
    { sVarType = FirstWord(sVarList);
      sVarName = FirstWord(RestWords(sVarList), ",");
      sVarList = RestWords(sVarList, ",");

      sVariable = FirstWord(sData, "|");
      sData = RestWords(sData, "|");

      if (iWriteVars)
      { if (sVarType == "string")
        { SetLocalString(oObject, sVarName, sVariable); }
        else if (sVarType == "int")
        { SetLocalInt(oObject, sVarName, StringToInt(sVariable)); }
        else if (sVarType == "float")
        { SetLocalFloat(oObject, sVarName, StringToFloat(sVariable)); }
      }
    }

    return sData;
}