//::///////////////////////////////////////////////
//:: Name re_rndenc
//:: FileName re_rndenc.nss
//:: Copyright (c) 2002 Raymond Miller
//:://////////////////////////////////////////////
/*
This script creates functions called RandomEncounter(),
CleanHouse(), and SetRndEncProperties() for use in the NWN
scripting language.  This script is meant to be used as an #include
and is part of the BESIE Random Encounter package by Ray Miller
*/
//:://////////////////////////////////////////////
//:: Created By: Ray Miller
//:: Created On: 7/6/2002
//:://////////////////////////////////////////////

// Encounter Type Constants
int ENCOUNTER_TYPE_AREA = 3;
int ENCOUNTER_TYPE_PARTY = 1;
int ENCOUNTER_TYPE_IND = 2;
int ENCOUNTER_TYPE_TOTALPARTYLEVELS = 4;

struct RndEncProperties
    {
    int bInitialized;
    int iDifficulty;
    int bConsiderCR;
    string sCreatureTable;
    int iLifeTime;
    int iMph;
    int iEncounterType;
    int bConflict;
    int iChanceOnRest;
    int bLOSCheck;
    };

// FUNCTION DECLARATIONS

// Sets properties for random encounters that are likely to seldom change
// - oObject: The object that holds these properties.
// - iDifficulty: 1 to 10
// - bConsiderCR: If TRUE takes CR of creature into consideration when
//   choosing an encounter.
// - sCreatureTable: "re_***" - where *** is a string of letter and/or numbers to indicate to the function what type
//   of creatures to spawn.  They are as follows:
//      a - animals
//      c - construct
//      d - dragon
//      e - elemental
//      g - giant
//      h - humanoid
//      i - insect
//      m - miscellaneous
//      p - planar
//      u - undead
//      b - bandit
//      x1 through x### - These are for custom encounter tables.
//      t1 through t### - These are for treasure tables.
// - iLifeTime: Time in seconds before unengaged encounters decay.
// - Mph: Should equal the Minutes Per Hour setting of the Module.
// - iEncounterType:
//      ENCOUNTER_TYPE_PARTY - Takes into consideration the average level of the entire party of the PC who is to
//      receive the encounter when choosing an encounter of appropriate difficulty level.
//      ENCOUNTER_TYPE_TOTALPARTYLEVELS (default) - Takes into consideration the TOTAL of all the levels of the PC's party who
//      currently reside in the same area as the PC to receive the encounter.
//      ENCOUNTER_TYPE_AREA - Takes into consideration the levels off all PCs and henchmen within a 20m radius of the PC
//      who is to receive the encounter.
//      ENCOUNTER_TYPE_IND - Takes into consideration only the levels of the individual PC who is to receive the encounter.
// - bConflict: If set to TRUE then random encounters can occur during combat.
// - iChanceOnRest: The chance of a random encounter occuring when a PC rests (only matters on Area Object and the "re_onrest"
//   script must be placed in PlayerOnRest handler of the module object).
// - bLOSCheck: Dependant upon a broken scripting function.  (future use!)
// Note:  This function is best called by the OnModuleLoad or OnAreaLoad handler.
void SetRndEncProperties(object oObject = OBJECT_SELF, int iDifficulty = 4, int bConsiderCR = TRUE, string sCreatureTable = "re_ceghimpubt0", int iLifeTime = 180, int iMph = 2, int iEncounterType = 4, int bConflict = FALSE, int iChanceOnRest = 20, int bLOSCheck = FALSE);

// Returns the structure "RndEncProperties" containing all the Random Encounter Properties set on oObject.
// The elements of the structure are as follows:
// - bInitialized: TRUE if properties have been set on this object.
// - iDifficulty: 1 to 10
// - bConsiderCR: If TRUE, takes CR of creature into consideration when
//   choosing an encounter.
// - sCreatureTable: "re_***" - where *** is a string of letter and/or numbers to indicate to the function what type
//   of creatures to spawn.  They are as follows:
//      a - animals
//      c - construct
//      d - dragon
//      e - elemental
//      g - giant
//      h - humanoid
//      i - insect
//      m - miscellaneous
//      p - planar
//      u - undead
//      b - bandit
//      x1 through x### - These are for custom encounter tables.
//      t1 through t### - These are for treasure tables.
// - iLifeTime: Time in seconds before unengaged encounters decay.
// - iMph: Should equal the Minutes Per Hour setting of the Module.
// - iEncounterType:
//      ENCOUNTER_TYPE_PARTY - Takes into consideration the average level of the entire party of the PC who is to
//      receive the encounter when choosing an encounter of appropriate difficulty level.
//      ENCOUNTER_TYPE_TOTALPARTYLEVELS (default) - Takes into consideration the TOTAL of all the levels of the PC's party who
//      currently reside in the same area as the PC to receive the encounter.
//      ENCOUNTER_TYPE_AREA - Takes into consideration the levels off all PCs and henchmen within a 20m radius of the PC
//      who is to receive the encounter.
//      ENCOUNTER_TYPE_IND - Takes into consideration only the levels of the individual PC who is to receive the encounter.
// - bConflict: If TRUE then random encounters can occur during combat.
// - iChanceOnRest: The chance of a random encounter occuring when a PC rests (only matters on Area Object and the "re_onrest"
//   script must be placed in PlayerOnRest handler of the module object).
// - bLOSCheck: Dependant upon a broken scripting function.  (future use!)
struct RndEncProperties GetRndEncProperties(object oObject);

