//:://////////////////////////////////////////////
//:: General utility functions
//:: inc_utility
//:://////////////////////////////////////////////
/** @file
    An include file for various small and generally
    useful functions.
*/
//:://////////////////////////////////////////////
//:://////////////////////////////////////////////


/**********************\
* Constant Definitions *
\**********************/

const int ARMOR_TYPE_CLOTH      = 0;
const int ARMOR_TYPE_LIGHT      = 1;
const int ARMOR_TYPE_MEDIUM     = 2;
const int ARMOR_TYPE_HEAVY      = 3;

const int ACTION_USE_ITEM_TMI_LIMIT = 1500;

/*********************\
* Function Prototypes *
\*********************/

/**
 * Returns the greater of the two values passed to it.
 *
 * @param a An integer
 * @param b Another integer
 * @return  a iff a is greater than b, otherwise b
 */
int max(int a, int b);

/**
 * Returns the lesser of the two values passed to it.
 *
 * @param a An integer
 * @param b Another integer
 * @return  a iff a is lesser than b, otherwise b
 */
int min(int a, int b);

/**
 * Returns the greater of the two values passed to it.
 *
 * @param a A float
 * @param b Another float
 * @return  a iff a is greater than b, otherwise b
 */
float fmax(float a, float b);

/**
 * Returns the lesser of the two values passed to it.
 *
 * @param a A float
 * @param b Another float
 * @return  a iff a is lesser than b, otherwise b
 */
float fmin(float a, float b);

/**
 * Takes a string in the standard hex number format (0x####) and converts it
 * into an integer type value. Only the last 8 characters are parsed in order
 * to avoid overflows.
 * If the string is not parseable (empty or contains characters other than
 * those used in hex notation), the function errors and returns 0.
 *
 * Full credit to Axe Murderer
 *
 * @param sHex  The string to convert
 * @return      Integer value of sHex or 0 on error
 */
int HexToInt(string sHex);

/*  NOTE: the following 2 functions don't actually do what they say and
    use real time not game time. Possibly because by default, 1 in-game minute
    is 2 seconds. As real-time minutes to sec function exists (TurnsToSeconds()),
    this is possibly redundant and should be replaced.

    // Use HoursToSeconds to figure out how long a scaled minute
    // is and then calculate the number of real seconds based
    // on that.
    float scaledMinute = HoursToSeconds(1) / 60.0;
    float totalMinutes = minutes * scaledMinute;

    // Return our scaled duration, but before doing so check to make sure
    // that it is at least as long as a round / level (time scale is in
    // the module properties, it's possible a minute / level could last less
    // time than a round / level !, so make sure they get at least as much
    // time as a round / level.
    float totalRounds = RoundsToSeconds(minutes);
    float result = totalMinutes > totalRounds ? totalMinutes : totalRounds;
    return result;
*/

/**
 * Takes an int representing the number of scaled 1 minute intervals wanted
 * and converts to seconds with 1 turn = 1 minute
 * @param minutes       The number of 1 min intervals (typically caster level)
 * @return              Float of duration in seconds
 */
float MinutesToSeconds(int minutes);

/**
 * Takes an int representing the number of scaled 10 minute intervals wanted
 * and converts to seconds with 1 turn = 1 minute
 * @param tenMinutes    The number of 10 min intervals (typically caster level)
 * @return              Float of duration in seconds
 */
float TenMinutesToSeconds(int tenMinutes);

/**
 * Converts metres to feet. Moved from prc_inc_util.
 * @param fMeters   distance in metres
 * @return          float of distance in feet
 */
float MetersToFeet(float fMeters);

/**
 * Checks whether an alignment matches given restrictions.
 * For example
 * GetIsValidAlignment (ALIGNMENT_CHAOTIC, ALIGNMENT_GOOD, 21, 3, 0 );
 * should return FALSE.
 *
 * Credit to Joe Travel
 *
 * @param iLawChaos          ALIGNMENT_* constant
 * @param iGoodEvil          ALIGNMENT_* constant
 * @param iAlignRestrict     Similar format as the restrictions in classes.2da
 * @param iAlignRstrctType   Similar format as the restrictions in classes.2da
 * @param iInvertRestriction Similar format as the restrictions in classes.2da
 *
 * @return TRUE if the alignment does not break the restrictions,
 *          FALSE otherwise.
 */
int GetIsValidAlignment( int iLawChaos, int iGoodEvil, int iAlignRestrict, int iAlignRstrctType, int iInvertRestriction );

/**
 * Gets a random location within an circular area around a base location.
 *
 * by Mixcoatl
 * download from
 * http://nwvault.ign.com/Files/scripts/data/1065075424375.shtml
 *
 * @param lBase     The center of the circle.
 * @param fDistance The radius of the circle. ie, the maximum distance the
 *                  new location may be from lBase.
 *
 * @return          A location in random direction from lBase between
 *                  0 and fDistance meters away.
 */
location GetRandomCircleLocation(location lBase, float fDistance=1.0);

/**
 * Gets a location relative to the first location
 * Includes rotating additional location based on facing of the first
 *
 * @param lMaster   The starting location
 * @param lAdd      The location to add
 *
 * @return          A location in random direction from lBase between
 *                  0 and fDistance meters away.
 */
location AddLocationToLocation(location lMaster, location lAdd);

/**
 * Genji Include Color gen_inc_color
 * first: 1-4-03
 * simple function to use the name of a item holding escape sequences that, though they will not compile,
 * they can be interpreted at run time and produce rbg scales between 32 and 255 in increments.
 * -- allows 3375 colors to be made.
 * for example SendMessageToPC(pc,GetRGB(15,15,1)+ "Help, I'm on fire!") will produce yellow text.
 * more examples:
 *
 *  GetRGB() := WHITE // no parameters, default is white
 *  GetRGB(15,15,1):= YELLOW
 *  GetRGB(15,5,1) := ORANGE
 *  GetRGB(15,1,1) := RED
 *  GetRGB(7,7,15) := BLUE
 *  GetRGB(1,15,1) := NEON GREEN
 *  GetRGB(1,11,1) := GREEN
 *  GetRGB(9,6,1)  := BROWN
 *  GetRGB(11,9,11):= LIGHT PURPLE
 *  GetRGB(12,10,7):= TAN
 *  GetRGB(8,1,8)  := PURPLE
 *  GetRGB(13,9,13):= PLUM
 *  GetRGB(1,7,7)  := TEAL
 *  GetRGB(1,15,15):= CYAN
 *  GetRGB(1,1,15) := BRIGHT BLUE
 *
 * issues? contact genji@thegenji.com
 * special thanks to ADAL-Miko and Rich Dersheimer in the bio forums.
 */
string GetRGB(int red = 15,int green = 15,int blue = 15);

/**
 * Checks if any PCs (or optionally their NPC party members) are in the
 * given area.
 *
 * @param oArea            The area to check
 * @param bNPCPartyMembers Whether to check the PC's party members, too
 */
int GetIsAPCInArea(object oArea, int bNPCPartyMembers = TRUE);

/**
 * Converts the given integer to string as IntToString and then
 * pads the left side until it's nLength characters long. If sign
 * is specified, the first character is reserved for it, and it is
 * always present.
 * Strings longer than the given length are trunctated to their nLength
 * right characters.
 *
 * credit goes to Pherves, who posted the original in homebrew scripts forum sticky
 *
 * @param nX      The integer to convert
 * @param nLength The length of the resulting string
 * @param nSigned If this is TRUE, a sign character is inserted as the leftmost
 *                character. Doing so leaves one less character for use as a digit.
 *
 * @return        The string that results from conversion as specified above.
 */
string IntToPaddedString(int nX, int nLength = 4, int nSigned = FALSE);

/**
 * Looks through the given string, replacing all instances of sToReplace with
 * sReplacement. If such a replacement creates another instance of sToReplace,
 * it, too is replaced. Be aware that you can cause an infinite loop with
 * properly constructed parameters due to this.
 *
 * @param sString      The string to modify
 * @param sToReplace   The substring to replace
 * @param sReplacement The replacement string
 * @return             sString with all instances of sToReplace replaced
 *                     with sReplacement
 */
