PRC8/nwn/nwnprc/trunk/include/inc_utility.nss
Jaysyn904 57b8d88878 Renamed inc_array to stop nwnx include collisions
Renamed inc_array to stop nwnx include collisions  Added missing bonus feat 2da for Forest Master. Changed prc_newspellbook.hak to prc_nsb.hak to prevent issues with nwserver.  Updated tester module.  Updated release archive.
2024-03-03 18:06:25 -05:00

1741 lines
59 KiB
Plaintext

//:://////////////////////////////////////////////
//:: 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 "prc_class_const"
#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 prc_inc_array and inc_debug
#include "inc_uniqueid" // Depends on prc_inc_array
#include "inc_set" // Depends on prc_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() {}