// Generates the likelihood of a random encounter.
// - fChanceOfEncounter: Odds of encounter spawning when funciton is called.  Accurate to two
//   decimal places.  .01 to 100.00 percent chance.
// - oEncounterObject: The object about which the encounter will spawn, whose levels (if a player)
//   will be considered when determining an appropriate creature.
// - sTemplate: When used as the sCreatureTable parameter in the SetRndEncProperties()
//   function this parameter has higher priority.  It can also be set to the tag of a
//   specific creature, or to "random" to use the default table set by SetRndEncProperties()
// - iMinNumberOfCreatures: If > 0, a random number of creatures between this and iMaxNumberOfCreatures
//   will spawn.  If set to 0, then exactly the number of creatures set by iMaxNumberOfCreatures will
//   spawn.
// - iMaxNumberOfCreatures: If this and iMinNumberOfCreatures is set to 0 then the number of Creatures
//   spawned will be determined by the CR of the creature spawned compared to the levels of the player(s).
// - iMinEncounterDistance: If set to 0, encounter distance will always be at the number set by iMaxEncounterDistance.
// - iMaxEncounterDistance: Farthest distance the encounter can be from oEncounterObject.
// - iOrientation: 0 to 360.  Counterclockwise representing the angle from facing where the encounter will spawn.
//   a value of 0 will spawn the encounter directly in front of oEncounterObject.  360 will generate a random angle.
// - iTolerance: The number of degrees by which the angle can randomly be off from iOrientation.
// - iCheckDistance: The distance a PC has to move before a Random Encounter check can be made against him.  If the PC has
//   not covered this much distance, then a call to the RandomEncounter() function for this PC will yield
//   OBJECT_INVALID.
// - iLevelOverride: Use this to force the function to base the encounter on a character level other than that
//   determined by oEncounterObject.
// - iDifficulty: Overrides the difficulty setting determined by the SetRndEncProperties() function.
object RandomEncounter(float fChanceOfEncounter = 100.0, object oEncounterObject = OBJECT_SELF, string sTemplate = "random", int iMinNumberOfCreatures = 0, int iMaxNumberOfCreatures = 0, int iMinEncounterDistance = 1, int iMaxEncounterDistance = 15, int iOrientation = 360, int iTolerance = 0, int iCheckDistance = 0, int iLevelOverride = 0, int iDifficulty = 0);

// Used to "clean up" an area that has become littered by random encounters.
// - bDestroyPlotItems - Tells the function whether or not to destroy items with their plot flags set.  If set to TRUE,
//   plot items will be destroyed just like any other item.
// - oArea - The area to clean up.
// - iSpawnOverride - Overrides the default (set by the SetRndEncProperties() function) time to destroy random encounter
//   creatures who are not engaged by PCs.
// - iItemOverride - Overrides the default time of 30 minutes after which to destroy items dropped by PCs
//   Note: Only works if the "re_moditemdrop" script included with the BESIE Random Encounter package
//   is placed in the module OnItemUnacquire handler.
// - iBodyBagOverride - Overrides the default time of 5 minutes after which to destroy loot that was dropped by creatures
//   who were killed.
// NOTE: If there is bDestroyPlotItems is FALSE and there is a plot item or items inside a container or body bag, the container
// and all non-plot items will decay but the plot item(s) will be left.
// NOTE: A value of zero assigned to the override parameters will cause the function to use the default value for that parameter.
void CleanHouse(int bDestroyPlotItems = FALSE, object oArea = OBJECT_SELF, int iSpawnOverride = 0, int iItemOverride = 0, int iBodyBagOverride = 0);

// Returns the game's calander time in seconds since time zero.
// - iMph: Minutes Per Hour.  This should match the module's setting.
int GetTimeInSeconds(int iMph = 2);

// Causes oCreature to walk to a randomly determined location.
// - lCenter: The center of a circle in which random destinations can be generated.
// - iDistance: The distance from lCenter in which to randomly generate destinations.
// - oCreature: The creature to perform the random walk.
// Note: Unlike the default RandomWalk function, this function does not persist until a ClearAllActions is called.  Instead this
// function generates a single random desitination and the move to that destination is added to the creatures action que only once
// per call.
location RandomWalk2(location lCenter, int iDistance = 20, object oCreature = OBJECT_SELF);

void SetRndEncProperties(object oObject = OBJECT_SELF, int iDifficulty = 4, int bConsiderCR = TRUE, string sCreatureTable = "re_ceghimpubt0", int iLifeTime = 180, int iMph = 2, int iEncounterType = 4, int bConflict = TRUE, int iChanceOnRest = 20, int bLOSCheck = FALSE)
{
    SetLocalInt(oObject, "re_bInitialized", TRUE);
    SetLocalInt(oObject, "re_iDifficulty", iDifficulty);
    SetLocalInt(oObject, "re_bConsiderCR", bConsiderCR);
    SetLocalString(oObject, "re_sCreatureTable", sCreatureTable);
    SetLocalInt(oObject, "re_iLifeTime", iLifeTime);
    SetLocalInt(oObject, "re_iMph", iMph);
    SetLocalInt(oObject, "re_iEncounterType", iEncounterType);
    SetLocalInt(oObject, "re_bConflict", bConflict);
    SetLocalInt(oObject, "re_iChanceOnRest", iChanceOnRest);
    SetLocalInt(oObject, "re_bLOSCheck", bLOSCheck);
}