string ReplaceChars(string sString, string sToReplace, string sReplacement);

/**
 * A wrapper for DestroyObject(). Attempts to bypass any
 * conditions that might prevent destroying the object.
 *
 * WARNING: This will destroy any object that can at all be
 *          destroyed by DestroyObject(). In other words, you
 *          can clobber critical bits with careless use.
 *          Only the module, PCs and areas are unaffected. Using this
 *          function on any of those will cause an infinite
 *          DelayCommand loop that will eat up resources, though.
 *
 *
 * @param oObject The object to destroy
 */
void MyDestroyObject(object oObject);

/**
 * Checks to see if oPC has an item created by sResRef in his/her/it's inventory
 *
 * @param oPC     The creature whose inventory to search.
 * @param sResRef The resref to look for in oPC's items.
 * @return        TRUE if any items matching sResRef were found, FALSE otherwise.
 */
int GetHasItem(object oPC, string sResRef);

/**
 * Calculates the base AC of the given armor.
 *
 * @param oArmor  An item of type BASE_ITEM_ARMOR
 * @return        The base AC of oArmor, or -1 on error
 */
int GetItemACBase(object oArmor);

/**
 * Gets the type of the given armor based on it's base AC.
 *
 * @param oArmor  An item of type BASE_ITEM_ARMOR
 * @return        ARMOR_TYPE_* constant of the armor, or -1 on error
 */
int GetArmorType(object oArmor);

/**
 * Calculates the number of steps along both moral and ethical axes that
 * the two target's alignments' differ.
 *
 * @param oSource A creature
 * @param oTarget Another creature
 * @return        The number of steps the target's alignment differs
 */
int CompareAlignment(object oSource, object oTarget);

/**
 * Repeatedly assigns an equipping action to equip the given item until
 * it is equipped. Used for getting around the fact that a player can
 * cancel the action. They will give up eventually :D
 *
 * WARNING: Note that forcing an equip into offhand when mainhand is empty
 * will result in an infinite loop. So will attempting to equip an item
 * into a slot it can't be equipped in.
 *
 * @param oPC     The creature to do the equipping.
 * @param oItem   The item to equip.
 * @param nSlot   INVENTORY_SLOT_* constant of the slot to equip into.
 * @param nThCall Internal parameter, leave as default. This determines
 *                how many times ForceEquip has called itself.
 */
void ForceEquip(object oPC, object oItem, int nSlot, int nThCall = 0);

/**
 * Repeatedly attempts to unequip the given item until it is no longer
 * in the slot given. Used for getting around the fact that a player can
 * cancel the action. They will give up eventually :D
 *
 * @param oPC     The creature to do the unequipping.
 * @param oItem   The item to unequip.
 * @param nSlot   INVENTORY_SLOT_* constant of the slot containing oItem.
 * @param nThCall Internal parameter, leave as default. This determines
 *                how many times ForceUnequip has called itself.
 */
void ForceUnequip(object oPC, object oItem, int nSlot, int nThCall = 0);

/**
 * Checks either of the given creature's hand slots are empty.
 *
 * @param oCreature Creature whose hand slots to check
 * @return          TRUE if either hand slot is empty, FALSE otherwise
 */
int GetHasFreeHand(object oCreature);

/**
 * Determines whether the creature is encumbered by it's carried items.
 *
 * @param oCreature Creature whose encumberment to determine
 * @return          TRUE if the creature is encumbered, FALSE otherwise
 */
int GetIsEncumbered(object oCreature);

/**
 * Try to identify all unidentified objects within the given creature's inventory
 * using it's skill ranks in lore.
 *
 * @param oPC The creature whose items to identify
 */
void TryToIDItems(object oPC = OBJECT_SELF);

/**
 * Converts a boolean to a string.
 *
 * @param bool The boolean value to convert. 0 is considered false
 *             and everything else is true.
 * @param bTLK Whether to use english strings or get the values from
 *             the TLK. If TRUE, the return values are retrieved
 *             from TLK indices 8141 and 8142. If FALSE, return values
 *             are either "True" or "False".
 *             Defaults to FALSE.
 * @see DebugBool2String() in inc_debug for debug print purposes
 */
string BooleanToString(int bool, int bTLK = FALSE);

/**
 * Returns a copy of the string, with leading and trailing whitespace omitted.
 *
 * @param s The string to trim.
 */
string TrimString(string s);

/**
 * Compares the given two strings lexicographically.
 * Returns -1 if the first string precedes the second.
 * Returns 0 if the strings are equal
 * Returns 1 if the first string follows the second.
 *
 * Examples:
 *
 * StringCompare("a", "a") = 0
 * StringCompare("a", "b") = -1
 * StringCompare("b", "a") = 1
 * StringCompare("a", "1") = 1
 * StringCompare("A", "a") = -1
 * StringCompare("Aa", "A") = 1
 */
int StringCompare(string s1, string s2);

/**
 * Finds first occurrence of string sFind
 * in string sString and replaces it with
 * sReplace and returns the result.
 * If sFind is not found, sString is returned.
 *
 * Examples:
 *
 * StringCompare("aabb", "a", "y") = "yabb"
 * StringCompare("aabb", "x", "y") = "aabb"
 */
string ReplaceString(string sString, string sFind, string sReplace);

/**
 * Determines the angle between two given locations. Angle returned
 * is relative to the first location.
 *
 * @param lFrom The base location
 * @param lTo   The other location
 * @return      The angle between the two locations, relative to lFrom
 */
float GetRelativeAngleBetweenLocations(location lFrom, location lTo);

/**
 * Returns the same string you would get if you examined the item in-game
 * Uses 2da & tlk lookups and should work for custom itemproperties too
 *
 * @param ipTest Itemproperty you want to get the string of
 *
 * @return       A string of the itemproperty, including spaces and bracket where appropriate
 */
string ItemPropertyToString(itemproperty ipTest);


/**
 * Tests if a creature can burn the amount of XP specified without loosing a level
 *
 * @param oPC   Creature to test, can be an NPC or a PC
 * @param nCost Amount of XP to chck for
 *
 * @return       TRUE/FALSE
 */
int GetHasXPToSpend(object oPC, int nCost);


/**
 * Removes an amount of XP via SetXP()
 *
 * @param oPC   Creature to remove XP from, can be an NPC or a PC
 * @param nCost Amount of XP to remove for
 */
void SpendXP(object oPC, int nCost);


/**
 * Tests if a creature can burn the amount of Gold specified
 *
 * @param oPC   Creature to test, can be an NPC or a PC
 * @param nCost Amount of Gold to chck for
 *
 * @return       TRUE/FALSE
 */
int GetHasGPToSpend(object oPC, int nCost);


/**
 * Removes an amount of Gold
 *
 * @param oPC   Creature to remove Gold from, can be an NPC or a PC
 * @param nCost Amount of Gold to remove for
 */
void SpendGP(object oPC, int nCost);

/*
 *  Convinence function for testing off-hand weapons
 */
int isNotShield(object oItem);

/**
 * Makes self use a specific itemproperty on an object
 *
 * Note: This uses a loop so vulnerable to TMI errors
 * Note: This is not 100% reliable, for example if uses/day finished
 * Note: Uses talent system. Unsure what would happen if the creature
 * can cast the same spell from some other means or if they
 * had multiple items with the same spell on them
 *
 * @param oItem   Item to use
 * @param ipIP    Itemproperty to use
 * @param oTarget Target object
 */
void ActionUseItemPropertyAtObject(object oItem, itemproperty ipIP, object oTarget = OBJECT_SELF);

/**
 * Makes self use a specific itemproperty at a location
 *
 * Note: This uses a loop so vulnerable to TMI errors
 * Note: This is not 100% reliable, for example if uses/day finished
 * Note: Uses talent system. Unsure what would happen if the creature
 * can cast the same spell from some other means or if they
 * had multiple items with the same spell on them
 *
 * @param oItem   Item to use
 * @param ipIP    Itemproperty to use
 * @param lTarget Target location
 */
void ActionUseItemPropertyAtLocation(object oItem, itemproperty ipIP, location lTarget);

