//::////////////////////////////////////////////// //:: 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 ""; } 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() {}