struct RndEncProperties GetRndEncProperties(object oObject = OBJECT_SELF)
{
    if(oObject == GetModule() && !GetLocalInt(GetModule(), "re_bInitialized")) SetRndEncProperties(GetModule());
    struct RndEncProperties strProps;
    strProps.bInitialized = GetLocalInt(oObject, "re_bInitialized");
    strProps.iDifficulty = GetLocalInt(oObject, "re_iDifficulty");
    strProps.bConsiderCR = GetLocalInt(oObject, "re_bConsiderCR");
    strProps.sCreatureTable = GetLocalString(oObject, "re_sCreatureTable");
    strProps.iLifeTime = GetLocalInt(oObject, "re_iLifeTime");
    strProps.iMph = GetLocalInt(oObject, "re_iMph");
    strProps.iEncounterType = GetLocalInt(oObject, "re_iEncounterType");
    strProps.bConflict = GetLocalInt(oObject, "re_bConflict");
    strProps.iChanceOnRest = GetLocalInt(oObject, "re_iChanceOnRest");
    strProps.bLOSCheck = GetLocalInt(oObject, "re_bLOSCheck");
    return strProps;
}

#include "re_table"
object RandomEncounter(float fChanceOfEncounter = 100.0, object oEncounterObject = OBJECT_SELF, string sTemplate = "random", int iMinNumberOfCreatures = 0, int iMaxNumberOfCreatures = 0, int iMinEncounterDistance = 1, int iMaxEncounterDistance = 15, int iOrientation = 360, int iTolerance = 0, int iCheckDistance = 0, int iLevelOverride = 0, int iDifficulty = 0)
{
// IF PROPERTIES ARE NOT SET ON MODULE OBJECT THEN SET THEM WITH DEFAULTS
    if(!GetLocalInt(GetModule(), "re_bInitialized"))
        {
        SetRndEncProperties(GetModule());
        }

// DETERMINE IF ENCOUNTER HAPPENS
    //Has the player moved farther than the CheckDistance?
    float fTravelDistance;
    if(GetIsPC(oEncounterObject))
        {
        if(!GetLocalInt(oEncounterObject, "re_bOldLocationSet"))
            {
            SetLocalInt(oEncounterObject, "re_bOldLocationSet", TRUE);
            SetLocalLocation(oEncounterObject, "re_lOldLocation", GetLocation(oEncounterObject));
            if(iCheckDistance) return OBJECT_INVALID;
            }
        if(GetDistanceBetweenLocations(GetLocation(oEncounterObject), GetLocalLocation(oEncounterObject, "re_lOldLocation")) < 0.0)
            {
            SetLocalLocation(oEncounterObject, "re_lOldLocation", GetLocation(oEncounterObject));
            if(iCheckDistance) return OBJECT_INVALID;
            }
        fTravelDistance = GetDistanceBetweenLocations(GetLocation(oEncounterObject), GetLocalLocation(oEncounterObject, "re_lOldLocation"));
        SetLocalFloat(oEncounterObject, "re_fTravelDistance", GetLocalFloat(oEncounterObject, "re_fTravelDistance") + fTravelDistance);
        SetLocalLocation(oEncounterObject, "re_lOldLocation", GetLocation(oEncounterObject));
        if(GetLocalFloat(oEncounterObject, "re_fTravelDistance") >= IntToFloat(iCheckDistance)) DeleteLocalFloat(oEncounterObject, "re_fTravelDistance");
        else return OBJECT_INVALID;
        }
    // The following two lines allow for a chance of encounter with a precision of up to
    // two decimal places.  ie. 100.00.  An encounter can have as little as a 0.01 chance
    // of occuring.
    int iHappens = Random(10000)+1;
    int iChanceOfEncounter = FloatToInt(fChanceOfEncounter * 100);
    if(iChanceOfEncounter < iHappens)
        {
        return OBJECT_INVALID;
        }
    //Are encounters disabled for this player?
    if(GetLocalInt(GetModule(), "re_" + GetPCPlayerName(oEncounterObject)))
        {
        return OBJECT_INVALID;
        }
    //Are random encounters disabled altogether?
    if(GetLocalInt(GetModule(), "re_disable"))
        {
        return OBJECT_INVALID;
        }
    //Is the player in combat with bConflict equal to false?
    object oHolder;
    int iCounter7 = 1; // Used in checking for nearby enemies.
    if(GetLocalInt(oEncounterObject, "re_bInitialized")) oHolder = oEncounterObject;
    else if(GetLocalInt(GetArea(oEncounterObject), "re_bInitialized")) oHolder = GetArea(oEncounterObject);
    else oHolder = GetModule();
    int bConflict = GetLocalInt(oHolder, "re_bConflict");
    if(!bConflict && GetIsPC(oEncounterObject))
        {
        if(GetIsInCombat(oEncounterObject)) return OBJECT_INVALID;
        object oNearest = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oEncounterObject, iCounter7);
        while(GetIsObjectValid(oNearest) && GetDistanceToObject(oNearest) < 35.0)
            {
            if(GetIsEnemy(oNearest) && (GetIsInCombat(oNearest) || GetObjectSeen(oNearest))) return OBJECT_INVALID;
            iCounter7++;
            oNearest = GetNearestCreature(CREATURE_TYPE_IS_ALIVE, TRUE, oEncounterObject, iCounter7);
            }
        }
    //Are any nearby party members in a conversation?
    object oAmIAPC;
    oAmIAPC = GetFirstObjectInShape(SHAPE_SPHERE, 35.0, GetLocation(oEncounterObject), FALSE, OBJECT_TYPE_CREATURE);
    if(GetIsObjectValid(oAmIAPC))
        {
        while(GetIsObjectValid(oAmIAPC))
            {
            if(GetIsPC(oAmIAPC))
                {
                if(GetFactionEqual(oEncounterObject, oAmIAPC))
                    {
                    if(IsInConversation(oAmIAPC))
                        {
                        return OBJECT_INVALID;
                        }
                    }
                }
            oAmIAPC = GetNextObjectInShape(SHAPE_SPHERE, 25.0, GetLocation(oEncounterObject), FALSE, OBJECT_TYPE_CREATURE);
            }
        }