// Checks the target for a specific EFFECT_TYPE constant value
int PRCGetHasEffect(int nEffectType, object oTarget = OBJECT_SELF);

//Does a check to determine if the NPC has an attempted
//spell or attack target
int PRCGetIsFighting(object oFighting = OBJECT_SELF);

// Returns TRUE if the player is polymorphed.
int GetIsPolyMorphedOrShifted(object oCreature);

/**
 * Gets a random delay based on the parameters passed in.
 *
 * @author              Bioware (GetRandomDelay() from nw_i0_spells)
 *
 * @param fMinimumTime  lower limit for the random time
 * @param fMaximumTime  upper limit for the random time
 *
 * @return              random float between the limits given
 */
float PRCGetRandomDelay(float fMinimumTime = 0.4, float fMaximumTime = 1.1);

//this is here rather than inc_utility because it uses creature size and screws compiling if its elsewhere
/**
 * Returns the skill rank adjusted according to the given parameters.
 * Using the default values, the result is the same as using GetSkillRank().
 *
 * @param oObject     subject to get skill of
 * @param nSkill      SKILL_* constant
 * @param bSynergy    include any applicable synergy bonus
 * @param bSize       include any applicable size bonus
 * @param bAbilityMod include relevant ability modification (including effects on that ability)
 * @param bEffect     include skill changing effects and itemproperties
 * @param bArmor      include armor mod if applicable (excluding shield)
 * @param bShield     include shield mod if applicable (excluding armor)
 * @param bFeat       include any applicable feats, including racial ones
 *
 * @return            subject's rank in the given skill, modified according to
 *                    the above parameters. If the skill is trained-only and the
 *                    subject does not have any ranks in it, returns 0.
 */
int GetSkill(object oObject, int nSkill, int bSynergy = FALSE, int bSize = FALSE,
             int bAbilityMod = TRUE, int bEffect = TRUE, int bArmor = TRUE,
             int bShield = TRUE, int bFeat = TRUE);
             
/**
 * Repeatedly attempts to put down the given item until it is no longer
 * in the slot given. Used for getting around the fact that a player can
 * cancel the action. They will give up eventually :D
 *
 * @param oPC     The creature to do the putting down.
 * @param oItem   The item to put down.
 * @param nSlot   INVENTORY_SLOT_* constant of the slot containing oItem.
 * @param nThCall Internal parameter, leave as default. This determines
 *                how many times ForcePutDown has called itself.
 */
void ForcePutDown(object oPC, object oItem, int nSlot, int nThCall = 0);             

///////////////////////////////////////
/* Constant declarations             */
///////////////////////////////////////

const int ERROR_CODE_5_ONCE_MORE = -1;
const int ERROR_CODE_5_ONCE_MORE2 = -1;
const int ERROR_CODE_5_ONCE_MORE3 = -1;
const int ERROR_CODE_5_ONCE_MORE4 = -1;
const int ERROR_CODE_5_ONCE_MORE5 = -1;

//////////////////////////////////////////////////
/* Include section                              */
//////////////////////////////////////////////////

// The following files have no dependecies, or self-contained dependencies that do not require looping via this file
// inc_debug is available via inc_2dacache
//#include "inc_debug"

#include "prc_inc_nwscript"
#include "inc_target_list"
#include "inc_logmessage"
#include "inc_threads"
#include "prc_inc_actions"
#include "inc_time"
#include "inc_draw_prc"
#include "inc_eventhook"
#include "inc_metalocation"
#include "inc_array_sort"   // Depends on inc_array and inc_debug
#include "inc_uniqueid"     // Depends on inc_array
#include "inc_set"          // Depends on inc_array, inc_heap


/**********************\
* Function Definitions *
\**********************/

int max(int a, int b) {return (a > b ? a : b);}

int min(int a, int b) {return (a < b ? a : b);}

float fmax(float a, float b) {return (a > b ? a : b);}

float fmin(float a, float b) {return (a < b ? a : b);}

int HexToInt_old(string sHex)
{
    if(sHex == "") return 0;                            // Some quick optimisation for empty strings
    sHex = GetStringRight(GetStringLowerCase(sHex), 8); // Truncate to last 8 characters and convert to lowercase
    if(GetStringLeft(sHex, 2) == "0x")                  // Cut out '0x' if it's present
        sHex = GetStringRight(sHex, GetStringLength(sHex) - 2);
    string sConvert = "0123456789abcdef";               // The string to index using the characters in sHex
    int nReturn, nHalfByte;
    while(sHex != "")
    {
        nHalfByte = FindSubString(sConvert, GetStringLeft(sHex, 1)); // Get the value of the next hexadecimal character
        if(nHalfByte == -1) return 0;                                // Invalid character in the string!
        nReturn  = nReturn << 4;                                     // Rightshift by 4 bits
        nReturn |= nHalfByte;                                        // OR in the next bits
        sHex = GetStringRight(sHex, GetStringLength(sHex) - 1);      // Remove the parsed character from the string
    }

    return nReturn;
}

const string sHexDigits = "0123456789abcdef";

int HexToInt(string sHex)
{
    if(sHex == "") return 0;
    int nVal = 0;
    string sDig = GetStringLowerCase(sHex);
    int nLen = GetStringLength(sDig);
    int nIdx = 2;
    while(nIdx < nLen)
    {
        nVal = (nVal << 4) + FindSubString(sHexDigits,GetSubString(sDig,nIdx++,1));
    }
    return nVal;
}

float TenMinutesToSeconds(int tenMinutes)
{
    return TurnsToSeconds(tenMinutes) * 10;
}

float MinutesToSeconds(int minutes)
{
    return TurnsToSeconds(minutes);
}

float MetersToFeet(float fMeters)
{
     fMeters *= 3.281;
     return fMeters;
}

int GetIsValidAlignment ( int iLawChaos, int iGoodEvil,int iAlignRestrict, int iAlignRstrctType, int iInvertRestriction )
{
    //deal with no restrictions first
    if(iAlignRstrctType == 0)
        return TRUE;
    //convert the ALIGNMENT_* into powers of 2
    iLawChaos = FloatToInt(pow(2.0, IntToFloat(iLawChaos-1)));
    iGoodEvil = FloatToInt(pow(2.0, IntToFloat(iGoodEvil-1)));
    //initialise result varaibles
    int iAlignTest, iRetVal = TRUE;
    //do different test depending on what type of restriction
    if(iAlignRstrctType == 1 || iAlignRstrctType == 3)   //I.e its 1 or 3
        iAlignTest = iLawChaos;
    if(iAlignRstrctType == 2 || iAlignRstrctType == 3) //I.e its 2 or 3
        iAlignTest = iAlignTest | iGoodEvil;
    //now the real test.
    if(iAlignRestrict & iAlignTest)//bitwise AND comparison
        iRetVal = FALSE;
    //invert it if applicable
    if(iInvertRestriction)
        iRetVal = !iRetVal;
    //and return the result
    return iRetVal;
}


location GetRandomCircleLocation(location lBase, float fDistance=1.0)
{
    // Pick a random angle for the location.
    float fAngle = IntToFloat(Random(3600)) / 10.0;

    // Pick a random facing for the location.
    float fFacing = IntToFloat(Random(3600)) / 10.0;

    // Pick a random distance from the base location.
    float fHowFar = IntToFloat(Random(FloatToInt(fDistance * 10.0))) / 10.0;

    // Retreive the position vector from the location.
    vector vPosition = GetPositionFromLocation(lBase);

    // Modify the base x/y position by the distance and angle.
    vPosition.y += (sin(fAngle) * fHowFar);
    vPosition.x += (cos(fAngle) * fHowFar);

    // Return the new random location.
    return Location(GetAreaFromLocation(lBase), vPosition, fFacing);
}

location AddLocationToLocation(location lMaster, location lAdd)
{
    //firstly rotate lAdd according to lMaster
    vector vAdd = GetPositionFromLocation(lAdd);
    //zero is +y in NWN convert zero to +x
    float fAngle = GetFacingFromLocation(lMaster);
    //convert angle to radians
    fAngle = ((fAngle-90)/360.0)*2.0*PI;
    vector vNew;
    vNew.x = (vAdd.x*cos(fAngle))-(vAdd.y*sin(fAngle));
    vNew.y = (vAdd.x*sin(fAngle))+(vAdd.y*cos(fAngle));
    vNew.z = vAdd.z;

    //now just add them on
    vector vMaster = GetPositionFromLocation(lMaster);
    vNew.x += vMaster.x;
    vNew.y += vMaster.y;
    vNew.z += vMaster.z;
    float fNew = GetFacingFromLocation(lAdd)+GetFacingFromLocation(lMaster);

    //return a location
    location lReturn = Location(GetAreaFromLocation(lMaster), vNew, fNew);
    return lReturn;
}





string GetRGB(int red = 15,int green = 15,int blue = 15)
{
    object coloringBook = GetObjectByTag("ColoringBook");
    if (coloringBook == OBJECT_INVALID)
        coloringBook = CreateObject(OBJECT_TYPE_ITEM,"gen_coloringbook",GetLocation(GetObjectByTag("HEARTOFCHAOS")));
    string buffer = GetName(coloringBook);
    if(red > 15) red = 15; if(green > 15) green = 15; if(blue > 15) blue = 15;
    if(red < 1)  red = 1;  if(green < 1)  green = 1;  if(blue < 1)  blue = 1;
    return "<c" + GetSubString(buffer, red - 1, 1) + GetSubString(buffer, green - 1, 1) + GetSubString(buffer, blue - 1, 1) +">";
}

int GetIsAPCInArea(object oArea, int bNPCPartyMembers = TRUE)
{
    object oPC = GetFirstPC();
    while (GetIsObjectValid(oPC))
    {
        if(bNPCPartyMembers)
        {
            object oFaction = GetFirstFactionMember(oPC, FALSE);
            while(GetIsObjectValid(oFaction))
            {
                if (GetArea(oFaction) == oArea)
                    return TRUE;
                oFaction = GetNextFactionMember(oPC, FALSE);
            }
        }
        oPC = GetNextPC();
    }
    return FALSE;
}

string IntToPaddedString(int nX, int nLength = 4, int nSigned = FALSE)
{
    if(nSigned)
        nLength--;//to allow for sign
    string sResult = IntToString(nX);
    // Trunctate to nLength rightmost characters
    if(GetStringLength(sResult) > nLength)
        sResult = GetStringRight(sResult, nLength);
    // Pad the left side with zero
    while(GetStringLength(sResult) < nLength)
    {
        sResult = "0" +sResult;
    }
    if(nSigned)
    {
        if(nX>=0)
            sResult = "+"+sResult;
        else
            sResult = "-"+sResult;
    }
    return sResult;
}

string ReplaceChars(string sString, string sToReplace, string sReplacement)
{
    int nInd;
    while((nInd = FindSubString(sString, sToReplace)) != -1)
    {
        sString = GetStringLeft(sString, nInd) +
                  sReplacement +
                  GetSubString(sString,
                               nInd + GetStringLength(sToReplace),
                               GetStringLength(sString) - nInd - GetStringLength(sToReplace)
                               );
    }
    return sString;
}

void MyDestroyObject(object oObject)
{
    if(GetIsObjectValid(oObject))
    {
        SetCommandable(TRUE ,oObject);
        AssignCommand(oObject, ClearAllActions());
        AssignCommand(oObject, SetIsDestroyable(TRUE, FALSE, FALSE));
        AssignCommand(oObject, DestroyObject(oObject));
        // May not necessarily work on first iteration
        DestroyObject(oObject);
        DelayCommand(0.1f, MyDestroyObject(oObject));
    }
}

int GetHasItem(object oPC, string sResRef)
{
    object oItem = GetFirstItemInInventory(oPC);

    while(GetIsObjectValid(oItem) && GetResRef(oItem) != sResRef)
        oItem = GetNextItemInInventory(oPC);

    return GetResRef(oItem) == sResRef;
}

int GetItemACBase(object oArmor)
{
    int nBonusAC = 0;

    // oItem is not armor then return an error
    if(GetBaseItemType(oArmor) != BASE_ITEM_ARMOR)
        return -1;

    // check each itemproperty for AC Bonus
    itemproperty ipAC = GetFirstItemProperty(oArmor);

    while(GetIsItemPropertyValid(ipAC))
    {
        int nType = GetItemPropertyType(ipAC);

        // check for ITEM_PROPERTY_AC_BONUS
        if(nType == ITEM_PROPERTY_AC_BONUS)
        {
            nBonusAC = GetItemPropertyCostTableValue(ipAC);
            break;
        }

        // get next itemproperty
        ipAC = GetNextItemProperty(oArmor);
    }

    // return base AC
    return GetItemACValue(oArmor) - nBonusAC;
}

// returns -1 on error, or the const int ARMOR_TYPE_*
int GetArmorType(object oArmor)
{
    int nType = -1;

    // get and check Base AC
    switch(GetItemACBase(oArmor) )
    {
        case 0: nType = ARMOR_TYPE_CLOTH;   break;
        case 1: nType = ARMOR_TYPE_LIGHT;   break;
        case 2: nType = ARMOR_TYPE_LIGHT;   break;
        case 3: nType = ARMOR_TYPE_LIGHT;   break;
        case 4: nType = ARMOR_TYPE_MEDIUM;  break;
        case 5: nType = ARMOR_TYPE_MEDIUM;  break;
        case 6: nType = ARMOR_TYPE_HEAVY;   break;
        case 7: nType = ARMOR_TYPE_HEAVY;   break;
        case 8: nType = ARMOR_TYPE_HEAVY;   break;
    }

    // return type
    return nType;
}

int CompareAlignment(object oSource, object oTarget)
{
    int iStepDif;
    int iGE1 = GetAlignmentGoodEvil(oSource);
    int iLC1 = GetAlignmentLawChaos(oSource);
    int iGE2 = GetAlignmentGoodEvil(oTarget);
    int iLC2 = GetAlignmentLawChaos(oTarget);

    if(iGE1 == ALIGNMENT_GOOD){
        if(iGE2 == ALIGNMENT_NEUTRAL)
            iStepDif += 1;
        else if(iGE2 == ALIGNMENT_EVIL)
            iStepDif += 2;
    }
    else if(iGE1 == ALIGNMENT_NEUTRAL){
        if(iGE2 != ALIGNMENT_NEUTRAL)
            iStepDif += 1;
    }
    else if(iGE1 == ALIGNMENT_EVIL){
        if(iLC2 == ALIGNMENT_NEUTRAL)
            iStepDif += 1;
        else if(iLC2 == ALIGNMENT_GOOD)
            iStepDif += 2;
    }
    if(iLC1 == ALIGNMENT_LAWFUL){
        if(iLC2 == ALIGNMENT_NEUTRAL)
            iStepDif += 1;
        else if(iLC2 == ALIGNMENT_CHAOTIC)
            iStepDif += 2;
    }
    else if(iLC1 == ALIGNMENT_NEUTRAL){
        if(iLC2 != ALIGNMENT_NEUTRAL)
            iStepDif += 1;
    }
    else if(iLC1 == ALIGNMENT_CHAOTIC){
        if(iLC2 == ALIGNMENT_NEUTRAL)
            iStepDif += 1;
        else if(iLC2 == ALIGNMENT_LAWFUL)
            iStepDif += 2;
    }
    return iStepDif;
}