// DECLARE AND INITIALIZE VARIABLES
    object oMod = GetModule();
    int iMph;
    if(!iDifficulty) iDifficulty = GetLocalInt(oHolder, "re_iDifficulty");
    int bConsiderCR = GetLocalInt(oHolder, "re_bConsiderCR");
    if(GetStringLowerCase(sTemplate) == "random") sTemplate = GetLocalString(GetModule(), "re_sCreatureTable");
    int iLifeTime = GetLocalInt(oHolder, "re_iLifeTime");
    if(!GetLocalInt(oHolder, "re_iMph")) iMph = 2;
    else iMph = GetLocalInt(oHolder, "re_iMph");
    int bLOSCheck = GetLocalInt(oHolder, "re_bLOSCheck");
    int iEncounterType = GetLocalInt(oHolder, "re_iEncounterType");
    int iCounter1 = 1; // Used to count the creatures when spawning them.
    int iCounter2 = 1; // Used in loop to set difficulty level.
    int iCounter3 = 1; // Used in loop to check line of sight float fEncounterDistance (future use!).
    int iCounter4;// Used in determining the PC to spawn the encounter if the encounter object passed is an area or the module.
    int iCounter5; // Used in determining treasure table.
    int iCounter6; // Used in giving treasure.
    int iNumberOfCreatures;
    int iEncounterDistance;
    int iFacingSameWay;
    int iLevels;
    int iTableNumber;
    int bNumberByLevel = FALSE;
    int bNoEncounter = FALSE;
    int bComplete1 = FALSE;
    int bComplete2 = FALSE;
    int bTreasure;
    float fMinCR;
    float fMaxCR;
    float fEncounterDistance;
    float fNewEncounterDistance;
    float fCreatureFacing;
    float fEncounterAngle;
    float fEncounterVector;
    float fAngleOffset;
    float fLevels;
    float fDifficulty = 0.167;
    string sBuild;
    string sTreasure = sTemplate;
    vector vEncounterVector;
    vector vVectorOffset;
    vector vCreatureVector;
    object oObject;
    object oCreature;
    object oArea;
    if(oEncounterObject == GetModule())
        {
        oAmIAPC = GetFirstPC();
        while(GetIsObjectValid(oAmIAPC))
            {
            if(!GetLocalInt(GetModule(), "re_" + GetPCPlayerName(oAmIAPC)))
                {
                SetLocalObject(oMod, "re_oEncounterObject" + IntToString(iCounter4), oAmIAPC);
                iCounter4++;
                }
            oAmIAPC = GetNextPC();
            }
        oEncounterObject = GetLocalObject(oMod, "re_oEncounterObject" + IntToString(Random(iCounter4)));
        }
    else if(GetObjectType(oEncounterObject) == 0 && oEncounterObject != GetModule())
        {
        oArea = oEncounterObject;
        oAmIAPC = GetFirstObjectInArea(oArea);
        while(GetIsObjectValid(oAmIAPC))
            {
            if(GetIsPC(oAmIAPC) && !GetLocalInt(GetModule(), "re_" + GetPCPlayerName(oAmIAPC)))
                {
                SetLocalObject(oArea, "re_oEncounterObject" + IntToString(iCounter4), oAmIAPC);
                iCounter4++;
                }
            oAmIAPC = GetNextObjectInArea(oArea);
            }
        oEncounterObject = GetLocalObject(oArea, "re_oEncounterObject" + IntToString(Random(iCounter4)));
        }
    else
        {
        oArea = GetArea(oEncounterObject);
        }
    if(!GetIsPC(oEncounterObject))
        iEncounterType = ENCOUNTER_TYPE_AREA;
    location lCreatureLocation;
    vector vEncounterObjectVector = GetPosition(oEncounterObject);
    int iMin = 60;
    int iHr = iMin * iMph;
    int iDay = iHr * 24;
    int iMth = iDay * 28;
    int iYr = iMth * 12;
    if(iDifficulty > 10)
        {
        iDifficulty = 10;
        }
    if(iDifficulty == 0)
        {
        iDifficulty = GetGameDifficulty() * 2;
        }
    while(iCounter2 <= iDifficulty)
        {
        fDifficulty = fDifficulty * 1.5;
        iCounter2++;
        }

// ERROR CORRECTION
    if(iMaxNumberOfCreatures < iMinNumberOfCreatures)
        {
        iMaxNumberOfCreatures = iMinNumberOfCreatures;
        }
    if(iMaxEncounterDistance < iMinEncounterDistance)
        {
        iMaxEncounterDistance = iMinEncounterDistance;
        }
    if(!GetIsPC(oEncounterObject))
        {
        iEncounterType = ENCOUNTER_TYPE_AREA;
        }

// CHECK TO SEE IF PC IS RESTING VIA THE BESIE "re_onrest" SCRIPT AND IF SO
// REMOVE RESTING EFFECTS.
    if(GetIsPC(oEncounterObject) && GetLocalInt(oEncounterObject, "re_resting"))
        {
        DeleteLocalInt(oEncounterObject, "re_resting");
        effect eEffect = GetFirstEffect(oEncounterObject);
        while(GetIsEffectValid(eEffect))
            {
            if(GetEffectType(eEffect) == EFFECT_TYPE_BLINDNESS && GetEffectCreator(eEffect) == GetModule()) RemoveEffect(oEncounterObject, eEffect);
            if(GetEffectType(eEffect) == VFX_IMP_SLEEP && GetEffectCreator(eEffect) == GetModule()) RemoveEffect(oEncounterObject, eEffect);
            eEffect = GetNextEffect(oEncounterObject);
            }
        }