void ForceEquip(object oPC, object oItem, int nSlot, int nThCall = 0)
{
    // Sanity checks
    // Make sure the parameters are valid
    if(!GetIsObjectValid(oPC)) return;
    if(!GetIsObjectValid(oItem)) return;
    // Make sure that the object we are attempting equipping is the latest one to be ForceEquipped into this slot
    if(GetIsObjectValid(GetLocalObject(oPC, "ForceEquipToSlot_" + IntToString(nSlot)))
        &&
       GetLocalObject(oPC, "ForceEquipToSlot_" + IntToString(nSlot)) != oItem
       )
        return;
    // Fail on non-commandable NPCs after ~1min
    if(!GetIsPC(oPC) && !GetCommandable(oPC) && nThCall > 60)
    {
        WriteTimestampedLogEntry("ForceEquip() failed on non-commandable NPC: " + DebugObject2Str(oPC) + " for item: " + DebugObject2Str(oItem));
        return;
    }

    float fDelay;

    // Check if the equipping has already happened
    if(GetItemInSlot(nSlot, oPC) != oItem)
    {
        // Test and increment the control counter
        if(nThCall++ == 0)
        {
            // First, try to do the equipping non-intrusively and give the target a reasonable amount of time to do it
            AssignCommand(oPC, ActionEquipItem(oItem, nSlot));
            fDelay = 1.0f;

            // Store the item to be equipped in a local variable to prevent contest between two different calls to ForceEquip
            SetLocalObject(oPC, "ForceEquipToSlot_" + IntToString(nSlot), oItem);
        }
        else
        {
            // Nuke the target's action queue. This should result in "immediate" equipping of the item
            if(GetIsPC(oPC) || nThCall > 5) // Skip nuking NPC action queue at first, since that can cause problems. 5 = magic number here. May need adjustment
            {
                AssignCommand(oPC, ClearAllActions());
                AssignCommand(oPC, ActionEquipItem(oItem, nSlot));
            }
            // Use a lenghtening delay in order to attempt handling lag and possible other interference. From 0.1s to 1s
            fDelay = min(nThCall, 10) / 10.0f;
        }

        // Loop
        DelayCommand(fDelay, ForceEquip(oPC, oItem, nSlot, nThCall));
    }
    // It has, so clean up
    else
        DeleteLocalObject(oPC, "ForceEquipToSlot_" + IntToString(nSlot));
}

void ForceUnequip(object oPC, object oItem, int nSlot, int nThCall = 0)
{
    // Sanity checks
    if(!GetIsObjectValid(oPC)) return;
    if(!GetIsObjectValid(oItem)) return;
    // Fail on non-commandable NPCs after ~1min
    if(!GetIsPC(oPC) && !GetCommandable(oPC) && nThCall > 60)
    {
        WriteTimestampedLogEntry("ForceUnequip() failed on non-commandable NPC: " + DebugObject2Str(oPC) + " for item: " + DebugObject2Str(oItem));
        return;
    }

    float fDelay;

    // Delay the first unequipping call to avoid a bug that occurs when an object that was just equipped is unequipped right away
    // - The item is not unequipped properly, leaving some of it's effects in the creature's stats and on it's model.
    if(nThCall == 0)
    {
        //DelayCommand(0.5, ForceUnequip(oPC, oItem, nSlot, FALSE));
        fDelay = 0.5;
    }
    else if(GetItemInSlot(nSlot, oPC) == oItem)
    {
        // Attempt to avoid interference by not clearing actions before the first attempt
        if(nThCall > 1)
            if(GetIsPC(oPC) || nThCall > 5) // Skip nuking NPC action queue at first, since that can cause problems. 5 = magic number here. May need adjustment
                AssignCommand(oPC, ClearAllActions());

        AssignCommand(oPC, ActionUnequipItem(oItem));

        // Ramp up the delay if the action is not getting through. Might let whatever is intefering finish
        fDelay = min(nThCall, 10) / 10.0f;
    }
    // The item has already been unequipped
    else
        return;

    // Loop
    DelayCommand(fDelay, ForceUnequip(oPC, oItem, nSlot, ++nThCall));
}

int GetHasFreeHand(object oCreature)
{
    return !GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature))
         || !GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature));
}

int GetIsEncumbered(object oCreature)
{
    int iStrength = GetAbilityScore(oCreature, ABILITY_STRENGTH);
    if(iStrength > 50)
        return FALSE; // encumbrance.2da doesn't go that high, so automatic success

    return GetWeight(oCreature) > StringToInt(Get2DACache("encumbrance", "Normal", iStrength));
}

void TryToIDItems(object oPC = OBJECT_SELF)
{
    int nGP;
    string sMax = Get2DACache("SkillVsItemCost", "DeviceCostMax", GetSkillRank(SKILL_LORE, oPC));
    int nMax = StringToInt(sMax);
    if (sMax == "") nMax = 120000000;
    object oItem = GetFirstItemInInventory(oPC);
    while(GetIsObjectValid(oItem))
    {
        if(!GetIdentified(oItem))
        {
            // Check for the value of the item first.
            SetIdentified(oItem, TRUE);
            nGP = GetGoldPieceValue(oItem);
            // If oPC has enough Lore skill to ID the item, then do so.
            if(nMax >= nGP)
                SendMessageToPC(oPC, GetStringByStrRef(16826224) + " " + GetName(oItem) + " " + GetStringByStrRef(16826225));
            else
                SetIdentified(oItem, FALSE);
        }
        oItem = GetNextItemInInventory(oPC);
    }
}

string BooleanToString(int bool, int bTLK = FALSE)
{
    return bTLK ?
            (bool ? GetStringByStrRef(8141) : GetStringByStrRef(8142)):
            (bool ? "True" : "False");
}

string TrimString(string s)
{
    int nCrop = 0;
    string temp;
    // Find end of the leading whitespace
    while(TRUE)
    {
        // Get the next character in the string, starting from the beginning
        temp = GetSubString(s, nCrop, 1);
        if(temp == " "  || // Space
           temp == "\n")   // Line break
            nCrop++;
        else
            break;
    }
    // Crop the leading whitespace
    s = GetSubString(s, nCrop, GetStringLength(s) - nCrop);

    // Find the beginning of the trailing whitespace
    nCrop = 0;
    while(TRUE)
    {
        // Get the previous character in the string, starting from the end
        temp = GetSubString(s, GetStringLength(s) - 1 - nCrop, 1);
        if(temp == " "  || // Space
           temp == "\n")   // Line break
            nCrop++;
        else
            break;
    }
    // Crop the trailing whitespace
    s = GetSubString(s, 0, GetStringLength(s) - nCrop);

    return s;
}

int GetFirstCharacterIndex(string s1)
{
    object oLookup = GetWaypointByTag("prc_str_lookup");
    if(!GetIsObjectValid(oLookup))
        oLookup = CreateObject(OBJECT_TYPE_WAYPOINT, "prc_str_lookup", GetLocation(GetObjectByTag("HEARTOFCHAOS")));

    return GetLocalInt(oLookup, GetStringUpperCase(GetSubString(s1, 0, 1)));
}

int StringCompare(string s1, string s2)
{
    object oLookup = GetWaypointByTag("prc_str_lookup");
    if(!GetIsObjectValid(oLookup))
        oLookup = CreateObject(OBJECT_TYPE_WAYPOINT, "prc_str_lookup", GetLocation(GetObjectByTag("HEARTOFCHAOS")));

    // Start comparing
    int nT,
        i = 0,
        nMax = min(GetStringLength(s1), GetStringLength(s2));
    while(i < nMax)
    {
        // Get the difference between the values of i:th characters
        nT = GetLocalInt(oLookup, GetSubString(s1, i, 1)) - GetLocalInt(oLookup, GetSubString(s2, i, 1));
        i++;
        if(nT < 0)
            return -1;
        if(nT == 0)
            continue;
        if(nT > 0)
            return 1;
    }

    // The strings have the same base. Of such, the shorter precedes
    nT = GetStringLength(s1) - GetStringLength(s2);
    if(nT < 0)
        return -1;
    if(nT > 0)
        return 1;

    // The strings were equal
    return 0;
}

string ReplaceString(string sString, string sFind, string sReplace)
{
    int n = FindSubString(sString, sFind);
    if(n!=-1)
        return GetStringLeft(sString, n) + sReplace + GetStringRight(sString, GetStringLength(sString) - GetStringLength(sFind) - n);
    else
        return sString;
}

float GetRelativeAngleBetweenLocations(location lFrom, location lTo)
{
    vector vPos1 = GetPositionFromLocation(lFrom);
    vector vPos2 = GetPositionFromLocation(lTo);
    //sanity check
    if(GetDistanceBetweenLocations(lFrom, lTo) == 0.0)
        return 0.0;

    float fAngle = acos((vPos2.x - vPos1.x) / GetDistanceBetweenLocations(lFrom, lTo));
    // The above formula only returns values [0, 180], so test for negative y movement
    if((vPos2.y - vPos1.y) < 0.0f)
        fAngle = 360.0f -fAngle;

    return fAngle;
}

string ItemPropertyToString(itemproperty ipTest)
{
    int nIPType = GetItemPropertyType(ipTest);
    string sName = GetStringByStrRef(StringToInt(Get2DACache("itempropdef", "GameStrRef", nIPType)));
    if(GetItemPropertySubType(ipTest) != -1)//nosubtypes
    {
        string sSubTypeResRef =Get2DACache("itempropdef", "SubTypeResRef", nIPType);
        int nTlk = StringToInt(Get2DACache(sSubTypeResRef, "Name", GetItemPropertySubType(ipTest)));
        if(nTlk > 0)
            sName += " "+GetStringByStrRef(nTlk);
    }
    if(GetItemPropertyParam1(ipTest) != -1)
    {
        string sParamResRef =Get2DACache("iprp_paramtable", "TableResRef", GetItemPropertyParam1(ipTest));
        if(Get2DACache("itempropdef", "SubTypeResRef", nIPType) != ""
            && Get2DACache(Get2DACache("itempropdef", "SubTypeResRef", nIPType), "TableResRef", GetItemPropertyParam1(ipTest)) != "")
            sParamResRef =Get2DACache(Get2DACache("itempropdef", "SubTypeResRef", nIPType), "TableResRef", GetItemPropertyParam1(ipTest));
        int nTlk = StringToInt(Get2DACache(sParamResRef, "Name", GetItemPropertyParam1Value(ipTest)));
        if(nTlk > 0)
            sName += " "+GetStringByStrRef(nTlk);
    }
    if(GetItemPropertyCostTable(ipTest) != -1)
    {
        string sCostResRef =Get2DACache("iprp_costtable", "Name", GetItemPropertyCostTable(ipTest));
        int nTlk = StringToInt(Get2DACache(sCostResRef, "Name", GetItemPropertyCostTableValue(ipTest)));
        if(nTlk > 0)
            sName += " "+GetStringByStrRef(nTlk);
    }
    return sName;
}

//Check for XP
int GetHasXPToSpend(object oPC, int nCost)
{
    // To be TRUE, make sure that oPC wouldn't lose a level by spending nCost.
    int nHitDice = GetHitDice(oPC);
    int nHitDiceXP = (500 * nHitDice * (nHitDice - 1)); // simplification of the sum
    //get current XP
    int nXP = GetXP(oPC);
    if(!nXP)
        nXP = GetLocalInt(oPC, "NPC_XP");
    //the test
    if (nXP >= (nHitDiceXP + nCost))
        return TRUE;
    return FALSE;
}

//Spend XP
void SpendXP(object oPC, int nCost)
{
    if(nCost > 0)
    {
        if(GetXP(oPC))
            SetXP(oPC, GetXP(oPC) - nCost);
        else if(GetLocalInt(oPC, "NPC_XP"))
            SetLocalInt(oPC, "NPC_XP", GetLocalInt(oPC, "NPC_XP")-nCost);
    }
}

//Check for GP
int GetHasGPToSpend(object oPC, int nCost)
{
    //if its a NPC, get master
    while(!GetIsPC(oPC)
        && GetIsObjectValid(GetMaster(oPC)))
    {
        oPC = GetMaster(oPC);
    }
    //test if it has gold
    if(GetIsPC(oPC))
    {
        return GetGold(oPC) >= nCost;
    }
    //NPC in NPC faction
    //cannot posses gold
    return FALSE;
}

//Spend GP
void SpendGP(object oPC, int nCost)
{
    if(nCost > 0)
    {
        //if its a NPC, get master
        while(!GetIsPC(oPC)
            && GetIsObjectValid(GetMaster(oPC)))
        {
            oPC = GetMaster(oPC);
        }
        TakeGoldFromCreature(nCost, oPC, TRUE);
    }
}



int isNotShield(object oItem)
{
    int iType = GetBaseItemType(oItem);

    return !(iType == BASE_ITEM_LARGESHIELD
           || iType == BASE_ITEM_TOWERSHIELD
           || iType == BASE_ITEM_SMALLSHIELD
           // Added torches to the check as they should not count either
           || iType == BASE_ITEM_TORCH);
}


void ActionUseItemPropertyAtObject(object oItem, itemproperty ipIP, object oTarget = OBJECT_SELF)
{
    int nIPSpellID = GetItemPropertySubType(ipIP);
    string sSpellID = Get2DACache("iprp_spells", "SpellIndex", nIPSpellID);
    int nSpellID = StringToInt(sSpellID);
    string sCategory = Get2DACache("spells", "Category", nSpellID);
    int nCategory = StringToInt(sCategory);
    int nCategoryPotionRandom = FALSE;
    //potions are strange
    //seem to be hardcoded to certain categories
    if(GetBaseItemType(oItem) == BASE_ITEM_POTIONS)
    {
        //potions are self-only
        if(oTarget != OBJECT_SELF)
            return;

        if(nCategory == TALENT_CATEGORY_BENEFICIAL_HEALING_AREAEFFECT
            || nCategory == TALENT_CATEGORY_BENEFICIAL_HEALING_TOUCH)
            nCategory = TALENT_CATEGORY_BENEFICIAL_HEALING_POTION;
        else if(nCategory == TALENT_CATEGORY_BENEFICIAL_CONDITIONAL_AREAEFFECT
            || nCategory == TALENT_CATEGORY_BENEFICIAL_CONDITIONAL_SINGLE)
            nCategory = TALENT_CATEGORY_BENEFICIAL_CONDITIONAL_POTION;
        else if(nCategory == TALENT_CATEGORY_BENEFICIAL_ENHANCEMENT_AREAEFFECT
            || nCategory == TALENT_CATEGORY_BENEFICIAL_ENHANCEMENT_SINGLE
            || nCategory == TALENT_CATEGORY_BENEFICIAL_ENHANCEMENT_SELF)
            nCategory = TALENT_CATEGORY_BENEFICIAL_ENHANCEMENT_POTION;
        else if(nCategory == TALENT_CATEGORY_BENEFICIAL_PROTECTION_SELF
            || nCategory == TALENT_CATEGORY_BENEFICIAL_PROTECTION_SINGLE
            || nCategory == TALENT_CATEGORY_BENEFICIAL_PROTECTION_AREAEFFECT)
            nCategory = TALENT_CATEGORY_BENEFICIAL_PROTECTION_POTION;
        else
        {
            //something odd here add strage randomized coding inside the loop
            nCategoryPotionRandom = TRUE;
            nCategory = TALENT_CATEGORY_BENEFICIAL_HEALING_POTION;
        }

    }

    talent tItem;
    tItem = GetCreatureTalentRandom(nCategory);
    int nCount = 0;
    while(GetIsTalentValid(tItem)
        && nCount < ACTION_USE_ITEM_TMI_LIMIT) //this is the TMI limiting thing, change as appropriate
    {
        if(nCategoryPotionRandom)
        {
            switch(d4())
            {
                default:
                case 1: nCategory = TALENT_CATEGORY_BENEFICIAL_HEALING_POTION; break;
                case 2: nCategory = TALENT_CATEGORY_BENEFICIAL_CONDITIONAL_POTION; break;
                case 3: nCategory = TALENT_CATEGORY_BENEFICIAL_ENHANCEMENT_POTION; break;
                case 4: nCategory = TALENT_CATEGORY_BENEFICIAL_PROTECTION_POTION; break;
            }
        }

        if(GetTypeFromTalent(tItem) == TALENT_TYPE_SPELL
            && GetIdFromTalent(tItem) == nSpellID)
        {
            ActionUseTalentOnObject(tItem, oTarget);
            //end while loop
            return;
        }
        nCount++;
        tItem = GetCreatureTalentRandom(nCategory);
    }
    //if you got to this point, something whent wrong
    //rather than failing silently, well log it
    DoDebug("ERROR: ActionUseItemProperty() failed for "+GetName(OBJECT_SELF)+" using "+GetName(oItem)+" to cast "+IntToString(nSpellID));
}