// DETERMINE THE ANGLE OFFSET OF THE SPAWN
    if(iOrientation == 360)
        {
        fEncounterAngle = IntToFloat(Random(360));
        }
    else
        {
        fEncounterAngle = GetFacingFromLocation(GetLocation(oEncounterObject)) + IntToFloat(iOrientation);
        fEncounterAngle = (fEncounterAngle + (IntToFloat(iTolerance) * 0.5)) - (IntToFloat(Random(iTolerance)));
        }

// DETERMINE THE DISTANCE FROM THE SPAWNING OBJECT
    if(iMinEncounterDistance == 0)
        {
        iMinEncounterDistance = iMaxEncounterDistance;
        fEncounterDistance = IntToFloat(iMaxEncounterDistance);
        }
    else
        {
        fEncounterDistance = IntToFloat(iMinEncounterDistance + Random((iMaxEncounterDistance - iMinEncounterDistance) + 1));
        }
    iEncounterDistance = FloatToInt(fEncounterDistance);

// DETERMINE THE FACING OF THE SPAWN
    if(GetLocalInt(oEncounterObject, "re_Facing"))
        {
        fCreatureFacing = fEncounterAngle + 180.0;
        iFacingSameWay = TRUE;
        DeleteLocalInt(oEncounterObject, "re_Facing");
        }
    else
        {
        fCreatureFacing = IntToFloat(Random(360));
        iFacingSameWay = Random(2); // Note: If there is more than one creature there is a 50% chance they will all be facing the same direction
        }

// DETERMINE TOTAL CHARACTER LEVELS TO CONSIDER WHEN CHOOSING A CREATURE
// AND/OR DETERMINING THE NUMBER OF CREATURES TO SPAWN.
    // If the variable iEncounterType is AREA, this routine
    // determines the total character levels
    // based upon the character levels of all PCs
    // in a 20 meter radius around the object that spawned
    // the encounter.
    // Later on the total character levels will be compared to
    // the challenge rating of the creature spawned, and a number
    // of creatures will be determined from that comparison.
    if(iEncounterType == ENCOUNTER_TYPE_AREA)
        {
        oAmIAPC = GetFirstObjectInShape(SHAPE_SPHERE, 20.0, GetLocation(oEncounterObject), FALSE, OBJECT_TYPE_CREATURE);
        while(GetIsObjectValid(oAmIAPC))
            {
            if(GetIsPC(oAmIAPC))
                {
                iLevels = iLevels + GetLevelByPosition(1, oAmIAPC) + GetLevelByPosition(2, oAmIAPC) + GetLevelByPosition(3, oAmIAPC);
                if(GetIsObjectValid(GetHenchman(oAmIAPC)))
                    {
                    iLevels = iLevels + GetLevelByPosition(1, GetHenchman(oAmIAPC)) + GetLevelByPosition(2, GetHenchman(oAmIAPC)) + GetLevelByPosition(3, GetHenchman(oAmIAPC));
                    }
                }
            oAmIAPC = GetNextObjectInShape(SHAPE_SPHERE, 20.0, GetLocation(oEncounterObject), FALSE, OBJECT_TYPE_CREATURE);
            }
        }
    else if(iEncounterType == ENCOUNTER_TYPE_PARTY)
        {
        iLevels = GetFactionAverageLevel(oEncounterObject);
        }
    else if(iEncounterType == ENCOUNTER_TYPE_TOTALPARTYLEVELS)
        {
        oObject = GetFirstFactionMember(oEncounterObject);
        while(GetIsObjectValid(oObject))
            {
            if(GetArea(oObject) == GetArea(oEncounterObject))
                {
                iLevels = iLevels + GetLevelByPosition(1, oObject) + GetLevelByPosition(2, oObject) + GetLevelByPosition(3, oObject);
                }
            oObject = GetNextFactionMember(oEncounterObject);
            }
        }
    else
        {
        // If the variable iEncounterType is set to IND, this
        // routine determines the total character levels based upon the
        // character level of the object that spawned the encounter.
        // if the object that spawned the encounter is NOT a PC then
        // the number of creatures spawned will be one.  This shouldn't
        // happen since the the encounter type sets itself to AREA if
        // the encounter object is a placeable.
        if(GetIsPC(oEncounterObject))
            {
            iLevels = GetLevelByPosition(1, oEncounterObject) + GetLevelByPosition(2, oEncounterObject) + GetLevelByPosition(3, oEncounterObject);
            }
        }
    // Modify the float representing the total levels by the difficulty level.
    if(iLevelOverride)
        {
        iLevels = iLevelOverride;
        }
    fLevels = IntToFloat(iLevels) * fDifficulty;