void ActionUseItemPropertyAtLocation(object oItem, itemproperty ipIP, location lTarget)
{
    int nIPSpellID = GetItemPropertySubType(ipIP);
    string sSpellID = Get2DACache("iprp_spells", "SpellIndex", nIPSpellID);
    int nSpellID = StringToInt(sSpellID);
    string sCategory = Get2DACache("spells", "Category", nSpellID);
    int nCategory = StringToInt(sCategory);

    //potions are odd
    //but since they are self-only it doesnt matter

    talent tItem;
    tItem = GetCreatureTalentRandom(nCategory);
    int nCount = 0;
    while(GetIsTalentValid(tItem)
        && nCount < ACTION_USE_ITEM_TMI_LIMIT) //this is the TMI limiting thing, change as appropriate
    {
        if(GetTypeFromTalent(tItem) == TALENT_TYPE_SPELL
            && GetIdFromTalent(tItem) == nSpellID)
        {
            ActionUseTalentAtLocation(tItem, lTarget);
            //end while loop
            return;
        }
        nCount++;
        tItem = GetCreatureTalentRandom(nCategory);
    }
    //if you got to this point, something whent wrong
    //rather than failing silently, well log it
    DoDebug("ERROR: ActionUseItemProperty() failed for "+GetName(OBJECT_SELF)+" using "+GetName(oItem)+" to cast "+IntToString(nSpellID));
}


//::///////////////////////////////////////////////
//:: Get Has Effect
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    Checks to see if the target has a given
    spell effect
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Oct 26, 2001
//:://////////////////////////////////////////////
int PRCGetHasEffect(int nEffectType, object oTarget = OBJECT_SELF)
{
    effect eCheck = GetFirstEffect(oTarget);
    while(GetIsEffectValid(eCheck))
    {
        if(GetEffectType(eCheck) == nEffectType)
        {
             return TRUE;
        }
        eCheck = GetNextEffect(oTarget);
    }
    return FALSE;
}
// Test main
//void main(){}

//::///////////////////////////////////////////////
//:: PRCGetIsFighting
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    Checks if the passed object has an Attempted
    Attack or Spell Target
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: March 13, 2002
//:://////////////////////////////////////////////
int PRCGetIsFighting(object oFighting = OBJECT_SELF)
{
    return GetIsObjectValid(GetAttemptedAttackTarget())
         || GetIsObjectValid(GetAttemptedSpellTarget());
}

// Determine whether the character is polymorphed or shfited.
int GetIsPolyMorphedOrShifted(object oCreature)
{
    int bPoly = FALSE;

    effect eChk = GetFirstEffect(oCreature);

    while (GetIsEffectValid(eChk))
    {
        if (GetEffectType(eChk) == EFFECT_TYPE_POLYMORPH)
            bPoly = TRUE;

        eChk = GetNextEffect(oCreature);
    }

    if (GetPersistantLocalInt(oCreature, "nPCShifted"))
        bPoly = TRUE;

    return bPoly;
}

float PRCGetRandomDelay(float fMinimumTime = 0.4, float fMaximumTime = 1.1)
{
    float fRandom = fMaximumTime - fMinimumTime;
    if(fRandom < 0.0)
    {
        return 0.0;
    }
    else
    {
        int nRandom;
        nRandom = FloatToInt(fRandom  * 10.0);
        nRandom = Random(nRandom) + 1;
        fRandom = IntToFloat(nRandom);
        fRandom /= 10.0;
        return fRandom + fMinimumTime;
    }
}

int GetSkill(object oObject, int nSkill, int bSynergy = FALSE, int bSize = FALSE, int bAbilityMod = TRUE, int bEffect = TRUE, int bArmor = TRUE, int bShield = TRUE, int bFeat = TRUE)
{
    if(!GetIsObjectValid(oObject))
        return 0;
    if(!GetHasSkill(nSkill, oObject))
        return 0;//no skill set it to zero
    int nSkillRank;  //get the current value at the end, after effects are applied
    if(bSynergy)
    {
        if(nSkill == SKILL_SET_TRAP
            && GetSkill(oObject, SKILL_DISABLE_TRAP, FALSE, FALSE, FALSE,
                FALSE, FALSE, FALSE, FALSE) >= 5)
                nSkillRank += 2;
        if(nSkill == SKILL_DISABLE_TRAP
            && GetSkill(oObject, SKILL_SET_TRAP, FALSE, FALSE, FALSE,
                FALSE, FALSE, FALSE, FALSE) >= 5)
                nSkillRank += 2;
    }
    if(bSize)
        if(nSkill == SKILL_HIDE)//only hide is affected by size
            nSkillRank += (PRCGetCreatureSize(oObject)-3)*(-4);
    if(!bAbilityMod)
    {
        string sAbility = Get2DACache("skills", "KeyAbility", nSkill);
        int nAbility;
        if(sAbility == "STR")
            nAbility = ABILITY_STRENGTH;
        else if(sAbility == "DEX")
            nAbility = ABILITY_DEXTERITY;
        else if(sAbility == "CON")
            nAbility = ABILITY_CONSTITUTION;
        else if(sAbility == "INT")
            nAbility = ABILITY_INTELLIGENCE;
        else if(sAbility == "WIS")
            nAbility = ABILITY_WISDOM;
        else if(sAbility == "CHA")
            nAbility = ABILITY_CHARISMA;
        nSkillRank -= GetAbilityModifier(nAbility, oObject);
    }
    if(!bEffect)
    {
        ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectSkillIncrease(nSkill, 30), oObject, 0.001);
        nSkillRank -= 30;
    }
    if(!bArmor
        && GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_CHEST, oObject))
        && Get2DACache("skills", "ArmorCheckPenalty", nSkill) == "1")
    {
        object oItem = GetItemInSlot(INVENTORY_SLOT_CHEST, oObject);
        // Get the torso model number
        int nTorso = GetItemAppearance( oItem, ITEM_APPR_TYPE_ARMOR_MODEL, ITEM_APPR_ARMOR_MODEL_TORSO);
        // Read 2DA for base AC
        // Can also use "parts_chest" which returns it as a "float"
        int nACBase = StringToInt(Get2DACache( "des_crft_appear", "BaseAC", nTorso));
        int nSkillMod;
        switch(nACBase)
        {
            case 0: nSkillMod = 0; break;
            case 1: nSkillMod = 0; break;
            case 2: nSkillMod = 0; break;
            case 3: nSkillMod = -1; break;
            case 4: nSkillMod = -2; break;
            case 5: nSkillMod = -5; break;
            case 6: nSkillMod = -7; break;
            case 7: nSkillMod = -7; break;
            case 8: nSkillMod = -8; break;
        }
        nSkillRank -= nSkillMod;
    }
    if(!bShield
        && GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oObject))
        && Get2DACache("skills", "ArmorCheckPenalty", nSkill) == "1")
    {
        object oItem = GetItemInSlot(INVENTORY_SLOT_CHEST, oObject);
        int nBase = GetBaseItemType(oItem);
        int nSkillMod;
        switch(nBase)
        {
            case BASE_ITEM_TOWERSHIELD: nSkillMod = -10; break;
            case BASE_ITEM_LARGESHIELD: nSkillMod = -2; break;
            case BASE_ITEM_SMALLSHIELD: nSkillMod = -1; break;
        }
        nSkillRank -= nSkillMod;
    }
    if(!bFeat)
    {
        int nSkillMod;
        int nEpicFeat;
        int nFocusFeat;
        switch(nSkill)
        {
            case SKILL_ANIMAL_EMPATHY:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_ANIMAL_EMPATHY;
                nFocusFeat = FEAT_SKILL_FOCUS_ANIMAL_EMPATHY;
                break;
            case SKILL_APPRAISE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_APPRAISE;
                nFocusFeat = FEAT_SKILLFOCUS_APPRAISE;
                if(GetHasFeat(FEAT_SILVER_PALM, oObject))
                    nSkillMod += 2;
                break;
            case SKILL_BLUFF:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_BLUFF;
                nFocusFeat = FEAT_SKILL_FOCUS_BLUFF;
                break;
            case SKILL_CONCENTRATION:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_CONCENTRATION;
                nFocusFeat = FEAT_SKILL_FOCUS_CONCENTRATION;
                if(GetHasFeat(FEAT_SKILL_AFFINITY_CONCENTRATION, oObject))
                    nSkillMod += 2;
                break;
            case SKILL_CRAFT_ARMOR:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_CRAFT_ARMOR;
                nFocusFeat = FEAT_SKILL_FOCUS_CRAFT_ARMOR;
                break;
            case SKILL_CRAFT_TRAP:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_CRAFT_TRAP;
                nFocusFeat = FEAT_SKILL_FOCUS_CRAFT_TRAP;
                break;
            case SKILL_CRAFT_WEAPON:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_CRAFT_WEAPON;
                nFocusFeat = FEAT_SKILL_FOCUS_CRAFT_WEAPON;
                break;
            case SKILL_DISABLE_TRAP:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_DISABLETRAP;
                nFocusFeat = FEAT_SKILL_FOCUS_DISABLE_TRAP;
                break;
            case SKILL_DISCIPLINE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_DISCIPLINE;
                nFocusFeat = FEAT_SKILL_FOCUS_DISCIPLINE;
                break;
            case SKILL_HEAL:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_HEAL;
                nFocusFeat = FEAT_SKILL_FOCUS_HEAL;
                break;
            case SKILL_HIDE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_HIDE;
                nFocusFeat = FEAT_SKILL_FOCUS_HIDE;
                break;
            case SKILL_INTIMIDATE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_INTIMIDATE;
                nFocusFeat = FEAT_SKILL_FOCUS_INTIMIDATE;
                break;
            case SKILL_LISTEN:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_LISTEN;
                nFocusFeat = FEAT_SKILL_FOCUS_LISTEN;
                if(GetHasFeat(FEAT_SKILL_AFFINITY_LISTEN, oObject))
                    nSkillMod += 2;
                if(GetHasFeat(FEAT_PARTIAL_SKILL_AFFINITY_LISTEN, oObject))
                    nSkillMod += 1;
                break;
            case SKILL_LORE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_LORE;
                nFocusFeat = FEAT_SKILL_FOCUS_LORE;
                if(GetHasFeat(FEAT_SKILL_AFFINITY_LORE, oObject))
                    nSkillMod += 2;
                if(GetHasFeat(FEAT_COURTLY_MAGOCRACY, oObject))
                    nSkillMod += 2;
                if(GetHasFeat(FEAT_BARDIC_KNOWLEDGE, oObject))
                    nSkillMod += GetLevelByClass(CLASS_TYPE_BARD, oObject)
                        +GetLevelByClass(CLASS_TYPE_HARPER, oObject);
                        +GetLevelByClass(CLASS_TYPE_SUBLIME_CHORD, oObject);
                break;
            case SKILL_MOVE_SILENTLY:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_MOVESILENTLY;
                nFocusFeat = FEAT_SKILL_FOCUS_MOVE_SILENTLY;
                if(GetHasFeat(FEAT_SKILL_AFFINITY_MOVE_SILENTLY, oObject))
                    nSkillMod += 2;
                break;
            case SKILL_OPEN_LOCK:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_OPENLOCK;
                nFocusFeat = FEAT_SKILL_FOCUS_OPEN_LOCK;
                break;
            case SKILL_PARRY:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_PARRY;
                nFocusFeat = FEAT_SKILL_FOCUS_PARRY;
                break;
            case SKILL_PERFORM:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_PERFORM;
                nFocusFeat = FEAT_SKILL_FOCUS_PERFORM;
                if(GetHasFeat(FEAT_ARTIST, oObject))
                    nSkillMod += 2;
                break;
            case SKILL_PERSUADE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_PERSUADE;
                nFocusFeat = FEAT_SKILL_FOCUS_PERSUADE;
                if(GetHasFeat(FEAT_SILVER_PALM, oObject))
                    nSkillMod += 2;
                break;
            case SKILL_PICK_POCKET:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_PICKPOCKET;
                nFocusFeat = FEAT_SKILL_FOCUS_PICK_POCKET;
                break;
            case SKILL_SEARCH:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_SEARCH;
                nFocusFeat = FEAT_SKILL_FOCUS_SEARCH;
                if(GetHasFeat(FEAT_SKILL_AFFINITY_SEARCH, oObject))
                    nSkillMod += 2;
                if(GetHasFeat(FEAT_PARTIAL_SKILL_AFFINITY_SEARCH, oObject))
                    nSkillMod += 1;
                break;
            case SKILL_SET_TRAP:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_SETTRAP;
                nFocusFeat = FEAT_SKILL_FOCUS_SET_TRAP;
                break;
            case SKILL_SPELLCRAFT:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_SPELLCRAFT;
                nFocusFeat = FEAT_SKILL_FOCUS_SPELLCRAFT;
                if(GetHasFeat(FEAT_COURTLY_MAGOCRACY, oObject))
                    nSkillMod += 2;
                break;
            case SKILL_SPOT:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_SPOT;
                nFocusFeat = FEAT_SKILL_FOCUS_SPOT;
                if(GetHasFeat(FEAT_SKILL_AFFINITY_SPOT, oObject))
                    nSkillMod += 2;
                if(GetHasFeat(FEAT_PARTIAL_SKILL_AFFINITY_SPOT, oObject))
                    nSkillMod += 1;
                if(GetHasFeat(FEAT_ARTIST, oObject))
                    nSkillMod += 2;
                if(GetHasFeat(FEAT_BLOODED, oObject))
                    nSkillMod += 2;
                break;
            case SKILL_TAUNT:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_TAUNT;
                nFocusFeat = FEAT_SKILL_FOCUS_TAUNT;
                break;
            case SKILL_TUMBLE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_TUMBLE;
                nFocusFeat = FEAT_SKILL_FOCUS_TUMBLE;
                break;
            case SKILL_USE_MAGIC_DEVICE:
                nEpicFeat  = FEAT_EPIC_SKILL_FOCUS_USEMAGICDEVICE;
                nFocusFeat = FEAT_SKILL_FOCUS_USE_MAGIC_DEVICE;
                break;
        }
        if(nEpicFeat != 0
            && GetHasFeat(nEpicFeat, oObject))
            nSkillMod += 10;
        if(nFocusFeat != 0
            && GetHasFeat(nFocusFeat, oObject))
            nSkillMod += 3;
        nSkillRank -= nSkillMod;
    }
    //add this at the end so any effects applied are counted
    nSkillRank += GetSkillRank(nSkill, oObject);
    return nSkillRank;
}

void ForcePutDown(object oPC, object oItem, int nSlot, int nThCall = 0)
{
    // Sanity checks
    if(!GetIsObjectValid(oPC)) return;
    if(!GetIsObjectValid(oItem)) return;
    // Fail on non-commandable NPCs after ~1min
    if(!GetIsPC(oPC) && !GetCommandable(oPC) && nThCall > 60)
    {
        WriteTimestampedLogEntry("ForcePutDown() failed on non-commandable NPC: " + DebugObject2Str(oPC) + " for item: " + DebugObject2Str(oItem));
        return;
    }

    float fDelay;

    if(GetItemInSlot(nSlot, oPC) == oItem)
    {
        // Attempt to avoid interference by not clearing actions before the first attempt
        if(nThCall > 1)
            if(GetIsPC(oPC) || nThCall > 5) // Skip nuking NPC action queue at first, since that can cause problems. 5 = magic number here. May need adjustment
                AssignCommand(oPC, ClearAllActions());

        AssignCommand(oPC, ActionPutDownItem(oItem));

        // Ramp up the delay if the action is not getting through. Might let whatever is intefering finish
        fDelay = min(nThCall, 10) / 10.0f;
    }
    // The item has already been unequipped
    else
        return;

    // Loop
    DelayCommand(fDelay, ForcePutDown(oPC, oItem, nSlot, ++nThCall));
}

// Test main
//void main() {}