// CHOOSE A CREATURE TO SPAWN
    if(GetStringLowerCase(sTemplate) == "random" || GetStringLowerCase(GetStringLeft(sTemplate, 3)) == "re_")
        {
        if(GetStringLowerCase(GetStringLeft(sTemplate, 3)) == "re_")
            {
            sTemplate = GetStringRight(sTemplate, GetStringLength(sTemplate) - 3);
            }
        if(fLevels < 0.25)
            {
            fMaxCR = 0.25;
            }
        else
            {
            fMaxCR = fLevels;
            }
        fMinCR = IntToFloat(FloatToInt(fMaxCR * 0.3));
        //If there is a definative number of creatures to spawn passed to
        //the RandomEncounter function when it is called, then do not
        //allow as much play in the low end, and a little more in the
        // high end challange ratings.
        if(iMinNumberOfCreatures == 0 && iMaxNumberOfCreatures > 1)
        {
        fMinCR = IntToFloat(FloatToInt(fMaxCR * 0.4));
        fMaxCR = fMaxCR * 1.2;
        fMinCR = IntToFloat(FloatToInt(fMinCR));
        }
        if(iMinNumberOfCreatures == 0 && iMaxNumberOfCreatures == 1)
        {
        fMinCR = IntToFloat(FloatToInt(fMaxCR * 0.6));
        fMaxCR = fMaxCR * 1.2;
        fMinCR = IntToFloat(FloatToInt(fMinCR));// Round off the CR.
        }
        if(GetLocalInt(oHolder, "re_bConsiderCR") == FALSE)
            {
            fMaxCR = 9999.0;
            fMinCR = 0.0;
            }
        sTemplate = GetRndEncCreature(fMinCR, fMaxCR, sTemplate);
        if(sTemplate == "") return OBJECT_INVALID;
        }

// DETERMINE IF CREATURE IS TO HAVE TREASURE AND WHAT TABLES TO USE
        if(GetLocalString(oMod, "re_s2DATreasure") != "")
            {
            sTreasure = GetLocalString(oMod, "re_s2DATreasure");
            DeleteLocalString(oMod, "re_s2DATreasure");
            }
        for(iCounter5 = 0; iCounter5 <= GetStringLength(sTreasure); iCounter5++)
        {
        if(bTreasure
        && (GetSubString(sTreasure, iCounter5, 1) == "0" || StringToInt(GetSubString(sTreasure, iCounter5, 1)) > 0))
            {
            sBuild = sBuild + GetSubString(sTreasure, iCounter5, 1);
            }
        else if(bTreasure)
            {
            iTableNumber++;
            SetLocalString(OBJECT_SELF, "re_sTreasureTable" + IntToString(iTableNumber), sBuild);
            bTreasure = FALSE;
            sBuild = "";
            }
        if(GetStringLowerCase(GetSubString(sTreasure, iCounter5, 1)) == "t")
            {
            bTreasure = TRUE;
            }
        }

// DETERMINE LOCATION AND SPAWN ONE CREATURE
    // NOTE: Line Of Sight checks have a bug.  Bioware says they are looking
    // into the bug.  I have spent an ungodly amount of hours trying to come
    // up with an acceptable work-around to the Line Of Sight functionality
    // of Get**ObjectInShape().  Unless somebody else can come up with a working
    // LOS check, I have no choice but to disregard LOS checks until they are
    // fixed.
    //
    // if(LOSCheck = TRUE)
    //     {
    //     <LOS code goes here>
    //     }
    //
    // note: one creature is spawned in now so its challange rating can be
    // used to determine if more are needed. (if that option is set)
        vEncounterVector = AngleToVector(fEncounterAngle);
        vVectorOffset = vEncounterVector * fEncounterDistance;
        vCreatureVector = vEncounterObjectVector + vVectorOffset;
        lCreatureLocation = Location(oArea, vCreatureVector, fCreatureFacing);
        oCreature = CreateObject(OBJECT_TYPE_CREATURE, sTemplate, lCreatureLocation, FALSE);

// VERIFY THE RESREF OF THE SPAWNED CREATURE AGAINST THE TEMPLATE AND RETURN AN ERROR IF THEY DO NOT MATCH
        if(GetStringLowerCase(GetResRef(oCreature)) != GetStringLowerCase(sTemplate))
            {
            string sError = "BESIE Error: " + sTemplate + " does not match the blueprint of a valid creature object!";
            DestroyObject(oCreature);
            if(GetIsPC(oEncounterObject)) SendMessageToPC(oEncounterObject, sError);
            else
                {
                object oPC = GetFirstPC();
                while(GetIsObjectValid(oPC))
                    {
                    if(GetArea(oPC) == GetArea(oEncounterObject)) SendMessageToPC(oPC, sError);
                    oPC = GetNextPC();
                    }
                }
            SendMessageToAllDMs(sError);
            WriteTimestampedLogEntry(sError);
            return OBJECT_INVALID;
            }

// DETERMINE THE NUMBER OF ADDITIONAL CREATURES TO SPAWN.
    // If the min and max number of creatures in the function call are zero
    // then get the min and max number from the local variables in the module
    // object.
    if(iMinNumberOfCreatures == 0 && iMaxNumberOfCreatures == 0)
        {
        iMinNumberOfCreatures = GetLocalInt(oMod, "re_iMinNumberOfCreatures");
        iMaxNumberOfCreatures = GetLocalInt(oMod, "re_iMaxNumberOfCreatures");
        }
    // Now that we are done with these local integers, we need to clean reset
    // them to their defaults so we don't accidentally use old numbers later.
    SetLocalInt(oMod, "re_iMinNumberOfCreatures", 0);
    SetLocalInt(oMod, "re_iMaxNumberOfCreatures", 0);
    if(iMinNumberOfCreatures == 0 && iMaxNumberOfCreatures != 0)
        {
        iNumberOfCreatures = iMaxNumberOfCreatures;
        }
    if(iMinNumberOfCreatures != 0 && iMaxNumberOfCreatures != 0)
        {
        iNumberOfCreatures = iMinNumberOfCreatures + Random((iMaxNumberOfCreatures - iMinNumberOfCreatures) + 1);
        }
    if(iMinNumberOfCreatures == 0 && iMaxNumberOfCreatures == 0)
        {
        // This is the routine that sets the number of creatures to spawn
        // based on their challenge rating and the total character levels.
        // It chooses a random number between one half (truncated) and 120
        // percent (1 for every 4) of the number of creatures ideal for the
        // difficulty level set.
        iMaxNumberOfCreatures = FloatToInt(fLevels / GetChallengeRating(oCreature));
        iMinNumberOfCreatures = FloatToInt(IntToFloat(iMaxNumberOfCreatures) * 0.5);
        iMaxNumberOfCreatures = FloatToInt(IntToFloat(iMaxNumberOfCreatures) * 1.25);

        //These lines were added with the v1.7 release because I noticed a situation where characters of
        //up to level 4 would still spawn orcs, goblins and other < CR1 creatures but they would
        //spawn a rediculous amount of them because of the low CR/LV ratio.  This is just to eliminate
        //that.
        if(iMinNumberOfCreatures > 8) iMinNumberOfCreatures = 8;
        if(iMaxNumberOfCreatures > 9) iMaxNumberOfCreatures = 9;

        iNumberOfCreatures = iMinNumberOfCreatures + Random((iMaxNumberOfCreatures - iMinNumberOfCreatures) + 1);
        if((iNumberOfCreatures < 1) && (iLevels > 0))
            {
            iNumberOfCreatures = 1;
            }
        }

// SPAWN THOSE SUCKERS!
    while(iCounter1 <= iNumberOfCreatures)
        {
        // Stick some labels on the creature for record keeping and reference (future use!)
        SetLocalInt(oCreature, "re_bRandomEncounter", TRUE);
        SetLocalObject(oCreature, "re_oRandomEncounterSpawner", oEncounterObject);
        SetLocalInt(oCreature, "re_iRandomEncounterCounter", 1);
        SetLocalInt(oCreature, "re_iRandomEncounterSpawnTime", (GetCalendarYear() * iYr) + (GetCalendarMonth() * iMth) + (GetCalendarDay()* iDay) + (GetTimeHour()* iHr) + (GetTimeMinute() * iMin) + GetTimeSecond());
        SetLocalInt(oCreature, "re_iRandomEncounterLifeTime", iLifeTime);
        /*-------------------------
        This routine was removed in v1.8 because the standard treasure tables were removed and replaced
        with a routine that simply awards an appropriate amount of coin.
        if(!GetLocalInt(GetModule(), "re_standardtable")
        || (GetLocalInt(GetModule(), "re_standardtable") && iCounter1 < 4))
        // The preceding if statement looks for a local variable set by the
        // standard treasure table included with BESIE.  If this variable is
        // set then it halts execution of the treasure script after the first
        // 3 creatures.  This prevents a Too Many Instructions error.
        {
        DeleteLocalInt(GetModule(), "re_standardtable");  //delete standard table int so as not to interfere with custom scripts.
        */
        // Give treasure to the creature if any tables are set.
        for(iCounter6 = 1; iCounter6 <= iTableNumber; iCounter6++)
            {
            ExecuteScript("re_treasure" + GetLocalString(OBJECT_SELF, "re_sTreasureTable" + IntToString(iCounter6)), oCreature);
            }
        //}
        if(iCounter1 < iNumberOfCreatures)
            {
            oCreature = CreateObject(OBJECT_TYPE_CREATURE, sTemplate, lCreatureLocation, FALSE);
            }
        iCounter1++;
        // Determine the facing of the next creature
        if(iFacingSameWay == FALSE)
            {
            fCreatureFacing = IntToFloat(Random(360));
            lCreatureLocation = Location(oArea, vCreatureVector, fCreatureFacing);
            }
        }
    // Stick a lable on the spawning object for record keeping and reference (future use?)
    SetLocalObject(oEncounterObject, "re_oLastRandomEncounterSpawned", oCreature);
    return oCreature;
}



void CleanHouse(int bDestroyPlotItems = FALSE, object oArea = OBJECT_SELF, int iSpawnOverride = 0, int iItemOverride = 0, int iBodyBagOverride = 0)
{
// GET THE TIME SCALE FOR THE MODULE
    int iMph;
    if(!GetLocalInt(GetModule(), "re_iMph"))
        {
        iMph = 2;
        }
    else
        {
        iMph = GetLocalInt(GetModule(), "re_iMph");
        }

// DECLARE AND INTIALIZE VARIABLES
    int iMin = 60;
    int iHr = iMin * iMph;
    int iDay = iHr * 24;
    int iMth = iDay * 28;
    int iYr = iMth * 12;
    int bShouldIKillHim = TRUE;
    int iLifeTime;
    int iItemLifeTime;
    int iBodyBagLifeTime;
    int iPresentTime = (GetCalendarYear() * iYr) + (GetCalendarMonth() * iMth) + (GetCalendarDay() * iDay) + (GetTimeHour() * iHr) + (GetTimeMinute() * iMin) + GetTimeSecond();
    object oObject;


// GET EACH OBJECT IN THE AREA AND TEST FOR VALIDITY
    //The following assignment uses a peculiar property of the GetArea() function in that if the GetArea() function
    //is called on an area then the area is returned.  So the oArea parameter of the CleanHouse function can be set
    //to an area or an object within that area and the function will work. (unless and/or until this is changed).
    object oAmIASpawn = GetFirstObjectInArea(GetArea(oArea));
    while(GetIsObjectValid(oAmIASpawn))
        {
        // IS IT A BODY BAG?
        if(GetTag(oAmIASpawn) == "BodyBag" && !GetLocalInt(oAmIASpawn, "re_bDroppedItem"))
            {
            SetLocalInt(oAmIASpawn, "re_bDroppedItem", TRUE);
            SetLocalInt(oAmIASpawn, "re_iDropTime", iPresentTime);
            object oItem = GetFirstItemInInventory(oAmIASpawn);
            while(GetIsObjectValid(oItem))
                {
                if(GetLocalInt(oItem, "bItemForGold")) DestroyObject(oItem);
                oItem = GetNextItemInInventory(oAmIASpawn);
                }
            }
        // IS IT A DROPPED ITEM?
        if(GetLocalInt(oAmIASpawn, "re_bDroppedItem"))
            {
            // HAS IT BEEN AROUND TOO LONG?
            if(iItemOverride)
                {
                iItemLifeTime = iItemOverride;
                }
            else
                {
                iItemLifeTime = 1800;
                }
            if(iBodyBagOverride)
                {
                iBodyBagLifeTime = iBodyBagOverride;
                }
            else
                {
                iBodyBagLifeTime = 300;
                }
            if((iPresentTime - GetLocalInt(oAmIASpawn, "re_iDropTime") > iItemLifeTime && GetTag(oAmIASpawn) != "BodyBag") || (iPresentTime - GetLocalInt(oAmIASpawn, "re_iDropTime") > iBodyBagLifeTime && GetTag(oAmIASpawn) == "BodyBag"))// && !GetPlotFlag(oAmIASpawn))
                {
                if(GetHasInventory(oAmIASpawn))
                    {
                    oObject = GetFirstItemInInventory(oAmIASpawn);
                    while(GetIsObjectValid(oObject))
                        {
                        if(!GetPlotFlag(oObject) || bDestroyPlotItems)
                            {
                            DestroyObject(oObject, 0.0);
                            }
                        oObject = GetNextItemInInventory(oAmIASpawn);
                        }
                    }
                if(!GetPlotFlag(oAmIASpawn) || bDestroyPlotItems)
                    {
                    DestroyObject(oAmIASpawn, 0.0);
                    }
                }
            }
        // IS HE IS A RANDOM ENCOUNTER?
        if(GetLocalInt(oAmIASpawn, "re_bRandomEncounter"))
            {
            // HAS HE BEEN AROUND TOO LONG?
            if(iSpawnOverride)
                {
                iLifeTime = iSpawnOverride;
                }
            else
                {
                iLifeTime = GetLocalInt(oAmIASpawn, "re_iRandomEncounterLifeTime");
                }
            if(iPresentTime - GetLocalInt(oAmIASpawn, "re_iRandomEncounterSpawnTime") > iLifeTime)
                {
                // IS HE IN COMBAT?
                if(!GetIsInCombat(oAmIASpawn))
                    {
                    // GET EACH PC AND TEST IF THE CREATURE SEES HIM
                    // Note: this is because the creature might be charmed
                    // or influenced not to attack the PCs by other means.
                    object oPC = GetFirstPC();
                    if(GetIsObjectValid(oPC))
                        {
                        while(GetIsObjectValid(oPC))
                            {
                            if(GetObjectSeen(oPC, oAmIASpawn))
                                {
                                bShouldIKillHim = FALSE;
                                }
                            oPC = GetNextPC();
                            }
                        }
                    // IF THE CREATURE HAS PASSED ALL OF THESE CHECKS, DESTROY HIM.
                    if(bShouldIKillHim)
                        {
                        if(!GetIsPC(oAmIASpawn)) //This is prevent despawning of creatures while possessed by a DM.
                            {
                            DestroyObject(oAmIASpawn, 0.0);
                            }
                        }
                    }
                }
            }
        oAmIASpawn = GetNextObjectInArea(oArea);
        }
}

//GET TIME IN SECONDS FUNCTION
int GetTimeInSeconds(int iMph = 2)
{
    if(!iMph) iMph = GetLocalInt(GetModule(), "re_iMph");
    int iMin = 60;
    int iHr = iMin * iMph;
    int iDay = iHr * 24;
    int iMth = iDay * 28;
    int iYr = iMth * 12;
    int iPresentTime = (GetCalendarYear() * iYr) + (GetCalendarMonth() * iMth) + (GetCalendarDay() * iDay) + (GetTimeHour() * iHr) + (GetTimeMinute() * iMin) + GetTimeSecond();
    return iPresentTime;
}

location RandomWalk2(location lCenter, int iDistance = 20, object oCreature = OBJECT_SELF)
{
    vector vVector;
    vector vVectorOffset;
    vector vFinalVector;
    location lLocation;
    object oArea = GetAreaFromLocation(lCenter);
    object oWaypoint;
    int nLocationValid = 0;
    float fDistanceToDoor;
    object oDoor;
    // determine location of invisible object to be used as target of ActionMoveToLocation command
    // if object is too near a door, location will be changed
    while (nLocationValid != 1)
       {
       // determine random location of invisible object to be placed as target of walk command
       float fAngle = IntToFloat(Random(360));
       float fDistance = IntToFloat(Random(iDistance) + 1);
       vVector = AngleToVector(fAngle);
       vVectorOffset = vVector * fDistance;
       vFinalVector = GetPositionFromLocation(lCenter) + vVectorOffset;
       lLocation = Location(oArea, vFinalVector, fAngle);
       // check distance for nearest door, set LocationValid flag if beyond 1 meter
       oWaypoint = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_invisobj", lLocation);
       oDoor = GetNearestObject(OBJECT_TYPE_DOOR, oWaypoint);
       fDistanceToDoor = GetDistanceBetween (oWaypoint, oDoor);
       if (fDistanceToDoor > 1.0)
          nLocationValid = 1; // terminates loop with current oWaypoint if door further away than 1 meter
       }
    lLocation = GetLocation(oWaypoint);
    AssignCommand(oCreature, ActionDoCommand(ActionMoveToLocation(lLocation)));
    DestroyObject(oWaypoint);
    return lLocation;

}