PRC8/nwn/nwnprc/trunk/include/prc_inc_shifting.nss
Jaysyn904 2a7cb1002e Fixed all content filenames over 16 characters
Fixed all content filenames over 16 characters
2024-08-30 19:49:40 -04:00

3112 lines
140 KiB
Plaintext

//::///////////////////////////////////////////////
//:: Shifting include
//:: prc_inc_shifting
//::///////////////////////////////////////////////
/** @file
Defines constants, functions and structs
related to shifting.
Creature data is stored as three persistant
arrays, with synchronised indexes.
- Resref
- Creature name, as given by GetName() on the
original creature from which the resref was
gotten
- Racial type
@author Ornedan
@date Created - 2006.03.04
*/
//:://////////////////////////////////////////////
//:://////////////////////////////////////////////
//////////////////////////////////////////////////
/* Constants */
//////////////////////////////////////////////////
const int SHIFTER_TYPE_NONE = 0;
const int SHIFTER_TYPE_SHIFTER = 1;
const int SHIFTER_TYPE_SOULEATER = 2;
const int SHIFTER_TYPE_POLYMORPH = 3;
const int SHIFTER_TYPE_CHANGESHAPE = 4;
const int SHIFTER_TYPE_HUMANOIDSHAPE = 5;
const int SHIFTER_TYPE_ALTER_SELF = 6;
const int SHIFTER_TYPE_DISGUISE_SELF = 7;
const int SHIFTER_TYPE_DRUID = 8;
const int SHIFTER_TYPE_ARANEA = 9;
const int UNSHIFT_FAIL = 0;
const int UNSHIFT_SUCCESS = 1;
const int UNSHIFT_SUCCESS_DELAYED = 2;
const float SHIFTER_MUTEX_UNSET_DELAY = 3.0f;
const float SHIFTER_SHAPE_PRINT_DELAY = 3.1f;
const float SHIFTER_TEMPLATE_DESTROY_DELAY = 6.0f;
const string SHIFTER_RESREFS_ARRAY = "PRC_ShiftingResRefs_";
const string SHIFTER_NAMES_ARRAY = "PRC_ShiftingNames_";
const string SHIFTER_RACIALTYPE_ARRAY = "PRC_RacialType_";
const string SHIFTER_TRUEAPPEARANCE = "PRC_ShiftingTrueAppearance";
const string SHIFTER_ISSHIFTED_MARKER = "nPCShifted"; //"PRC_IsShifted"; // @todo Refactor across all scripts
const string SHIFTER_SHIFT_MUTEX = "PRC_Shifting_InProcess";
const string SHIFTER_RESTRICT_SPELLS = "PRC_Shifting_RestrictSpells";
const string SHIFTER_OVERRIDE_RACE = "PRC_ShiftingOverride_Race";
const string SHIFTER_TRUE_RACE = "PRC_ShiftingTrue_Race";
const string SHIFTER_ORIGINALHP = "PRC_Shifter_OriginalHP";
const string SHIFTER_ORIGINALMAXHP = "PRC_Shifter_OriginalMaxHP";
const string SHIFTING_TEMPLATE_WP_TAG = "PRC_SHIFTING_TEMPLATE_SPAWN";
const string SHIFTING_SLAITEM_RESREF = "epicshifterpower";
const string SHIFTING_SLAITEM_TAG = "EpicShifterPowers";
const string SHIFTER_DELETED_SHAPE_PREFIX = "X: "; //TODO: add TLK entry?
const int STRREF_YOUNEED = 16828326; // "You need"
const int STRREF_MORECHARLVL = 16828327; // "more character levels before you can take on that form."
const int STRREF_NOPOLYTOPC = 16828328; // "You cannot polymorph into a PC."
const int STRREF_FORBIDPOLY = 16828329; // "Target cannot be polymorphed into."
const int STRREF_SETTINGFORBID = 16828330; // "The module settings prevent this creature from being polymorphed into."
const int STRREF_PNPSFHT_FEYORSSHIFT = 16828331; // "You cannot use PnP Shifter abilities to polymorph into this creature."
const int STRREF_PNPSHFT_MORELEVEL = 16828332; // "more PnP Shifter levels before you can take on that form."
const int STRREF_NEED_SPACE = 16828333; // "Your inventory is too full for the PRC Polymorphing system to work. Please make space for three (3) helmet-size items (4x4) in your inventory before trying again."
const int STRREF_POLYMORPH_MUTEX = 16828334; // "The PRC Polymorphing system will not work while you are affected by a polymorph effect. Please remove it before trying again."
const int STRREF_SHIFTING_MUTEX = 16828335; // "Another PRC Polymorph transformation is underway at this moment. Please wait until it completes before trying again."
const int STRREF_TEMPLATE_FAILURE = 16828336; // "Polymorph failed: Failed to create a template of the creature to polymorph into."
const int DEBUG_APPLY_PROPERTIES = FALSE;
const int DEBUG_ABILITY_BOOST_CALCULATIONS = FALSE;
const int DEBUG_EFFECTS = FALSE;
const int DEBUG_EXTRA_FEATS = FALSE;
const string PRC_Shifter_ShapeGeneration = "PRC_Shifter_ShapeGeneration";
const string PRC_Shifter_ApplyEffects_EvalPRC_Generation = "PRC_Shifter_ApplyEffects_EvalPRC_Generation";
int CHUNK_SIZE = 25; //50 was too big, so use 25
//////////////////////////////////////////////////
/* Structures */
//////////////////////////////////////////////////
/**
* A struct for data about appearance.
*/
struct appearancevalues{
/* Fields for the actual appearance */
/// The appearance type aka appearance.2da row
int nAppearanceType;
/// Body part - Right foot
int nBodyPart_RightFoot;
/// Body part - Left Foot
int nBodyPart_LeftFoot;
/// Body part - Right Shin
int nBodyPart_RightShin;
/// Body part - Left Shin
int nBodyPart_LeftShin;
/// Body part - Right Thigh
int nBodyPart_RightThigh;
/// Body part - Left Thigh
int nBodyPart_LeftThight;
/// Body part - Pelvis
int nBodyPart_Pelvis;
/// Body part - Torso
int nBodyPart_Torso;
/// Body part - Belt
int nBodyPart_Belt;
/// Body part - Neck
int nBodyPart_Neck;
/// Body part - Right Forearm
int nBodyPart_RightForearm;
/// Body part - Left Forearm
int nBodyPart_LeftForearm;
/// Body part - Right Bicep
int nBodyPart_RightBicep;
/// Body part - Left Bicep
int nBodyPart_LeftBicep;
/// Body part - Right Shoulder
int nBodyPart_RightShoulder;
/// Body part - Left Shoulder
int nBodyPart_LeftShoulder;
/// Body part - Right Hand
int nBodyPart_RightHand;
/// Body part - Left Hand
int nBodyPart_LeftHand;
/// Body part - Head
int nBodyPart_Head;
/// The wing type
int nWingType;
/// The tail type
int nTailType;
/* Other stuff */
/// Portrait ID
int nPortraitID;
/// Portrait resref
string sPortraitResRef;
/// The footstep type
int nFootStepType;
///The gender
int nGender;
///Colors
// Skin color
int nSkinColor;
// Hair color
int nHairColor;
// Tattoo 1 color
int nTat1Color;
// Tattoo 2 color
int nTat2Color;
};
//////////////////////////////////////////////////
/* Function prototypes */
//////////////////////////////////////////////////
int IsPolymorphed(object oPC);
int StoreCurrentRaceAsTrueRace(object oShifter);
// True appearance stuff //
/**
* Stores the given creature's current appearance as it's true appearance.
*
* @param oShifter The creature whose true appearance to store
* @param bCarefull If this is TRUE, will only store the appearance if the creature
* is not shifted or polymorphed
* @return TRUE if the appearance was stored, FALSE if not
*/
int StoreCurrentAppearanceAsTrueAppearance(object oShifter, int bCarefull = TRUE);
/**
* Restores the given creature to it's stored true appearance.
*
* NOTE: This will function will fail if any polymorph effect is present on the creature.
*
*
* @param oShifter The creature whose appearance to set into an appearance
* previously stored as it's true appearance.
*
* @return TRUE if appearance was restored, FALSE if not. Causes for failure
* are being polymorphed and not having a true appearance stored.
*/
int RestoreTrueAppearance(object oShifter);
// Storage functions //
/**
* Stores the target's resref in the 'shifting template's list of the given creature.
* Will silently fail if either the shifter or the target are not valid objects
* or if the target is a PC.
*
* @param oShifter The creature to whose list to store oTarget's resref in
* @param nShifterType SHIFTER_TYPE_* of the list to store in
* @param oTarget The creature whose resref to store for later use in shifting
*/
int StoreShiftingTemplate(object oShifter, int nShifterType, object oTarget);
/**
* Gets the number of 'template's stored in the given creature's list.
*
* @param oShifter The creature whose list to examine
* @param nShifterType SHIFTER_TYPE_* of the list to store examine
* @return The number of entries in the arrays making up the list
*/
int GetNumberOfStoredTemplates(object oShifter, int nShifterType);
/**
* Reads the resref stored at the given index at a creature's 'template's
* list.
*
* @param oShifter The creature from whose list to read
* @param nShifterType SHIFTER_TYPE_* of the list to read from
* @param nIndex The index of the entry to get in the list. Standard
* base-0 indexing.
* @return The resref stored at the given index. "" on failure (ex.
* reading from an index outside the list.
*/
string GetStoredTemplate(object oShifter, int nShifterType, int nIndex);
/**
* Reads the name stored at the given index at a creature's 'templates's
* list.
*
* @param oShifter The creature from whose list to read
* @param nShifterType SHIFTER_TYPE_* of the list to read from
* @param nIndex The index of the entry to get in the list. Standard
* base-0 indexing.
* @return The name stored at the given index. "" on failure (ex.
* reading from an index outside the list.
*/
string GetStoredTemplateName(object oShifter, int nShifterType, int nIndex);
/**
* Deletes the 'shifting template's entry in a creature's list at a given
* index.
*
* @param oShifter The creature from whose list to delete
* @param nShifterType SHIFTER_TYPE_* of the list to delete from
* @param nIndex The index of the entry to delete in the list. Standard
* base-0 indexing.
*/
void DeleteStoredTemplate(object oShifter, int nShifterType, int nIndex);
// Shifting-related functions
/**
* Determines whether the given creature can shift into the given target.
*
* @param oShifter The creature attempting to shift into oTemplate
* @param nShifterType SHIFTER_TYPE_*
* @param oTemplate The target of the shift
*
* @return TRUE if oShifter can shift into oTemplate, FALSE otherwise
*/
int GetCanShiftIntoCreature(object oShifter, int nShifterType, object oTemplate);
/**
* Attempts to shift into the given template creature. This functions as a wrapper
* for ShiftIntoResRef(), which is supplied with oTemplate's resref.
*
* @param oShifter The creature doing the shifting
* @param nShifterType SHIFTER_TYPE_*
* @param oTemplate The creature to shift into
* @param bGainSpellLikeAbilities Whether to give the shifter access the template's SLAs
*
* @return TRUE if the shifting started successfully,
* FALSE if it failed outright
*/
int ShiftIntoCreature(object oShifter, int nShifterType, object oTemplate, int bGainSpellLikeAbilities = FALSE);
/**
* Attempts to shift into the given template creature. If the shifter is already
* shifted, this will unshift them first. Any errors will result in a message
* being sent to the shifter.
*
* @param oShifter The creature doing the shifting
* @param nShifterType SHIFTER_TYPE_*
* @param sResRef Resref of the creature to shift into
* @param bGainSpellLikeAbilities Whether to give the shifter access the template's SLAs
*
* @return TRUE if the shifting started successfully,
* FALSE if it failed outright
*/
int ShiftIntoResRef(object oShifter, int nShifterType, string sResRef, int bGainSpellLikeAbilities = FALSE);
/**
* Undoes any currently active shifting, restoring original appearance &
* creature items.
* NOTE: Will fail if any of the following conditions are true
* - oShifter is polymorphed and bRemovePoly is false
* - SHIFTER_SHIFT_MUTEX flag is true on oShifter and bIgnoreShiftingMutex is false
* - There is no true form stored for oShifter
*
* @param oShifter The creature to unshift
* @param bRemovePoly Whether to also remove polymorph effects
* @param bIgnoreShiftingMutex Whether to ignore the value of SHIFTER_SHIFT_MUTEX
*
* @return One of following:
* - UNSHIFT_FAIL if one of the abovementioned failure conditions occurs.
* If this is returned, nothing is done to oShifter.
* - UNSHIFT_SUCCESS if the unshifting was completed immediately
* - UNSHIFT_SUCCESS_DELAYED if the unshifting is doable, but delayed to
* wait while a polymorph effect is being removed.
*/
int UnShift(object oShifter, int bRemovePoly = TRUE, int bIgnoreShiftingMutex = FALSE);
// Appearance data functions
/**
* Reads in all the data about the target creature's appearance and stores it in
* a structure that is then returned.
*
* @param oTemplate Creature whose appearance data to read
* @return An appearancevalues structure containing the data
*/
struct appearancevalues GetAppearanceData(object oTemplate);
/**
* Sets the given creature's appearance data to values in the given appearancevalues
* structure.
*
* @param oTarget The creauture whose appearance to modify
* @param appval The appearance data to apply to oTarget
*/
void SetAppearanceData(object oTarget, struct appearancevalues appval);
/**
* Retrieves an appearancevalues structure that has been placed in local variable
* storage.
*
* @param oStore The object on which the data has been stored
* @param sName The name of the local variable
* @return An appearancevalues structure containing the retrieved data
*/
struct appearancevalues GetLocalAppearancevalues(object oStore, string sName);
/**
* Stores an appearancevalues structure on the given object as local variables.
*
* @param oStore The object onto which to store the data
* @param sName The name of the local variable
* @param appval The data to store
*/
void SetLocalAppearancevalues(object oStore, string sName, struct appearancevalues appval);
/**
* Deletes an appearancevalues structure that has been stored on the given object
* as local variable.
*
* @param oStore The object from which to delete data
* @param sName The name of the local variable
*/
void DeleteLocalAppearancevalues(object oStore, string sName);
/**
* Persistant storage version of GetLocalAppearancevalues(). As normal for
* persistant storage, behaviour is not guaranteed in case the storage object is
* not a creature.
*
* @param oStore The object on which the data has been stored
* @param sName The name of the local variable
* @return An appearancevalues structure containing the retrieved data
*/
struct appearancevalues GetPersistantLocalAppearancevalues(object oStore, string sName);
/**
* Persistant storage version of GetLocalAppearancevalues(). As normal for
* persistant storage, behaviour is not guaranteed in case the storage object is
* not a creature.
*
* @param oStore The object onto which to store the data
* @param sName The name of the local variable
* @param appval The data to store
*/
void SetPersistantLocalAppearancevalues(object oStore, string sName, struct appearancevalues appval);
/**
* Persistant storage version of GetLocalAppearancevalues(). As normal for
* persistant storage, behaviour is not guaranteed in case the storage object is
* not a creature.
*
* @param oStore The object from which to delete data
* @param sName The name of the local variable
*/
void DeletePersistantLocalAppearancevalues(object oStore, string sName);
/**
* Forces an unshift if spell duration ends, for Alter Self, etc. If player
* unshifts before then, fuction will recognize it's a new shift and do nothing.
*
* @param oShifter The object to force an unshift if needed
* @param nShifterNumber a number to check against to make sure DelayCommand
* doesn't end the wrong shift
*/
void ForceUnshift(object oShifter, int nShiftedNumber);
/**
* Creates a string containing the values of the fields of the given appearancevalues
* structure.
*
* @param appval The appearancevalues structure to convert into a string
* @return A string that describes the contents of appval
*/
string DebugAppearancevalues2Str(struct appearancevalues appval);
//////////////////////////////////////////////////
/* Includes */
//////////////////////////////////////////////////
//#include "inc_utility"
//#include "prc_inc_switch"
#include "prc_inc_racial"
#include "prc_inc_function"
#include "prc_inc_onhit"
#include "prc_shifter_info"
#include "prc_weap_apt"
#include "prc_inc_wpnrest"
#include "inc_nwnx_funcs"
effect ePhysicalStats;
int Max_Bonus = GetPRCSwitch(PRC_PNP_SHIFTER_BONUS);
//////////////////////////////////////////////////
/* Internal functions */
//////////////////////////////////////////////////
void _prc_inc_shifting_EvalPRCFeats(object oShifter, object oShifterHide)
{
//There is what appears to be a Bioware bug where sometimes when certain item properties,
//most notably those for elemental immunities, are removed, they remain in effect and
//are still listed in the character sheet. Reequiping the creature hide
//seems to force NWN notice that the item property has disappeared,
//while calling ScrubPCSkin/EvalPRCFeats does not.
//Apparently it's not even necessary to unequip it first (which is good).
//This also causes EvalPRCFeats to be called, so we don't need to do that ourselves.
AssignCommand(oShifter, ActionEquipItem(oShifterHide, INVENTORY_SLOT_CARMOUR));
}
/** Internal function.
* Looks through the given creature's inventory and deletes all
* creature items not in the creature item slots.
*
* @param oShifter The creature through whose inventory to look
*/
void _prc_inc_shifting_RemoveExtraCreatureItems(object oShifter)
{
int nItemType;
object oItem = GetFirstItemInInventory(oShifter);
object oCWPB = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oShifter);
object oCWPL = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oShifter);
object oCWPR = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oShifter);
object oCSkin = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oShifter);
while(GetIsObjectValid(oItem))
{
nItemType = GetBaseItemType(oItem);
if(nItemType == BASE_ITEM_CBLUDGWEAPON ||
nItemType == BASE_ITEM_CPIERCWEAPON ||
nItemType == BASE_ITEM_CREATUREITEM ||
nItemType == BASE_ITEM_CSLASHWEAPON ||
nItemType == BASE_ITEM_CSLSHPRCWEAP
)
{
if(oItem != oCWPB &&
oItem != oCWPL &&
oItem != oCWPR &&
oItem != oCSkin
)
MyDestroyObject(oItem);
}
oItem = GetNextItemInInventory(oShifter);
}
}
/** Internal function.
* @todo Finish function & comments
*/
void _prc_inc_shifting_CopyAllItemProperties(object oFrom, object oTo)
{
itemproperty iProp = GetFirstItemProperty(oFrom);
while(GetIsItemPropertyValid(iProp))
{
if(GetItemPropertyDurationType(iProp) == DURATION_TYPE_PERMANENT)
IPSafeAddItemProperty(oTo, iProp, 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
iProp = GetNextItemProperty(oFrom);
}
}
/** Internal function.
* Builds the shifter spell-like and activatable supernatural abilities item.
*
* @param oTemplate The target creature of an ongoing shift
* @param oSLAItem The item to create the activatable itemproperties on.
*/
//NOTE: THIS FUNCTION HAS A LOT OF CODE IN COMMON WITH _prc_inc_shifting_PrintShifterActiveAbilities
void _prc_inc_shifting_CreateShifterActiveAbilitiesItem(object oShifter, object oTemplate, object oSLAItem)
{
object oTemplateHide = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTemplate);
itemproperty iProp = GetFirstItemProperty(oTemplateHide);
while(GetIsItemPropertyValid(iProp))
{
if(GetItemPropertyDurationType(iProp) == DURATION_TYPE_PERMANENT && GetItemPropertyType(iProp) == ITEM_PROPERTY_CAST_SPELL)
AddItemProperty(GetItemPropertyDurationType(iProp), iProp, oSLAItem);
iProp = GetNextItemProperty(oTemplateHide);
}
// Loop over shifter_abilitie.2da
string sNumUses;
int nSpell, nNumUses, nProps;
int i = 0;
while(nSpell = StringToInt(Get2DACache("shifter_abilitie", "Spell", i)))
{
// See if the template has this spell
if(GetHasSpell(nSpell, oTemplate))
{
// Determine the number of uses from the 2da
sNumUses = Get2DACache("shifter_abilitie", "IPCSpellNumUses", i);
if(sNumUses == "1_USE_PER_DAY")
nNumUses = IP_CONST_CASTSPELL_NUMUSES_1_USE_PER_DAY;
else if(sNumUses == "2_USES_PER_DAY")
nNumUses = IP_CONST_CASTSPELL_NUMUSES_2_USES_PER_DAY;
else if(sNumUses == "3_USES_PER_DAY")
nNumUses = IP_CONST_CASTSPELL_NUMUSES_3_USES_PER_DAY;
else if(sNumUses == "4_USES_PER_DAY")
nNumUses = IP_CONST_CASTSPELL_NUMUSES_4_USES_PER_DAY;
else if(sNumUses == "5_USES_PER_DAY")
nNumUses = IP_CONST_CASTSPELL_NUMUSES_5_USES_PER_DAY;
else if(sNumUses == "UNLIMITED_USE")
nNumUses = IP_CONST_CASTSPELL_NUMUSES_UNLIMITED_USE;
else{
if(DEBUG) DoDebug("prc_inc_shifting: _CreateShifterActiveAbilitiesItem(): Unknown IPCSpellNumUses in shifter_abilitie.2da line " + IntToString(i) + ": " + sNumUses);
nNumUses = -1;
}
// Create the itemproperty and add it to the item
iProp = ItemPropertyCastSpell(StringToInt(Get2DACache("shifter_abilitie", "IPSpell", i)), nNumUses);
AddItemProperty(DURATION_TYPE_PERMANENT, iProp, oSLAItem);
// Increment property counter
nProps += 1;
}
// Increment loop counter
i += 1;
}
}
/** Internal function.
* Adds bonus feats granting feats defined in shifter_feats.2da to the shifter's hide if
* the template has the given feat.
*
* @param oTemplate The target creature of an ongoing shift
* @param oShifterHide The shifter's hide object
*/
void _prc_inc_shifting_CopyFeats(object oShifter, object oTemplate, object oShifterHide, int nStartIndex, int nLimitIndex)
{
// Loop over shifter_feats.2da. Assume there are no more entries when
string sFeat;
int i = nStartIndex;
while((i < nLimitIndex) && (sFeat = Get2DACache("shifter_feats", "Feat", i)) != "")
{
if (_prc_inc_GetHasFeat(oTemplate, StringToInt(sFeat)))
{
IPSafeAddItemProperty(
oShifterHide,
ItemPropertyBonusFeat(StringToInt(Get2DACache("shifter_feats", "IPFeat", i))),
0.0,
X2_IP_ADDPROP_POLICY_KEEP_EXISTING
);
string sFeatName = GetStringByStrRef(StringToInt(Get2DACache("feat", "Feat", StringToInt(sFeat))));
}
i += 1;
}
}
void _prc_inc_shifting_AddCreatureWeaponFeats(object oShifter, object oShifterHide)
{
//If PC has unarmed feats, give them the corresponding creature feats when shifted
if (GetHasFeat(FEAT_WEAPON_FOCUS_UNARMED_STRIKE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_WeapFocCreature), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_WEAPON_SPECIALIZATION_UNARMED_STRIKE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_WeapSpecCreature), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_WEAPON_FOCUS_UNARMED, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_WeapEpicFocCreature), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_WEAPON_SPECIALIZATION_UNARMED, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_WeapEpicSpecCreature), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_IMPROVED_CRITICAL_UNARMED_STRIKE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_ImpCritCreature), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_OVERWHELMING_CRITICAL_UNARMED, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_OVERCRITICAL_CREATURE), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_DEVASTATING_CRITICAL_UNARMED, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_DEVCRITICAL_CREATURE), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
//If PC has creature feats, give them the corresponding unarmed feats when shifted
if (GetHasFeat(FEAT_WEAPON_FOCUS_CREATURE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_WEAPON_FOCUS_UNARMED_STRIKE), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_WEAPON_SPECIALIZATION_CREATURE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_WEAPON_SPECIALIZATION_UNARMED_STRIKE), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_WEAPON_FOCUS_CREATURE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_EPIC_WEAPON_FOCUS_UNARMED_STRIKE), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_WEAPON_SPECIALIZATION_CREATURE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_EPIC_WEAPON_SPECIALIZATION_UNARMED_STRIKE), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_IMPROVED_CRITICAL_CREATURE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_IMPROVED_CRITICAL_UNARMED), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_OVERWHELMING_CRITICAL_CREATURE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_EPIC_OVERWHELMING_CRITICAL_UNARMED), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
if (GetHasFeat(FEAT_EPIC_DEVASTATING_CRITICAL_CREATURE, oShifter))
IPSafeAddItemProperty(oShifterHide, ItemPropertyBonusFeat(IP_CONST_FEAT_EPIC_DEVASTATING_CRITICAL_UNARMED), 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
}
/** Internal function.
* Determines if the given resref has already been stored in the
* templates array of the given creature's shifting list for
* a particular shifting type.
*
* @param oShifter The creature
* @param nShifterType The shifting list to look in
* @param sResRef The resref to look for
* @return TRUE if the resref is present in the array
*/
int _prc_inc_shifting_GetIsTemplateStored(object oShifter, int nShifterType, string sResRef)
{
string sResRefsArray = SHIFTER_RESREFS_ARRAY + IntToString(nShifterType);
int i, nArraySize = persistant_array_get_size(oShifter, sResRefsArray);
// Lowercase the searched for string
sResRef = GetStringLowerCase(sResRef);
for(i = 0; i < nArraySize; i++)
{
if(sResRef == persistant_array_get_string(oShifter, sResRefsArray, i))
return i+1;
}
return 0;
}
/** Internal function.
* Performs some checks to see if the given creature can shift without
* the system falling apart.
*
* @param oShifter The creature that would be shifted
* @return TRUE if all is OK, FALSE otherwise
*/
int _prc_inc_shifting_GetCanShift(object oShifter)
{
// Mutex - If another shifting process is active, fail immediately without disturbing it
if(GetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX))
{
DelayCommand(SHIFTER_MUTEX_UNSET_DELAY, SetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX, FALSE)); //In case the mutex got stuck, unstick it
SendMessageToPCByStrRef(oShifter, STRREF_SHIFTING_MUTEX); // "Another PRC Polymorph transformation is underway at this moment. Please wait until it completes before trying again."
return FALSE;
}
// Test space in inventory for creating the creature items
int bReturn = TRUE;
object o1 = CreateItemOnObject("pnp_shft_tstpkup", oShifter),
o2 = CreateItemOnObject("pnp_shft_tstpkup", oShifter),
o3 = CreateItemOnObject("pnp_shft_tstpkup", oShifter);
if(!(GetItemPossessor(o1) == oShifter &&
GetItemPossessor(o2) == oShifter &&
GetItemPossessor(o3) == oShifter
))
{
bReturn = FALSE;
SendMessageToPCByStrRef(oShifter, STRREF_NEED_SPACE); // "Your inventory is too full for the PRC Shifting system to work. Please make space for three (3) helmet-size items (4x4) in your inventory before trying again."
}
DestroyObject(o1);
DestroyObject(o2);
DestroyObject(o3);
// Polymorph effect and shifting are mutually exclusive. Letting them stack
// is inviting massive fuckups.
if (IsPolymorphed(oShifter))
bReturn = FALSE;
// True form must be stored in order to be allowed to shift
if(!GetPersistantLocalInt(oShifter, SHIFTER_TRUEAPPEARANCE))
bReturn = FALSE;
return bReturn;
}
//Function by The_Krit from http://nwn.bioware.com/forums/viewtopic.html?topic=601171&forum=47
//TODO: This still doesn't bypass spell effects that make the PC immune to ability drain (e.g. "Negative energy protection" spell)
void _prc_inc_shifting_BypassItemProperties(object oCreature, int nSubType, int nType = ITEM_PROPERTY_IMMUNITY_MISCELLANEOUS)
{
object oItem;
itemproperty ipBypass;
// Loop through oCreature's inventory slots.
int nSlot = NUM_INVENTORY_SLOTS;
while ( nSlot-- > 0 )
{
// Get the item in this slot.
oItem = GetItemInSlot(nSlot, oCreature);
if ( oItem != OBJECT_INVALID )
{
// Loop through oItem's item properties.
ipBypass = GetFirstItemProperty(oItem);
while ( GetIsItemPropertyValid(ipBypass) )
{
// See if ipBypass is what we want to bypass.
if ( GetItemPropertyType(ipBypass) == nType &&
GetItemPropertySubType(ipBypass) == nSubType &&
GetItemPropertyDurationType(ipBypass) == DURATION_TYPE_PERMANENT )
{
// Remove ipBypass for a split second.
RemoveItemProperty(oItem, ipBypass);
DelayCommand(0.1, AddItemProperty(DURATION_TYPE_PERMANENT, ipBypass, oItem));
}
// Next item property.
ipBypass = GetNextItemProperty(oItem);
}//while (ipBypass)
}//if (oItem)
}//while (nSlot)
}
void _prc_inc_shifting_ApplyStatPenalties(object oCreature, object oPropertyHolder, int nDeltaSTR, int nDeltaDEX, int nDeltaCON)
{
//The immunity properties removed by _prc_inc_shifting_ApplyStatPenalties aren't actually removed until this script
//finishes running, so we delay adding the penalties until after that happens. Re-adding the removed immunity properties
//is delayed even longer (schedulded by _prc_inc_shifting_BypassItemProperties) so that it happens after the penalties have been applied.
_prc_inc_shifting_BypassItemProperties(oCreature, IP_CONST_IMMUNITYMISC_LEVEL_ABIL_DRAIN);
if(nDeltaSTR < 0)
DelayCommand(0.0, SetCompositeBonus(oPropertyHolder, "Shifting_AbilityAdjustmentSTRPenalty", -nDeltaSTR, ITEM_PROPERTY_DECREASED_ABILITY_SCORE, IP_CONST_ABILITY_STR));
if(nDeltaDEX < 0)
DelayCommand(0.0, SetCompositeBonus(oPropertyHolder, "Shifting_AbilityAdjustmentDEXPenalty", -nDeltaDEX, ITEM_PROPERTY_DECREASED_ABILITY_SCORE, IP_CONST_ABILITY_DEX));
if(nDeltaCON < 0)
DelayCommand(0.0, SetCompositeBonus(oPropertyHolder, "Shifting_AbilityAdjustmentCONPenalty", -nDeltaCON, ITEM_PROPERTY_DECREASED_ABILITY_SCORE, IP_CONST_ABILITY_CON));
}
//TODO: use ForceEquip() in inc_utility instead?
void SafeEquipItem(object oShifter, object oItem, int nSlot, float fDelay = 1.0f)
{
if (GetIsObjectValid(oItem) && GetItemInSlot(nSlot, oShifter) != oItem)
{
AssignCommand(oShifter, ActionEquipItem(oItem, nSlot));
if (fDelay < 9.0f) //Don't repeat forever
DelayCommand(fDelay, SafeEquipItem(oShifter, oItem, nSlot, fDelay * 2)); //Try increasing delays until it works
}
}
void _prc_inc_shifting_ApplyEffects(object oShifter, int bShifting)
{
if (GetLocalInt(oShifter, "PRC_Shifter_AffectsToApply"))
{
if(bShifting)
{
SetLocalInt(oShifter, "PRC_SHIFTER_APPLY_ALL_SPELL_EFFECTS", TRUE);
//The only place this is ever removed is by the spell script after it is used.
//This prevents race conditions where this function is called again
//and changes the value to FALSE before the spell script had a chance to use it.
}
if (GetLocalInt(oShifter, "PRC_Shifter_Use_RodOfWonder"))
{
if (DEBUG_EFFECTS || DEBUG)
DoDebug("ADDING EFFECTS: ROD OF WONDER");
object oCastingObject = CreateObject(OBJECT_TYPE_PLACEABLE, "x0_rodwonder", GetLocation(oShifter));
AssignCommand(oCastingObject, ActionCastSpellAtObject(SPELL_SHIFTING_EFFECTS, oShifter, METAMAGIC_NONE, TRUE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE));
//Handled by spell code: DestroyObject(oCastingObject, 6.0);
}
else
{
if (DEBUG_EFFECTS || DEBUG)
DoDebug("ADDING EFFECTS: ALTERNATE");
CastSpellAtObject(SPELL_SHIFTING_EFFECTS, oShifter, METAMAGIC_NONE, 0, 0, 0, OBJECT_INVALID, oShifter);
}
}
}
void _prc_inc_shifting_RemoveSpellEffects_RodOfWonder(object oShifter, int bApplyAll)
{
effect eTest = GetFirstEffect(oShifter);
while(GetIsEffectValid(eTest))
{
if(GetEffectSpellId(eTest) == SPELL_SHIFTING_EFFECTS)
{
if (GetEffectType(eTest) == EFFECT_TYPE_TEMPORARY_HITPOINTS)
{
int nHPBonus = GetLocalInt(oShifter, "PRC_Shifter_ExtraCON_HPBonus");
if (bApplyAll || !nHPBonus)
{
if(DEBUG_EFFECTS || DEBUG)
DoDebug("Removing temp HP: " + IntToString(bApplyAll) + ", " + IntToString(nHPBonus));
RemoveEffect(oShifter, eTest);
}
else if(DEBUG_EFFECTS || DEBUG)
DoDebug("Skipped removing temp HP" + IntToString(bApplyAll) + ", " + IntToString(nHPBonus));
}
else if (GetEffectType(eTest) == EFFECT_TYPE_INVISIBILITY)
{
int bHarmlessInvisible = GetLocalInt(oShifter, "PRC_Shifter_HarmlessInvisible");
if (bApplyAll || !bHarmlessInvisible)
{
if(DEBUG_EFFECTS || DEBUG)
DoDebug("Removing invisibility" + IntToString(bApplyAll) + ", " + IntToString(bHarmlessInvisible));
RemoveEffect(oShifter, eTest);
}
else if(DEBUG_EFFECTS || DEBUG)
DoDebug("Skipped removing invisibility" + IntToString(bApplyAll) + ", " + IntToString(bHarmlessInvisible));
}
else
RemoveEffect(oShifter, eTest);
}
eTest = GetNextEffect(oShifter);
}
}
void _prc_inc_shifting_RemoveSpellEffects_CastSpellAtObject(object oShifter, int bApplyAll)
{
effect eTest = GetFirstEffect(oShifter);
while(GetIsEffectValid(eTest))
{
int nSpellId = GetEffectSpellId(eTest);
//TODO: Is THERE ANY WAY TO SET THE SPELL ID CORRECTLY INSTEAD OF IT BEING -1?
//EvalPRCFeats seems to call scripts using the ExecuteScript function and the spell id is set correctly. How does this happen?
if(nSpellId == SPELL_SHIFTING_EFFECTS || nSpellId == -1)
{
switch(GetEffectType(eTest))
{
case EFFECT_TYPE_ATTACK_INCREASE:
case EFFECT_TYPE_ATTACK_DECREASE:
case EFFECT_TYPE_DAMAGE_INCREASE:
case EFFECT_TYPE_DAMAGE_DECREASE:
case EFFECT_TYPE_SAVING_THROW_INCREASE:
case EFFECT_TYPE_SAVING_THROW_DECREASE:
case EFFECT_TYPE_SKILL_INCREASE:
case EFFECT_TYPE_SKILL_DECREASE:
case EFFECT_TYPE_AC_INCREASE:
case EFFECT_TYPE_AC_DECREASE:
RemoveEffect(oShifter, eTest);
break;
case EFFECT_TYPE_TEMPORARY_HITPOINTS:
{
int nHPBonus = GetLocalInt(oShifter, "PRC_Shifter_ExtraCON_HPBonus");
if (bApplyAll || !nHPBonus)
RemoveEffect(oShifter, eTest);
else if(DEBUG_EFFECTS || DEBUG)
DoDebug("Skipped removing temp HP" + IntToString(bApplyAll) + ", " + IntToString(nHPBonus));
break;
}
case EFFECT_TYPE_INVISIBILITY:
{
int bHarmlessInvisible = GetLocalInt(oShifter, "PRC_Shifter_HarmlessInvisible");
if (bApplyAll || !bHarmlessInvisible)
RemoveEffect(oShifter, eTest);
else if(DEBUG_EFFECTS || DEBUG)
DoDebug("Skipped removing invisibility" + IntToString(bApplyAll) + ", " + IntToString(bHarmlessInvisible));
break;
}
}
}
eTest = GetNextEffect(oShifter);
}
if(GetLocalInt(oShifter, "ReserveFeatsRunning"))
DelayCommand(0.1f, ExecuteScript("prc_reservefeat", oShifter));
}
void _prc_inc_shifting_RemoveSpellEffects(object oShifter, int bApplyAll)
{
if (GetLocalInt(oShifter, "PRC_Shifter_Use_RodOfWonder"))
{
if(DEBUG_EFFECTS || DEBUG)
DoDebug("REMOVING EFFECTS: ROD OF WONDER");
if(!GetLocalInt(oShifter, "PRC_Shifter_Using_RodOfWonder"))
{
//Remove effects of other approach if just switching to this one.
//This may remove too many effects, but shifting should force the effects
//that should not have been removed to be re-added, and they won't be
//removed again.
SetLocalInt(oShifter, "PRC_Shifter_Using_RodOfWonder", TRUE);
_prc_inc_shifting_RemoveSpellEffects_CastSpellAtObject(oShifter, bApplyAll);
}
_prc_inc_shifting_RemoveSpellEffects_RodOfWonder(oShifter, bApplyAll);
}
else
{
/*
This method has advantages and disadvantages compared to the Rod of Wonder method.
Advantages:
* It's instantaneous; if the system is busy (e.g., in a really difficult fight)
the Rod of Wonder sometimes takes 10-15 seconds, which can literally kill you
Disadvantages:
* It can't tell which effects it added, so it sometimes has to remove too many
(e.g., it might perhaps remove race-related AC bonus, etc.)
//TODO: if I can come up with a better way to mark or detect which effects were added here, this can be fixed
//Check GetEffectCreator? Tie the effect to a unique object so we can identify it?
Even with the disadvantages, for the builds I've used I find that the advantages make this method preferable.
However, for some builds or environments the Rod of Wonder might still be preferable, so provide the user with an option
by means of the PRC_Shifter_Use_RodOfWonder variable.
*/
if(DEBUG_EFFECTS || DEBUG)
DoDebug("REMOVING EFFECTS: ALTERNATE");
_prc_inc_shifting_RemoveSpellEffects_CastSpellAtObject(oShifter, bApplyAll);
}
}
void _prc_inc_shifting_DeleteEffectInts(object oShifter)
{
DeleteLocalInt(oShifter, "PRC_SHIFTER_EXTRA_STR_WEAPON_MAINHAND");
DeleteLocalInt(oShifter, "PRC_SHIFTER_EXTRA_STR_WEAPON_OFFHAND");
DeleteLocalInt(oShifter, "PRC_SHIFTER_EXTRA_DEX_ARMOR");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraSTR");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraSTR_Feats");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraSTR_AttackBonus");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraSTR_DamageBonus");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraSTR_DamageType");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraSTR_SaveAndSkillBonus");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraDEX");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraDEX_Feats");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraDEX_ACBonus");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraDEX_SaveAndSkillBonus");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraCON");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraCON_Feats");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraCON_HPBonus");
DeleteLocalInt(oShifter, "PRC_Shifter_ExtraCON_SaveAndSkillBonus");
DeleteLocalInt(oShifter, "PRC_Shifter_NaturalAC");
DeleteLocalInt(oShifter, SHIFTER_RESTRICT_SPELLS);
DeleteLocalInt(oShifter, "PRC_Shifter_HarmlessInvisible");
DeleteLocalInt(oShifter, SHIFTER_OVERRIDE_RACE);
int nShiftingTrueRace = GetPersistantLocalInt(oShifter, SHIFTER_TRUE_RACE);
if(nShiftingTrueRace)
PRC_Funcs_SetRace(oShifter, nShiftingTrueRace - 1); //Offset by 1 to differentiate value 0 from non-existence
DeleteLocalInt(oShifter, "PRC_Shifter_AffectsToApply");
}
int _prc_inc_shifting_HasExtraFeat(object oPC, string sRemovedFeatList, object oTemplate, int nFeat, int nIPFeat)
{
if (nFeat == -1 || nIPFeat == -1)
return FALSE;
int bPCHasFeat = GetHasFeat(nFeat, oPC);
string sIPFeat = "("+IntToString(nIPFeat)+")";
int bDeleted = (FindSubString(sRemovedFeatList, sIPFeat) != -1);
int bTemplateHasFeat = GetHasFeat(nFeat, oTemplate);
return (bPCHasFeat && !bDeleted) || bTemplateHasFeat;
}
void _prc_inc_shifting_AddExtraFeat(object oPC, string sRemovedFeatList, object oPropertyHolder, int nIPFeat, string sFeatTrackingVariable)
{
string sIPFeat = "("+IntToString(nIPFeat)+")";
itemproperty iProp = ItemPropertyBonusFeat(nIPFeat);
if (FindSubString(sRemovedFeatList, sIPFeat) != -1) //TODO: skip this check and always use the delay?
{
//An item property for the same feat was present before and was removed;
//but removed item properties aren't actually removed until the script
//ends. However, this means that the new one we add here is removed.
//So, delay adding it until after the removal takes place.
DelayCommand(0.0f, IPSafeAddItemProperty(oPropertyHolder, iProp, 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING));
}
else
IPSafeAddItemProperty(oPropertyHolder, iProp, 0.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING);
SetLocalString(oPC, sFeatTrackingVariable, GetLocalString(oPC, sFeatTrackingVariable) + sIPFeat);
}
int _prc_inc_shifting_TryAddExtraFeat(object oPC, string sRemovedFeatList, object oTemplate, object oPropertyHolder, int nFeat, int nIPFeat, string sFeatTrackingVariable)
{
int nAddedCount = 0;
if (!_prc_inc_shifting_HasExtraFeat(oPC, sRemovedFeatList, oTemplate, nFeat, nIPFeat))
{
_prc_inc_shifting_AddExtraFeat(oPC, sRemovedFeatList, oPropertyHolder, nIPFeat, sFeatTrackingVariable);
nAddedCount = 1;
}
return nAddedCount;
}
int _prc_inc_shifting_TryAddExtraFeats(object oPC, string sRemovedFeatList, object oTemplate, object oPropertyHolder, int nFeatStart, int nFeatEnd, int nIPFeatStart, int nCount, string sFeatTrackingVariable)
{
int nAddedCount = 0;
int nIPFeat = -1;
int nFeat;
//Find which feats should be added
for (nFeat = nFeatStart; nAddedCount < nCount && nFeat <= nFeatEnd; nFeat++)
{
nIPFeat = nIPFeatStart + (nFeat - nFeatStart);
if (!_prc_inc_shifting_HasExtraFeat(oPC, sRemovedFeatList, oTemplate, nFeat, nIPFeat))
nAddedCount += 1;
}
//Only need to add the last one, since they're cumulative
if (nAddedCount)
_prc_inc_shifting_AddExtraFeat(oPC, sRemovedFeatList, oPropertyHolder, nIPFeat, sFeatTrackingVariable);
return nAddedCount;
}
/*
void _prc_inc_shifting_SetSTR(object oShifter, int nSTR)
{
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_STRENGTH, nSTR);
//NWNX does not take into account any racial ability bonus/penalty. For instance, if the race has a
//+2 STR bonus, and we try to set STR to 10, it will instead set to 12. Handle that here.
int nErrorSTR = nSTR - GetAbilityScore(oShifter, ABILITY_STRENGTH, TRUE);
if (nErrorSTR)
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_STRENGTH, nSTR + nErrorSTR);
}
void _prc_inc_shifting_SetDEX(object oShifter, int nDEX)
{
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_DEXTERITY, nDEX);
//NWNX does not take into account any racial ability bonus/penalty. For instance, if the race has a
//+2 DEX bonus, and we try to set DEX to 10, it will instead set to 12. Handle that here.
int nErrorDEX = nDEX - GetAbilityScore(oShifter, ABILITY_DEXTERITY, TRUE);
if (nErrorDEX)
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_DEXTERITY, nDEX + nErrorDEX);
}
void _prc_inc_shifting_SetCON(object oShifter, int nCON)
{
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_CONSTITUTION, nCON);
//NWNX does not take into account any racial ability bonus/penalty. For instance, if the race has a
//+2 CON bonus, and we try to set CON to 10, it will instead set to 12. Handle that here.
int nErrorCON = nCON - GetAbilityScore(oShifter, ABILITY_CONSTITUTION, TRUE);
if (nErrorCON)
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_CONSTITUTION, nCON + nErrorCON);
}
void _prc_inc_shifting_SetINT(object oShifter, int nINT)
{
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_INTELLIGENCE, nINT);
//NWNX does not take into account any racial ability bonus/penalty. For instance, if the race has a
//+2 INT bonus, and we try to set INT to 10, it will instead set to 12. Handle that here.
int nErrorINT = nINT - GetAbilityScore(oShifter, ABILITY_INTELLIGENCE, TRUE);
if (nErrorINT)
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_INTELLIGENCE, nINT + nErrorINT);
}
void _prc_inc_shifting_SetWIS(object oShifter, int nWIS)
{
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_WISDOM, nWIS);
//NWNX does not take into account any racial ability bonus/penalty. For instance, if the race has a
//+2 WIS bonus, and we try to set WIS to 10, it will instead set to 12. Handle that here.
int nErrorWIS = nWIS - GetAbilityScore(oShifter, ABILITY_WISDOM, TRUE);
if (nErrorWIS)
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_WISDOM, nWIS + nErrorWIS);
}
void _prc_inc_shifting_SetCHA(object oShifter, int nCHA)
{
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_CHARISMA, nCHA);
//NWNX does not take into account any racial ability bonus/penalty. For instance, if the race has a
//+2 CHA bonus, and we try to set CHA to 10, it will instead set to 12. Handle that here.
int nErrorCHA = nCHA - GetAbilityScore(oShifter, ABILITY_CHARISMA, TRUE);
if (nErrorCHA)
PRC_Funcs_SetAbilityScore(oShifter, ABILITY_CHARISMA, nCHA + nErrorCHA);
}
*/
void _prc_inc_shifting_ApplyTemplate(object oShifter, int nIndex, int nShifterType, object oTemplate, int bShifting, object oShifterPropertyHolder1=OBJECT_INVALID, object oShifterPropertyHolder2=OBJECT_INVALID)
{
int nShapeGeneration = GetLocalInt(oShifter, PRC_Shifter_ShapeGeneration);
if (nShapeGeneration != nIndex)
{
//Don't apply properties that were scheduled when we were in another shape
if (DEBUG_APPLY_PROPERTIES)
DoDebug("_prc_inc_shifting_ApplyTemplate, exiting--old shape, Shifter Index: " + IntToString(nShapeGeneration));
return;
}
if(!GetIsObjectValid(oShifterPropertyHolder1))
{
//Put some properties on skin because they won't work otherwise--
//e.g., OnHit properties that should fire when the Shifter is hit by
//an enemy don't fire if they're not on the hide.
//Also, for some reason stat penalty item properties simply won't
//apply on creature weapons but work correctly on the skin.
//All properties applied to the skin need to be reapplied
//whenever the skin is scrubbed.
oShifterPropertyHolder1 = GetPCSkin(oShifter);
}
int bSkinScrubbed = !GetLocalInt(oShifterPropertyHolder1, "PRC_SHIFTER_TEMPLATE_APPLIED");
//TODO: Use PRC_ScrubPCSkin_Generation instead?
SetLocalInt(oShifterPropertyHolder1, "PRC_SHIFTER_TEMPLATE_APPLIED", TRUE); //This gets cleared by DeletePRCLocalInts (after sleeping, etc.).
int bApplyProperties1 = bSkinScrubbed;
if(!GetIsObjectValid(oShifterPropertyHolder2))
{
object oShifterCWpR = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oShifter);
object oShifterCWpL = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oShifter);
object oShifterCWpB = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oShifter);
//Put some properties on a creature weapon, because having them removed and re-added causes issues
//(e.g. having CON increase removed when your HP is low enough can kill you).
//These properties never need to be reapplied because the creature weapons are never scrubbed.
oShifterPropertyHolder2 = OBJECT_INVALID;
if(GetIsObjectValid(oShifterCWpR))
oShifterPropertyHolder2 = oShifterCWpR;
if(GetIsObjectValid(oShifterCWpL))
oShifterPropertyHolder2 = oShifterCWpL;
if(GetIsObjectValid(oShifterCWpB))
oShifterPropertyHolder2 = oShifterCWpB;
}
int bApplyProperties2 = bShifting && GetIsObjectValid(oShifterPropertyHolder2);
object oTemplateHide = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTemplate);
int bNeedSpellCast = FALSE; //Indicates whether there are any effects that require the spell to apply them
if(nShifterType != SHIFTER_TYPE_CHANGESHAPE &&
nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE)
{
// Copy all itemproperties from the source's hide. No need to check for validity of oTemplateHide - it not
// existing works the same as it existing, but having no iprops.
if(bApplyProperties1)
_prc_inc_shifting_CopyAllItemProperties(oTemplateHide, oShifterPropertyHolder1);
}
_prc_inc_shifting_DeleteEffectInts(oShifter);
// Ability score adjustments - doesn't apply to Change Shape
if(nShifterType != SHIFTER_TYPE_CHANGESHAPE &&
nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE &&
nShifterType != SHIFTER_TYPE_ALTER_SELF &&
nShifterType != SHIFTER_TYPE_DISGUISE_SELF &&
nShifterType != SHIFTER_TYPE_ARANEA)
{
struct _prc_inc_ability_info_struct rInfoStruct = _prc_inc_shifter_GetAbilityInfo(oTemplate, oShifter);
if (DEBUG_ABILITY_BOOST_CALCULATIONS || DEBUG)
{
DoDebug("Template Creature STR/DEX/CON: " + IntToString(rInfoStruct.nTemplateSTR) + "/" + IntToString(rInfoStruct.nTemplateDEX) + "/" + IntToString(rInfoStruct.nTemplateCON));
DoDebug("Shifter STR/DEX/CON: " + IntToString(rInfoStruct.nShifterSTR) + "/" + IntToString(rInfoStruct.nShifterDEX) + "/" + IntToString(rInfoStruct.nShifterCON));
DoDebug("Delta STR/DEX/CON: " + IntToString(rInfoStruct.nDeltaSTR) + "/" + IntToString(rInfoStruct.nDeltaDEX) + "/" + IntToString(rInfoStruct.nDeltaCON));
DoDebug("Item STR/DEX/CON: " + IntToString(rInfoStruct.nItemSTR) + "/" + IntToString(rInfoStruct.nItemDEX) + "/" + IntToString(rInfoStruct.nItemCON));
DoDebug("Item Delta STR/DEX/CON: " + IntToString(rInfoStruct.nItemDeltaSTR) + "/" + IntToString(rInfoStruct.nItemDeltaDEX) + "/" + IntToString(rInfoStruct.nItemDeltaCON));
DoDebug("Extra STR/DEX/CON: " + IntToString(rInfoStruct.nExtraSTR) + "/" + IntToString(rInfoStruct.nExtraDEX) + "/" + IntToString(rInfoStruct.nExtraCON));
}
// Set the ability score adjustments as composite bonuses
{
if(bApplyProperties2 || bApplyProperties1)
{
//set ability bonus in engine to allow for bonuses greater than 12 (default engine limit)
//SetAbilityBonusLimit(127);
SetAbilityBonusLimit (GetPRCSwitch(PRC_PNP_SHIFTER_BONUS));
//clear any existing ability boosts
effect eEffect = GetFirstEffect(oShifter);
while(GetIsEffectValid(eEffect))
{
if(GetEffectTag(eEffect) == "ShifterAbilities")
RemoveEffect(oShifter,eEffect);
eEffect = GetNextEffect(oShifter);
}
//New upper limit checks based on the max bonus of +127 to ability scores, also checking against PRC switch -Barmlot
if (rInfoStruct.nDeltaSTR > 127)
rInfoStruct.nDeltaSTR = 127;
if (rInfoStruct.nDeltaSTR > Max_Bonus)
rInfoStruct.nDeltaSTR = Max_Bonus;
if (rInfoStruct.nDeltaDEX > 127)
rInfoStruct.nDeltaDEX = 127;
if (rInfoStruct.nDeltaDEX > Max_Bonus)
rInfoStruct.nDeltaDEX = Max_Bonus;
if (rInfoStruct.nDeltaCON > 127)
rInfoStruct.nDeltaCON = 127;
if (rInfoStruct.nDeltaCON > Max_Bonus)
rInfoStruct.nDeltaCON = Max_Bonus;
effect eNewStr;
effect eNewDex;
effect eNewCon;
//Not sure this positive/negative check is necessary as EffectAbilityIncrease seems to take a signed integer, leaving as is -Barmlot
if (rInfoStruct.nDeltaSTR > 0)
eNewStr = EffectAbilityIncrease(IP_CONST_ABILITY_STR, rInfoStruct.nDeltaSTR);
else
eNewStr = EffectAbilityDecrease(IP_CONST_ABILITY_STR, rInfoStruct.nDeltaSTR);
if (rInfoStruct.nDeltaDEX > 0)
eNewDex = EffectAbilityIncrease(IP_CONST_ABILITY_DEX, rInfoStruct.nDeltaDEX);
else
eNewDex = EffectAbilityDecrease(IP_CONST_ABILITY_DEX, rInfoStruct.nDeltaDEX);
if (rInfoStruct.nDeltaCON > 0)
eNewCon = EffectAbilityIncrease(IP_CONST_ABILITY_CON, rInfoStruct.nDeltaCON);
else
eNewCon = EffectAbilityDecrease(IP_CONST_ABILITY_CON, rInfoStruct.nDeltaCON);
ePhysicalStats = EffectLinkEffects(eNewStr, eNewDex);
ePhysicalStats = EffectLinkEffects(ePhysicalStats, eNewCon);
ePhysicalStats = TagEffect(ePhysicalStats, "ShifterAbilities");
if (GetLevelByClass(CLASS_TYPE_PNP_SHIFTER, oShifter) > 5)
ePhysicalStats = SupernaturalEffect(ePhysicalStats);
ApplyEffectToObject(DURATION_TYPE_PERMANENT,ePhysicalStats,oShifter);
}
}
}
// Approximately figure out the template's natural AC bonus
if (nShifterType != SHIFTER_TYPE_CHANGESHAPE &&
nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE &&
nShifterType != SHIFTER_TYPE_DISGUISE_SELF &&
nShifterType != SHIFTER_TYPE_ARANEA)
{
int nNaturalAC = _prc_inc_CreatureNaturalAC(oTemplate);
if(nNaturalAC > 0)
{
bNeedSpellCast = TRUE;
SetLocalInt(oShifter, "PRC_Shifter_NaturalAC", nNaturalAC);
}
}
// Feats - read from shifter_feats.2da, check if template has it and copy over if it does
// Delayed, since this takes way too long
if(bApplyProperties1)
{
if(nShifterType != SHIFTER_TYPE_CHANGESHAPE
&& nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE
&& nShifterType != SHIFTER_TYPE_ALTER_SELF
&& nShifterType != SHIFTER_TYPE_DISGUISE_SELF
&& nShifterType != SHIFTER_TYPE_ARANEA)
{
//Copy feats in chunks because shapes with *many* feats were giving TMI errors
string sFeat;
int CHUNK_SIZE = 25; //50 was too big, so use 25
int i = 0;
while((sFeat = Get2DACache("shifter_feats", "Feat", i)) != "")
{
DelayCommand(0.0f, _prc_inc_shifting_CopyFeats(oShifter, oTemplate, oShifterPropertyHolder1, i, i+CHUNK_SIZE));
i += CHUNK_SIZE;
}
}
DelayCommand(0.1f, _prc_inc_shifting_AddCreatureWeaponFeats(oShifter, oShifterPropertyHolder1));
DelayCommand(1.0f, DoWeaponsEquip(oShifter)); //Since our weapon proficiency feats may have changed, reapply weapon feat simulations
//TODO: Handle armor also?
//TODO: Should actually unequip weapon if incorrect size, armor if not proficient (but this might be a pain).
}
// Casting restrictions if our - inaccurate - check indicates the template can't cast spells
if(!_prc_inc_shifting_GetCanFormCast(oTemplate) && !GetHasFeat(FEAT_PRESTIGE_SHIFTER_NATURALSPELL, oShifter))
SetLocalInt(oShifter, SHIFTER_RESTRICT_SPELLS, TRUE);
// Harmless stuff gets invisibility
if(_prc_inc_shifting_GetIsCreatureHarmless(oTemplate))
{
bNeedSpellCast = TRUE;
SetLocalInt(oShifter, "PRC_Shifter_HarmlessInvisible", TRUE);
}
//Override racial type.
//Change shape doesn't include this, but there is a feat that gives it to Changelings
if((nShifterType != SHIFTER_TYPE_CHANGESHAPE
&& nShifterType != SHIFTER_TYPE_SHIFTER
&& nShifterType != SHIFTER_TYPE_POLYMORPH
&& nShifterType != SHIFTER_TYPE_DRUID
&& nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE
&& nShifterType != SHIFTER_TYPE_ALTER_SELF
&& nShifterType != SHIFTER_TYPE_DISGUISE_SELF
&& nShifterType != SHIFTER_TYPE_ARANEA)
|| GetHasFeat(FEAT_RACIAL_EMULATION))
{
int nRace = MyPRCGetRacialType(oTemplate);
SetLocalInt(oShifter, SHIFTER_OVERRIDE_RACE, nRace + 1); //Offset by 1 to differentiate value 0 from non-existence
if(GetPersistantLocalInt(oShifter, SHIFTER_TRUE_RACE)) //Only change race if we can get back again when we unshift!
PRC_Funcs_SetRace(oShifter, nRace);
}
// If something needs permanent effects applied, create a placeable to do the casting in order to bind the effects to a spellID
if(bNeedSpellCast)
{
SetLocalInt(oShifter, PRC_Shifter_ApplyEffects_EvalPRC_Generation, GetLocalInt(oShifter, PRC_EvalPRCFeats_Generation));
SetLocalInt(oShifter, "PRC_Shifter_AffectsToApply", TRUE);
_prc_inc_shifting_ApplyEffects(oShifter, bShifting);
}
else
{
DeleteLocalInt(oShifter, "PRC_SHIFTER_APPLY_ALL_SPELL_EFFECTS");
_prc_inc_shifting_RemoveSpellEffects(oShifter, TRUE);
}
}
/** Internal function.
* Implements the actual shifting bit. Copies creature items, changes appearance, etc
*
* @param oShifter The creature shifting
* @param nShifterType SHIFTER_TYPE_*
* @param oTemplate The template creature
* @param bGainSpellLikeAbilities Whether to create the SLA item
*/
void _prc_inc_shifting_ShiftIntoTemplateAux(object oShifter, int nShifterType, object oTemplate, int bGainSpellLikeAbilities)
{
if(DEBUG) DoDebug("prc_inc_shifting: _ShiftIntoResRefAux():\n"
+ "oShifter = " + DebugObject2Str(oShifter) + "\n"
+ "nShifterType = " + IntToString(nShifterType) + "\n"
+ "oTemplate = " + DebugObject2Str(oTemplate) + "\n"
+ "bGainSpellLikeAbilities = " + DebugBool2String(bGainSpellLikeAbilities) + "\n"
);
string sFormName = GetName(oTemplate);
int nRequiredShifterLevel = _prc_inc_shifting_ShifterLevelRequirement(oTemplate);
int nRequiredCharacterLevel = _prc_inc_shifting_CharacterLevelRequirement(oTemplate);
float fChallengeRating = GetChallengeRating(oTemplate);
// Make sure the template creature is still valid
if(!GetIsObjectValid(oTemplate) || GetObjectType(oTemplate) != OBJECT_TYPE_CREATURE)
{
if(DEBUG) DoDebug("prc_inc_shifting: _ShiftIntoTemplateAux(): ERROR: oTemplate is not a valid object or not a creature: " + DebugObject2Str(oTemplate));
/// @todo Write a better error message
SendMessageToPCByStrRef(oShifter, STRREF_TEMPLATE_FAILURE); // "Polymorph failed: Failed to create a template of the creature to polymorph into."
// On failure, unset the mutex right away
SetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX, FALSE);
}
else
{
// Queue unsetting the mutex. Done here so that even if something breaks along the way, this has a good chance of getting executed
DelayCommand(SHIFTER_MUTEX_UNSET_DELAY, SetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX, FALSE));
/* Start the actual shifting */
// First, clear the shifter's action queue. We'll be assigning a bunch of commands that should get executed ASAP
AssignCommand(oShifter, ClearAllActions(TRUE));
// Get the shifter's creature items
object oShifterHide = GetPCSkin(oShifter); // Use the PRC wrapper for this to make sure we get the right object
object oShifterCWpR = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oShifter);
object oShifterCWpL = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oShifter);
object oShifterCWpB = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oShifter);
// Get the template's creature items
object oTemplateHide = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTemplate);
object oTemplateCWpR = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oTemplate);
object oTemplateCWpL = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oTemplate);
object oTemplateCWpB = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oTemplate);
//Changelings don't get the natural attacks
object oCreatureWeapon = OBJECT_INVALID;
if(nShifterType != SHIFTER_TYPE_DISGUISE_SELF)
{
// Handle creature weapons - replace any old weapons with new
// Delete old natural weapons
if(GetIsObjectValid(oShifterCWpR)) MyDestroyObject(oShifterCWpR);
if(GetIsObjectValid(oShifterCWpL)) MyDestroyObject(oShifterCWpL);
if(GetIsObjectValid(oShifterCWpB)) MyDestroyObject(oShifterCWpB);
oShifterCWpR = oShifterCWpL = oShifterCWpR = OBJECT_INVALID;
// Copy the template's weapons and assign equipping
if(GetIsObjectValid(oTemplateCWpR))
{
oShifterCWpR = CopyItem(oTemplateCWpR, oShifter, TRUE);
oCreatureWeapon = oShifterCWpR;
SetIdentified(oShifterCWpR, TRUE);
SafeEquipItem(oShifter, oShifterCWpR, INVENTORY_SLOT_CWEAPON_R);
}
if(GetIsObjectValid(oTemplateCWpL))
{
oShifterCWpL = CopyItem(oTemplateCWpL, oShifter, TRUE);
oCreatureWeapon = oShifterCWpL;
SetIdentified(oShifterCWpL, TRUE);
SafeEquipItem(oShifter, oShifterCWpL, INVENTORY_SLOT_CWEAPON_L);
}
if(GetIsObjectValid(oTemplateCWpB))
{
oShifterCWpB = CopyItem(oTemplateCWpB, oShifter, TRUE);
oCreatureWeapon = oShifterCWpB;
SetIdentified(oShifterCWpB, TRUE);
SafeEquipItem(oShifter, oShifterCWpB, INVENTORY_SLOT_CWEAPON_B);
}
if (!GetIsObjectValid(oCreatureWeapon))
{
//Make a dummy creature weapon that doesn't do anything except hold properties--it is never used as a weapon
//Don't do this if using NWNX funcs: the properties that would normally be applied to the creature
//weapon aren't needed, since NWNX funcs makes the changes directly to the creature instead.
oShifterCWpB = CreateItemOnObject("pnp_shft_cweap", oShifter); //create a shifter blank creature weapon
SetIdentified(oShifterCWpB, TRUE);
oCreatureWeapon = oShifterCWpB;
SafeEquipItem(oShifter, oShifterCWpB, INVENTORY_SLOT_CWEAPON_B);
}
}
//Hide isn't modified for Change Shape - Special Qualities don't transfer
if(nShifterType != SHIFTER_TYPE_CHANGESHAPE &&
nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE &&
nShifterType != SHIFTER_TYPE_ARANEA)
{
// Handle hide
// Nuke old props and composite bonus tracking - they will be re-evaluated later
ScrubPCSkin(oShifter, oShifterHide);
DeletePRCLocalInts(oShifterHide);
}
//Do this here instead of letting EvalPRCFeats do it below so that it happens sooner
_prc_inc_shifting_ApplyTemplate(oShifter, GetLocalInt(oShifter, PRC_Shifter_ShapeGeneration), nShifterType, oTemplate, TRUE, oShifterHide, oCreatureWeapon);
// Ability score adjustments - doesn't apply to Change Shape
if(nShifterType != SHIFTER_TYPE_CHANGESHAPE &&
nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE &&
nShifterType != SHIFTER_TYPE_ALTER_SELF &&
nShifterType != SHIFTER_TYPE_DISGUISE_SELF &&
nShifterType != SHIFTER_TYPE_ARANEA)
{
// Get the base delta
int nCreatureCON = GetAbilityScore(oTemplate, ABILITY_CONSTITUTION, TRUE);
int nHealHP = GetHitDice(oShifter) + nCreatureCON; //TODO: Need to take into account Great Constitution and Epic Toughness?
if(!GetPRCSwitch(PRC_PNP_REST_HEALING))
{
//Wildshape is supposed to heal the same amount as a night's sleep, which by default NWN rules
//is full healing. That would be overpowered, so we'll use only double the PnP healing amount here.
nHealHP *= 2;
}
{
//Preserve HP loss we had before shifting, but first heal by the amount that wildshaping is supposed to
//(one hitpoint per hit die plus one hitpoint per point of CON)
//Since we temporarily fully healed before shifting, we can assume we have full HP before damaging or healing.
int nOriginalMaxHP = GetLocalInt(oShifter, SHIFTER_ORIGINALMAXHP);
if(nOriginalMaxHP)
{
int nOriginalHP = GetLocalInt(oShifter, SHIFTER_ORIGINALHP);
int nDamageAmount = nOriginalMaxHP - nOriginalHP - nHealHP;
if(nDamageAmount > 0)
{
//TODO: make sure the full damage amount is applied
//(e.g., try a different damage type if immune to this one,
//or damage multiple times if full damage is prevented due
//to partial immunity, etc.).
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(nDamageAmount), oShifter);
}
else
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(-nDamageAmount), oShifter);
SetLocalInt(oShifter, SHIFTER_ORIGINALMAXHP, 0);
}
else
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(nHealHP), oShifter);
}
}
// If requested, generate an item for using SLAs
if(bGainSpellLikeAbilities)
{
object oSLAItem = CreateItemOnObject(SHIFTING_SLAITEM_RESREF, oShifter);
// Delayed to prevent potential TMI
DelayCommand(0.0f, _prc_inc_shifting_CreateShifterActiveAbilitiesItem(oShifter, oTemplate, oSLAItem));
}
// Change the appearance to that of the template
if(GetAppearanceType(oTemplate) > 5)
SetAppearanceData(oShifter, GetAppearanceData(oTemplate));
else
{
SetAppearanceData(oShifter, GetAppearanceData(oTemplate));
SetLocalInt(oShifter, "DynamicAppearance", TRUE);
SetCreatureAppearanceType(oShifter, GetAppearanceType(oTemplate));
}
// Some VFX
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_POLYMORPH), oShifter);
// Set the shiftedness marker
SetPersistantLocalInt(oShifter, SHIFTER_ISSHIFTED_MARKER, TRUE);
if(nShifterType == SHIFTER_TYPE_ALTER_SELF ||
(nShifterType == SHIFTER_TYPE_DISGUISE_SELF
&& GetRacialType(oShifter) != RACIAL_TYPE_CHANGELING
&& !GetLocalInt(oShifter, "MaskOfFleshInvocation")
&& !GetLocalInt(oShifter, "HasFaceChanger")))
{
int nShiftedNumber = GetPersistantLocalInt(oShifter, "nTimesShifted");
if(nShiftedNumber > 9) nShiftedNumber = 0;
nShiftedNumber++;
SetPersistantLocalInt(oShifter, "nTimesShifted", nShiftedNumber);
int nMetaMagic = PRCGetMetaMagicFeat();
int nDuration = PRCGetCasterLevel(oShifter) * 10;
if ((nMetaMagic & METAMAGIC_EXTEND))
{
nDuration *= 2;
}
DelayCommand(TurnsToSeconds(nDuration), ForceUnshift(oShifter, nShiftedNumber));
}
else if(GetLocalInt(oShifter, "HumanoidShapeInvocation"))
{
int nShiftedNumber = GetPersistantLocalInt(oShifter, "nTimesShifted");
if(nShiftedNumber > 9) nShiftedNumber = 0;
nShiftedNumber++;
SetPersistantLocalInt(oShifter, "nTimesShifted", nShiftedNumber);
DelayCommand(HoursToSeconds(24), ForceUnshift(oShifter, nShiftedNumber));
}
else if(GetLocalInt(oShifter, "MaskOfFleshInvocation"))
{
int nShiftedNumber = GetPersistantLocalInt(oShifter, "nTimesShifted");
if(nShiftedNumber > 9) nShiftedNumber = 0;
nShiftedNumber++;
SetPersistantLocalInt(oShifter, "nTimesShifted", nShiftedNumber);
int nDuration = GetLocalInt(oShifter, "MaskOfFleshInvocation");
DelayCommand(HoursToSeconds(nDuration), ForceUnshift(oShifter, nShiftedNumber));
}
else if(GetLocalInt(oShifter, "HasFaceChanger"))
{
int nShiftedNumber = GetPersistantLocalInt(oShifter, "nTimesShifted");
if(nShiftedNumber > 9) nShiftedNumber = 0;
nShiftedNumber++;
SetPersistantLocalInt(oShifter, "nTimesShifted", nShiftedNumber);
int nDuration = GetLocalInt(oShifter, "FaceChangerBonus");
DelayCommand(RoundsToSeconds(nDuration), ForceUnshift(oShifter, nShiftedNumber));
}
// Run the class & feat evaluation code
SetPersistantLocalString(oShifter, "PRC_SHIFTING_TEMPLATE_RESREF", GetResRef(oTemplate));
SetPersistantLocalInt(oShifter, "PRC_SHIFTING_SHIFTER_TYPE", nShifterType);
_prc_inc_shifting_EvalPRCFeats(oShifter, oShifterHide);
}
// Print shift information--this is slow, so wait until shifting is done
int bDebug = GetLocalInt(oShifter, "prc_shift_debug");
DelayCommand(SHIFTER_SHAPE_PRINT_DELAY, _prc_inc_PrintShape(oShifter, oTemplate, bDebug));
// Destroy the template creature--we need the template to print, so wait until that's done
DelayCommand(SHIFTER_TEMPLATE_DESTROY_DELAY, MyDestroyObject(oTemplate));
}
/** Internal function.
* Does the actual work in unshifting. Restores creature items and
* appearance. If oTemplate is valid, _prc_inc_shifting_ShiftIntoTemplateAux()
* will be called once unshifting is finished.
*
* NOTE: This assumes that all polymorph effects have already been removed.
*
* @param oShifter Creature to unshift
*
* Reshift parameters:
* @param nShifterType Passed to _prc_inc_shifting_ShiftIntoTemplateAux() when reshifting.
* @param oTemplate Passed to _prc_inc_shifting_ShiftIntoTemplateAux() when reshifting.
* @param bGainSpellLikeAbilities Passed to _prc_inc_shifting_ShiftIntoTemplateAux() when reshifting.
*/
void _prc_inc_shifting_UnShiftAux(object oShifter, int nShifterType, object oTemplate, int bGainSpellLikeAbilities)
{
int bReshift = GetIsObjectValid(oTemplate);
effect eEffect = GetFirstEffect(oShifter);
while(GetIsEffectValid(eEffect))
{
if(GetEffectTag(eEffect) == "ShifterAbilities")
RemoveEffect(oShifter,eEffect);
eEffect = GetNextEffect(oShifter);
}
// Get the shifter's creature items
object oShifterHide = GetPCSkin(oShifter); // Use the PRC wrapper for this to make sure we get the right object
object oShifterCWpR = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oShifter);
object oShifterCWpL = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oShifter);
object oShifterCWpB = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oShifter);
int nAdjustHP = bReshift && (
nShifterType != SHIFTER_TYPE_CHANGESHAPE
&& nShifterType != SHIFTER_TYPE_HUMANOIDSHAPE
&& nShifterType != SHIFTER_TYPE_ALTER_SELF
&& nShifterType != SHIFTER_TYPE_DISGUISE_SELF
&& nShifterType != SHIFTER_TYPE_ARANEA
//TODO?: && nShifterType != SHIFTER_TYPE_NONE
);
if(nAdjustHP)
{
int nOriginalHP = GetCurrentHitPoints(oShifter);
int nOriginalMaxHP = GetMaxHitPoints(oShifter);
SetLocalInt(oShifter, SHIFTER_ORIGINALHP, nOriginalHP);
SetLocalInt(oShifter, SHIFTER_ORIGINALMAXHP, nOriginalMaxHP);
//Before unshifting, fully heal the shifter. After shifting, some of the added hitpoints will be taken away again.
//This is to prevent the Shifter from dying when shifting from one high CON form to another high CON form
//due to the temporary loss of CON caused by shifting into the low-CON true form in between.
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(nOriginalMaxHP), oShifter);
}
// Clear the hide. We'll have to run EvalPRCFeats() later on
ScrubPCSkin(oShifter, oShifterHide);
DeletePRCLocalInts(oShifterHide);
GetPersistantLocalInt(oShifter, SHIFTER_TRUEAPPEARANCE);
//HACK: For some reason, the next call to this function after the line above it returns FALSE even if it should return TRUE,
//but the next call to it after that returns the correct result. So, call it here: this makes it return the correct
//result for the RestoreTrueAppearance call below.
//TODO: REMOVE when workaround no longer needed
//TODO: is this because of the delays in DeletePRCLocalInts?
// Nuke the creature weapons. If the normal form is supposed to have natural weapons, they'll get re-constructed
if(GetIsObjectValid(oShifterCWpR)) MyDestroyObject(oShifterCWpR);
if(GetIsObjectValid(oShifterCWpL)) MyDestroyObject(oShifterCWpL);
if(GetIsObjectValid(oShifterCWpB)) MyDestroyObject(oShifterCWpB);
object oSLAItem = GetItemPossessedBy(oShifter, SHIFTING_SLAITEM_TAG);
int bCheckForAuraEffects = FALSE;
if(GetIsObjectValid(oSLAItem))
{
MyDestroyObject(oSLAItem);
bCheckForAuraEffects = TRUE;
}
// Remove effects
_prc_inc_shifting_DeleteEffectInts(oShifter);
if (!bReshift)
_prc_inc_shifting_RemoveSpellEffects(oShifter, TRUE);
effect eTest = GetFirstEffect(oShifter);
if (bCheckForAuraEffects)
{
while(GetIsEffectValid(eTest))
{
int nEffectSpellID = GetEffectSpellId(eTest);
if(nEffectSpellID == SPELLABILITY_AURA_BLINDING ||
nEffectSpellID == SPELLABILITY_AURA_COLD ||
nEffectSpellID == SPELLABILITY_AURA_ELECTRICITY ||
nEffectSpellID == SPELLABILITY_AURA_FEAR ||
nEffectSpellID == SPELLABILITY_AURA_FIRE ||
nEffectSpellID == SPELLABILITY_AURA_MENACE ||
nEffectSpellID == SPELLABILITY_AURA_PROTECTION ||
nEffectSpellID == SPELLABILITY_AURA_STUN ||
nEffectSpellID == SPELLABILITY_AURA_UNEARTHLY_VISAGE ||
nEffectSpellID == SPELLABILITY_AURA_UNNATURAL ||
nEffectSpellID == SPELLABILITY_DRAGON_FEAR
)
{
RemoveEffect(oShifter, eTest);
}
eTest = GetNextEffect(oShifter);
}
}
// Restore appearance
if(!RestoreTrueAppearance(oShifter))
{
string sError = "prc_inc_shifting: _UnShiftAux(): ERROR: Unable to restore true form for " + DebugObject2Str(oShifter);
if(DEBUG) DoDebug(sError);
else WriteTimestampedLogEntry(sError);
}
// Unset the shiftedness marker
SetPersistantLocalInt(oShifter, SHIFTER_ISSHIFTED_MARKER, FALSE);
_prc_inc_shifting_EvalPRCFeats(oShifter, oShifterHide);
// Queue reshifting to happen if needed. Let a short while pass so any fallout from the unshift gets handled
if(bReshift)
DelayCommand(1.0f, _prc_inc_shifting_ShiftIntoTemplateAux(oShifter, nShifterType, oTemplate, bGainSpellLikeAbilities));
else
SetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX, FALSE);
}
/** Internal function.
* A polymorph effect was encountered during unshifting and removed. We need to
* wait until it's actually removed (instead of merely gone from the active effects
* list on oShifter) before calling _prc_inc_shifting_UnShiftAux().
* This is done by tracking the contents of the creature armour slot. The object in
* it will change when the polymorph is really removed.
*
* @param oShifter The creature whose creature armour slot to monitor.
* @param oSkin The skin object that was in the slot when the UnShift() call that triggered
* this was run.
* @param nRepeats Number of times this function has repeated the delay. Used to track timeout
*/
void _prc_inc_shifting_UnShiftAux_SeekPolyEnd(object oShifter, object oSkin, int nRepeats = 0)
{
// Over 15 seconds passed, something is wrong
if(nRepeats++ > 100)
{
if(DEBUG) DoDebug("prc_inc_shifting: _UnShiftAux_SeekPolyEnd(): ERROR: Repeated over 100 times, skin object remains the same.");
return;
}
// See if the skin object has changed. When it does, the polymorph is genuinely gone instead of just being removed from the effects list
if(GetItemInSlot(INVENTORY_SLOT_CARMOUR, oShifter) == oSkin)
DelayCommand(0.15f, _prc_inc_shifting_UnShiftAux_SeekPolyEnd(oShifter, oSkin, nRepeats));
// It's gone, finish unshifting
else
_prc_inc_shifting_UnShiftAux(oShifter, SHIFTER_TYPE_NONE, OBJECT_INVALID, FALSE);
}
object _prc_inc_load_template_from_resref(string sResRef, int nHD)
{
/* Create the template to shift into */
// Get the waypoint in Limbo where shifting template creatures are spawned
object oSpawnWP = GetWaypointByTag(SHIFTING_TEMPLATE_WP_TAG);
// Paranoia check - the WP should be built into the area data of Limbo
if(!GetIsObjectValid(oSpawnWP))
{
if(DEBUG) DoDebug("prc_inc_shifting: ShiftIntoResRef(): ERROR: Template spawn waypoint does not exist.");
// Create the WP
oSpawnWP = CreateObject(OBJECT_TYPE_WAYPOINT, "nw_waypoint001", GetLocation(GetObjectByTag("HEARTOFCHAOS")), FALSE, SHIFTING_TEMPLATE_WP_TAG);
}
// Get the WP's location
location lSpawn = GetLocation(oSpawnWP);
// And spawn an instance of the given template there
object oTemplate = CreateObject(OBJECT_TYPE_CREATURE, sResRef, lSpawn);
/*
Some modules create low-level templates and level them up.
Until now, when we tried learning the higher-level version, we ended up with the
lower-level template instead. This level-up code takes care of that.
Properly, we should remember what level we actually learned it at and only level that
far, instead of leveling up to our current level. I've chosen to do it this way
for the following reasons:
> Doing it properly is more intrusive and more error-prone coding. For now, at least,
I don't think it makes sense to do that.
> Doing it properly way would litter our known shapes list with the same creature at different
levels, when all we really want is the highest level one we can use.
> From a role-playing point of view, shapes implemented that way by a module are presumably
common in the module, so a shifter would be pretty familiar with them and so would generally
have learned the highest level he can shift into.
*/
int bTemplateCanLevel = FALSE;
if (LevelUpHenchman(oTemplate))
{
bTemplateCanLevel = TRUE;
MyDestroyObject(oTemplate);
oTemplate = CreateObject(OBJECT_TYPE_CREATURE, sResRef, lSpawn);
}
if(bTemplateCanLevel)
{
int nNeedLevels = nHD - GetHitDice(oTemplate);
if (GetPRCSwitch(PNP_SHFT_USECR))
{
int i;
for (i=0; i<40; i+=1)
{
if (GetChallengeRating(oTemplate) > IntToFloat(nHD))
break;
if (!LevelUpHenchman(oTemplate))
break;
}
nNeedLevels = GetHitDice(oTemplate);
if (GetChallengeRating(oTemplate) > IntToFloat(nHD))
nNeedLevels -= 1;
MyDestroyObject(oTemplate);
oTemplate = CreateObject(OBJECT_TYPE_CREATURE, sResRef, lSpawn);
nNeedLevels -= GetHitDice(oTemplate);
}
while (nNeedLevels > 0)
{
if (!LevelUpHenchman(oTemplate))
break;
nNeedLevels -= 1;
}
SetLocalInt(oTemplate, "prc_template_can_level", 1);
}
return oTemplate;
}
string _prc_inc_get_stored_name_from_template(object oTemplate)
{
string sSuffix;
if(GetLocalInt(oTemplate, "prc_template_can_level"))
sSuffix = "+";
string sResult = GetStringByStrRef(57438+0x01000000);
sResult = ReplaceString(sResult, "%(NAME)", GetName(oTemplate));
sResult = ReplaceString(sResult, "%(SL)", IntToString(_prc_inc_shifting_ShifterLevelRequirement(oTemplate)));
sResult = ReplaceString(sResult, "%(CL)", IntToString(GetHitDice(oTemplate)) + sSuffix);
sResult = ReplaceString(sResult, "%(CR)", FloatToString(GetChallengeRating(oTemplate), 4, 1) + sSuffix);
return sResult;
}
//////////////////////////////////////////////////
/* Function definitions */
//////////////////////////////////////////////////
int StoreCurrentRaceAsTrueRace(object oShifter)
{
if (GetPersistantLocalInt(oShifter, SHIFTER_ISSHIFTED_MARKER))
return FALSE;
if (IsPolymorphed(oShifter))
return FALSE;
SetPersistantLocalInt(oShifter, SHIFTER_TRUE_RACE, MyPRCGetRacialType(oShifter) + 1);
return TRUE;
}
int StoreCurrentAppearanceAsTrueAppearance(object oShifter, int bCarefull = TRUE)
{
// If requested, check that the creature isn't shifted or polymorphed
if(bCarefull)
{
if (GetPersistantLocalInt(oShifter, SHIFTER_ISSHIFTED_MARKER))
return FALSE;
if (IsPolymorphed(oShifter))
return FALSE;
}
// Get the appearance data
struct appearancevalues appval = GetAppearanceData(oShifter);
// Store it
SetPersistantLocalAppearancevalues(oShifter, SHIFTER_TRUEAPPEARANCE, appval);
SetPersistantLocalInt(oShifter, "TrueFormAppearanceType", GetAppearanceType(oShifter));
// Set a marker that tells that the true appearance is stored
SetPersistantLocalInt(oShifter, SHIFTER_TRUEAPPEARANCE, TRUE);
return TRUE;
}
int RestoreTrueAppearance(object oShifter)
{
// Check for the "true appearance stored" marker. Abort if it's not present
if(!GetPersistantLocalInt(oShifter, SHIFTER_TRUEAPPEARANCE))
return FALSE;
// See if the character is polymorphed. Won't restore the appearance if it is
if (IsPolymorphed(oShifter))
return FALSE;
// We got this far, everything should be OK
// Retrieve the appearance data
struct appearancevalues appval = GetPersistantLocalAppearancevalues(oShifter, SHIFTER_TRUEAPPEARANCE);
// Apply it to the creature
SetAppearanceData(oShifter, appval);
if(GetLocalInt(oShifter, "DynamicAppearance"))
{
SetCreatureAppearanceType(oShifter, GetPersistantLocalInt(oShifter, "TrueFormAppearanceType"));
DeleteLocalInt(oShifter, "DynamicAppearance");
}
// Inform caller of success
return TRUE;
}
// Storage functions //
int StoreShiftingTemplate(object oShifter, int nShifterType, object oTarget)
{
// Some paranoia - both the target and the object to store on must be valid. And PCs are never legal for storage - PC resref should be always empty
if(!(GetIsObjectValid(oShifter) && GetIsObjectValid(oTarget) && GetResRef(oTarget) != ""))
return FALSE;
string sResRefsArray = SHIFTER_RESREFS_ARRAY + IntToString(nShifterType);
string sNamesArray = SHIFTER_NAMES_ARRAY + IntToString(nShifterType);
string sRacialTypeArray = SHIFTER_RACIALTYPE_ARRAY + IntToString(nShifterType);
// Determine array existence
if(!persistant_array_exists(oShifter, sResRefsArray))
persistant_array_create(oShifter, sResRefsArray);
if(!persistant_array_exists(oShifter, sNamesArray))
persistant_array_create(oShifter, sNamesArray);
if(!persistant_array_exists(oShifter, sRacialTypeArray))
persistant_array_create(oShifter, sRacialTypeArray);
// Get the storeable data
string sResRef = GetResRef(oTarget);
string sName = _prc_inc_get_stored_name_from_template(oTarget);
string sRacialType = IntToString(MyPRCGetRacialType(oTarget));
int nArraySize = persistant_array_get_size(oShifter, sResRefsArray);
// Check for the template already being present
if(_prc_inc_shifting_GetIsTemplateStored(oShifter, nShifterType, sResRef))
return FALSE;
persistant_array_set_string(oShifter, sResRefsArray, nArraySize, sResRef);
persistant_array_set_string(oShifter, sNamesArray, nArraySize, sName);
persistant_array_set_string(oShifter, sRacialTypeArray, nArraySize, sRacialType);
return TRUE;
}
int GetNumberOfStoredTemplates(object oShifter, int nShifterType)
{
if(!persistant_array_exists(oShifter, SHIFTER_RESREFS_ARRAY + IntToString(nShifterType)))
return 0;
return persistant_array_get_size(oShifter, SHIFTER_RESREFS_ARRAY + IntToString(nShifterType));
}
string GetStoredTemplate(object oShifter, int nShifterType, int nIndex)
{
return persistant_array_get_string(oShifter, SHIFTER_RESREFS_ARRAY + IntToString(nShifterType), nIndex);
}
string GetStoredTemplateFilter(object oShifter, int nShifterType)
{
string sResult = "|";
int nCount = GetNumberOfStoredTemplates(oShifter, nShifterType);
int i;
for(i=0; i<nCount; i++)
sResult += persistant_array_get_string(oShifter, SHIFTER_RESREFS_ARRAY + IntToString(nShifterType), i) + "|";
return sResult;
}
string GetStoredTemplateName(object oShifter, int nShifterType, int nIndex)
{
return persistant_array_get_string(oShifter, SHIFTER_NAMES_ARRAY + IntToString(nShifterType), nIndex);
}
int GetStoredTemplateRacialType(object oShifter, int nShifterType, int nIndex)
{
string sRacialTypeArray = SHIFTER_RACIALTYPE_ARRAY + IntToString(nShifterType);
string sRacialType = persistant_array_get_string(oShifter, sRacialTypeArray, nIndex);
if(sRacialType == "")
return -1;
else
return StringToInt(sRacialType);
}
void _UpdateStoredTemplateInfo(object oShifter, int nShifterType, int nStart, int nLimit)
{
int nDeletedPrefixLen = GetStringLength(SHIFTER_DELETED_SHAPE_PREFIX);
string sNamesArray = SHIFTER_NAMES_ARRAY + IntToString(nShifterType);
string sRacialTypeArray = SHIFTER_RACIALTYPE_ARRAY + IntToString(nShifterType);
int i;
for(i = nStart; i < nLimit; i++)
{
string sName = persistant_array_get_string(oShifter, sNamesArray, i);
string sRacialType = persistant_array_get_string(oShifter, sRacialTypeArray, i);
object oTarget = _prc_inc_load_template_from_resref(GetStoredTemplate(oShifter, nShifterType, i), 0);
if(GetIsObjectValid(oTarget))
{
sName = _prc_inc_get_stored_name_from_template(oTarget);
sRacialType = IntToString(MyPRCGetRacialType(oTarget));
MyDestroyObject(oTarget);
persistant_array_set_string(oShifter, sNamesArray, i, sName);
persistant_array_set_string(oShifter, sRacialTypeArray, i, sRacialType);
_prc_inc_PrintShapeInfo(oShifter, IntToString(i) + " Updated: " + sName);
}
else
{
if (GetStringLeft(sName, nDeletedPrefixLen) != SHIFTER_DELETED_SHAPE_PREFIX)
persistant_array_set_string(oShifter, sNamesArray, i, SHIFTER_DELETED_SHAPE_PREFIX + sName); //Add tag to name indicating that it could not be loaded
_prc_inc_PrintShapeInfo(oShifter, IntToString(i) + " Could not update: " + sName);
}
}
}
void UpdateStoredTemplateInfo(object oShifter, int nShifterType, int nStart = 0)
{
string sRacialTypeArray = SHIFTER_RACIALTYPE_ARRAY + IntToString(nShifterType);
if(!persistant_array_exists(oShifter, sRacialTypeArray))
persistant_array_create(oShifter, sRacialTypeArray);
CHUNK_SIZE = 5;
int nArraySize = GetNumberOfStoredTemplates(oShifter, nShifterType);
if(nStart < nArraySize)
{
int nEnd = min(nStart + CHUNK_SIZE, nArraySize);
_UpdateStoredTemplateInfo(oShifter, nShifterType, nStart, nEnd);
if(nEnd < nArraySize)
DelayCommand(0.0f, UpdateStoredTemplateInfo(oShifter, nShifterType, nEnd));
}
}
void DeleteStoredTemplate(object oShifter, int nShifterType, int nIndex)
{
string sResRefsArray = SHIFTER_RESREFS_ARRAY + IntToString(nShifterType);
string sNamesArray = SHIFTER_NAMES_ARRAY + IntToString(nShifterType);
string sRacialTypeArray = SHIFTER_RACIALTYPE_ARRAY + IntToString(nShifterType);
// Determine array existence
if(!persistant_array_exists(oShifter, sResRefsArray))
return;
if(!persistant_array_exists(oShifter, sNamesArray))
return;
int nRacialTypeArrayExists = persistant_array_exists(oShifter, sRacialTypeArray);
// Move array entries
int i, nArraySize = persistant_array_get_size(oShifter, sResRefsArray);
for(i = nIndex; i < nArraySize - 1; i++)
{
persistant_array_set_string(oShifter, sResRefsArray, i,
persistant_array_get_string(oShifter, sResRefsArray, i + 1)
);
persistant_array_set_string(oShifter, sNamesArray, i,
persistant_array_get_string(oShifter, sNamesArray, i + 1)
);
if(nRacialTypeArrayExists)
persistant_array_set_string(oShifter, sRacialTypeArray, i,
persistant_array_get_string(oShifter, sRacialTypeArray, i + 1)
);
}
// Shrink the arrays
persistant_array_shrink(oShifter, sResRefsArray, nArraySize - 1);
persistant_array_shrink(oShifter, sNamesArray, nArraySize - 1);
if(nRacialTypeArrayExists)
persistant_array_shrink(oShifter, sRacialTypeArray, nArraySize - 1);
}
int GetStoredTemplateDeleteMark(object oShifter, int nShifterType, int nIndex)
{
int nDeletedPrefixLen = GetStringLength(SHIFTER_DELETED_SHAPE_PREFIX);
string sNamesArray = SHIFTER_NAMES_ARRAY + IntToString(nShifterType);
if(!persistant_array_exists(oShifter, sNamesArray))
return FALSE;
int nArraySize = persistant_array_get_size(oShifter, sNamesArray);
if (nIndex > nArraySize)
return FALSE;
string sName = persistant_array_get_string(oShifter, sNamesArray, nIndex);
return GetStringLeft(sName, nDeletedPrefixLen) == SHIFTER_DELETED_SHAPE_PREFIX;
}
void SetStoredTemplateDeleteMark(object oShifter, int nShifterType, int nIndex, int bMark)
{
int nDeletedPrefixLen = GetStringLength(SHIFTER_DELETED_SHAPE_PREFIX);
string sNamesArray = SHIFTER_NAMES_ARRAY + IntToString(nShifterType);
if(!persistant_array_exists(oShifter, sNamesArray))
return;
int nArraySize = persistant_array_get_size(oShifter, sNamesArray);
if (nIndex > nArraySize)
return;
string sName = persistant_array_get_string(oShifter, sNamesArray, nIndex);
if (GetStringLeft(sName, nDeletedPrefixLen) == SHIFTER_DELETED_SHAPE_PREFIX)
{
if (!bMark)
sName = GetStringRight(sName, GetStringLength(sName)-nDeletedPrefixLen);
}
else
{
if (bMark)
sName = SHIFTER_DELETED_SHAPE_PREFIX + sName;
}
persistant_array_set_string(oShifter, sNamesArray, nIndex, sName);
}
void DeleteMarkedStoredTemplates(object oShifter, int nShifterType, int nSrc=0, int nDst=0)
{
int nDeletedPrefixLen = GetStringLength(SHIFTER_DELETED_SHAPE_PREFIX);
string sResRefsArray = SHIFTER_RESREFS_ARRAY + IntToString(nShifterType);
string sNamesArray = SHIFTER_NAMES_ARRAY + IntToString(nShifterType);
string sRacialTypeArray = SHIFTER_RACIALTYPE_ARRAY + IntToString(nShifterType);
// Determine array existence
if(!persistant_array_exists(oShifter, sResRefsArray))
return;
if(!persistant_array_exists(oShifter, sNamesArray))
return;
int nRacialTypeArrayExists = persistant_array_exists(oShifter, sRacialTypeArray);
// Move array entries, skipping the marked ones
int nCount = 0, nArraySize = persistant_array_get_size(oShifter, sResRefsArray);
string sName, sResRef, sRace;
while(nSrc < nArraySize && nCount++ < CHUNK_SIZE)
{
sName = persistant_array_get_string(oShifter, sNamesArray, nSrc);
if (GetStringLeft(sName, nDeletedPrefixLen) != SHIFTER_DELETED_SHAPE_PREFIX)
{
if (nSrc != nDst)
{
persistant_array_set_string(oShifter, sNamesArray, nDst, sName);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nSrc);
persistant_array_set_string(oShifter, sResRefsArray, nDst, sResRef);
if (nRacialTypeArrayExists)
{
sRace = persistant_array_get_string(oShifter, sRacialTypeArray, nSrc);
persistant_array_set_string(oShifter, sRacialTypeArray, nDst, sRace);
}
}
nDst++;
}
else
{
DoDebug("Deleting shape: " + sName);
}
nSrc++;
}
if (nSrc < nArraySize)
DelayCommand(0.0f, DeleteMarkedStoredTemplates(oShifter, nShifterType, nSrc, nDst));
else
{
// Shrink the arrays
persistant_array_shrink(oShifter, sResRefsArray, nDst);
persistant_array_shrink(oShifter, sNamesArray, nDst);
if(nRacialTypeArrayExists)
persistant_array_shrink(oShifter, sRacialTypeArray, nDst);
}
}
// Shifting-related functions
int GetCreatureIsKnown(object oShifter, int nShifterType, object oTemplate)
{
int nReturn = 0;
if(GetIsObjectValid(oShifter) && GetIsObjectValid(oTemplate))
nReturn = _prc_inc_shifting_GetIsTemplateStored(oShifter, nShifterType, GetResRef(oTemplate));
return nReturn;
}
string FindResRefFromString(object oShifter, int nShifterType, string sFindString, int bList)
{
string sResRef, sResRefsArray = SHIFTER_RESREFS_ARRAY + IntToString(nShifterType);
string sName, sNamesArray = SHIFTER_NAMES_ARRAY + IntToString(nShifterType);
int nResRef, nArraySize = persistant_array_get_size(oShifter, sResRefsArray);
//Check for current shape
if(sFindString == ".")
{
sResRef = GetPersistantLocalString(oShifter, "PRC_SHIFTING_TEMPLATE_RESREF");
if(sResRef == "")
DoDebug("Current shape match: NOT SHIFTED");
else
{
nResRef = _prc_inc_shifting_GetIsTemplateStored(oShifter, nShifterType, sResRef) - 1;
if(nResRef >= 0)
sName = persistant_array_get_string(oShifter, sNamesArray, nResRef);
else
sName = "???";
DoDebug("Current shape match: " + IntToString(nResRef+1) + " = " + sName + " [" + sResRef + "]");
}
return sResRef;
}
//Check for shape numbers
int nShapeNumberMatch = StringToInt(sFindString);
if(nShapeNumberMatch >= 1 && nShapeNumberMatch <= nArraySize)
{
nShapeNumberMatch -= 1;
sName = persistant_array_get_string(oShifter, sNamesArray, nShapeNumberMatch);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nShapeNumberMatch);
DoDebug("Shape number match: #" + IntToString(nShapeNumberMatch+1) + " = " + sName + " [" + sResRef + "]");
return sResRef;
}
//Check for quick shift slots numbers
string sFirstLetter = GetStringLeft(sFindString, 1);
if(sFirstLetter == "q" || sFirstLetter == "Q")
{
int nQuickSlotMatch = StringToInt(GetStringRight(sFindString, GetStringLength(sFindString)-1));
if(nQuickSlotMatch >= 1 && nQuickSlotMatch <= 10)
{
sResRef = GetPersistantLocalString(oShifter, "PRC_Shifter_Quick_" + IntToString(nQuickSlotMatch) + "_ResRef");
nResRef = _prc_inc_shifting_GetIsTemplateStored(oShifter, nShifterType, sResRef) - 1;
if(nResRef >= 0)
sName = persistant_array_get_string(oShifter, sNamesArray, nResRef);
else
sName = "???";
if(sResRef == "")
DoDebug("Quickslot match: Q" + IntToString(nQuickSlotMatch) + " = EMPTY QUICKSLOT");
else
DoDebug("Quickslot match: Q" + IntToString(nQuickSlotMatch) + " = " + sName + " [" + sResRef + "]");
return sResRef;
}
}
//Check for matching resref first (exact case)
int nResRefMatch = _prc_inc_shifting_GetIsTemplateStored(oShifter, nShifterType, sFindString) - 1;
if(nResRefMatch >= 0)
{
sName = persistant_array_get_string(oShifter, sNamesArray, nResRefMatch);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nResRefMatch);
DoDebug("ResRef match: "+ IntToString(nResRefMatch+1) + " = " + sName + " [" + sResRef + "]");
return sResRef;
}
//Check for matching name next (ignoring case)
sFindString = GetStringLowerCase(sFindString);
int nExactMatch = -1, nPrefixMatch = -1, nPartialMatch = -1;
int nExactMatchCount = 0, nPrefixMatchCount = 0, nPartialMatchCount = 0;
int i, nFind;
string sLowerName;
for(i = 0; i < nArraySize; i++)
{
sName = persistant_array_get_string(oShifter, sNamesArray, i);
nFind = FindSubString(sName, " (");
if(nFind != -1)
sName = GetStringLeft(sName, nFind); //Remove the part in parens that tells shape HD, CR, etc.
sLowerName = GetStringLowerCase(sName);
if (bList && sFindString == "")
{
sName = persistant_array_get_string(oShifter, sNamesArray, i);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, i);
DoDebug("#" + IntToString(i+1) + " = " + sName + " [" + sResRef + "]");
}
else if(sFindString == sLowerName)
{
nExactMatch = i;
nExactMatchCount += 1;
sName = persistant_array_get_string(oShifter, sNamesArray, nExactMatch);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nExactMatch);
DoDebug("Exact match: #" + IntToString(i+1) + " = " + sName + " [" + sResRef + "]");
}
else
{
//TODO: multi-word prefix matches
nFind = FindSubString(sLowerName, sFindString);
if(nFind == 0)
{
nPrefixMatch = i;
nPrefixMatchCount += 1;
sName = persistant_array_get_string(oShifter, sNamesArray, nPrefixMatch);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nPrefixMatch);
DoDebug("Beginning match: #" + IntToString(i+1) + " = " + sName + " [" + sResRef + "]");
}
else if(nFind > 0)
{
nPartialMatch = i;
nPartialMatchCount += 1;
sName = persistant_array_get_string(oShifter, sNamesArray, nPartialMatch);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nPartialMatch);
DoDebug("Middle match: #" + IntToString(i+1) + " = " + sName + " [" + sResRef + "]");
}
}
}
if(nExactMatchCount)
{
if(nExactMatchCount > 1)
{
DoDebug("TOO MANY EXACT MATCHES (" +IntToString(nExactMatchCount) + " found)");
return "";
}
sName = persistant_array_get_string(oShifter, sNamesArray, nExactMatch);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nExactMatch);
DoDebug("FINAL MATCH: #" + IntToString(nExactMatch+1) + " = " + sName + " [" + sResRef + "]");
return sResRef;
}
if(nPrefixMatchCount)
{
if(nPrefixMatchCount > 1)
{
DoDebug("TOO MANY BEGINNING MATCHES (" +IntToString(nPrefixMatchCount) + " found)");
return "";
}
sName = persistant_array_get_string(oShifter, sNamesArray, nPrefixMatch);
sResRef = persistant_array_get_string(oShifter, sResRefsArray, nPrefixMatch);
DoDebug("FINAL MATCH: #" + IntToString(nPrefixMatch+1) + " = " + sName + " [" + sResRef + "]");
return sResRef;
}
if(nPartialMatchCount)
{
if(nPartialMatchCount > 1)
{
DoDebug("TOO MANY MIDDLE MATCHES (" +IntToString(nPartialMatchCount) + " found)");
return "";
}
sName = persistant_array_get_string(oShifter, sNamesArray, nPartialMatch);
sResRef= persistant_array_get_string(oShifter, sResRefsArray, nPartialMatch);
DoDebug("FINAL MATCH: #" + IntToString(nPartialMatch+1) + " = " + sName + " [" + sResRef + "]");
return sResRef;
}
DoDebug("NO MATCH");
return "";
}
int GetCanShiftIntoCreature(object oShifter, int nShifterType, object oTemplate)
{
if (!GetPersistantLocalInt(oShifter, "PRC_UNI_SHIFT_SCRIPT"))
{
SetPersistantLocalInt(oShifter, "PRC_UNI_SHIFT_SCRIPT", 1);
ExecuteScript("prc_uni_shift", oShifter);
}
// Base assumption: Can shift into the target
int bReturn = TRUE;
// Some basic checks
if(GetIsObjectValid(oShifter) && GetIsObjectValid(oTemplate))
{
// PC check
if(GetIsPC(oTemplate))
{
bReturn = FALSE;
SendMessageToPCByStrRef(oShifter, STRREF_NOPOLYTOPC); // "You cannot polymorph into a PC."
}
// Shifting prevention feat
else if(GetHasFeat(SHIFTER_BLACK_LIST, oTemplate))
{
bReturn = FALSE;
SendMessageToPCByStrRef(oShifter, STRREF_FORBIDPOLY); // "Target cannot be polymorphed into."
}
// Test switch-based limitations
if(bReturn)
{
int nSize = PRCGetCreatureSize(oTemplate);
int nRacialType = MyPRCGetRacialType(oTemplate);
// Size switches
if(nSize >= CREATURE_SIZE_HUGE && GetPRCSwitch(PNP_SHFT_S_HUGE))
bReturn = FALSE;
if(nSize == CREATURE_SIZE_LARGE && GetPRCSwitch(PNP_SHFT_S_LARGE))
bReturn = FALSE;
if(nSize == CREATURE_SIZE_MEDIUM && GetPRCSwitch(PNP_SHFT_S_MEDIUM))
bReturn = FALSE;
if(nSize == CREATURE_SIZE_SMALL && GetPRCSwitch(PNP_SHFT_S_SMALL))
bReturn = FALSE;
if(nSize <= CREATURE_SIZE_TINY && GetPRCSwitch(PNP_SHFT_S_TINY))
bReturn = FALSE;
// Type switches
if(nRacialType == RACIAL_TYPE_OUTSIDER && GetPRCSwitch(PNP_SHFT_F_OUTSIDER))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_ELEMENTAL && GetPRCSwitch(PNP_SHFT_F_ELEMENTAL))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_CONSTRUCT && GetPRCSwitch(PNP_SHFT_F_CONSTRUCT))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_UNDEAD && GetPRCSwitch(PNP_SHFT_F_UNDEAD))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_DRAGON && GetPRCSwitch(PNP_SHFT_F_DRAGON))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_ABERRATION && GetPRCSwitch(PNP_SHFT_F_ABERRATION))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_OOZE && GetPRCSwitch(PNP_SHFT_F_OOZE))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_MAGICAL_BEAST && GetPRCSwitch(PNP_SHFT_F_MAGICALBEAST))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_GIANT && GetPRCSwitch(PNP_SHFT_F_GIANT))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_VERMIN && GetPRCSwitch(PNP_SHFT_F_VERMIN))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_BEAST && GetPRCSwitch(PNP_SHFT_F_BEAST))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_ANIMAL && GetPRCSwitch(PNP_SHFT_F_ANIMAL))
bReturn = FALSE;
if(nRacialType == RACIAL_TYPE_HUMANOID_MONSTROUS && GetPRCSwitch(PNP_SHFT_F_MONSTROUSHUMANOID))
bReturn = FALSE;
if(GetPRCSwitch(PNP_SHFT_F_HUMANOID) &&
(nRacialType == RACIAL_TYPE_DWARF ||
nRacialType == RACIAL_TYPE_ELF ||
nRacialType == RACIAL_TYPE_GNOME ||
nRacialType == RACIAL_TYPE_HUMAN ||
nRacialType == RACIAL_TYPE_HALFORC ||
nRacialType == RACIAL_TYPE_HALFELF ||
nRacialType == RACIAL_TYPE_HALFLING ||
nRacialType == RACIAL_TYPE_HUMANOID_ORC ||
nRacialType == RACIAL_TYPE_HUMANOID_REPTILIAN
))
bReturn = FALSE;
if(!bReturn)
SendMessageToPCByStrRef(oShifter, STRREF_SETTINGFORBID); // "The module settings prevent this creature from being polymorphed into."
}
// Still OK, test HD or CR
if(bReturn)
{
// Check target's HD or CR
int nShifterHD = GetHitDice(oShifter);
int nTemplateHD = _prc_inc_shifting_CharacterLevelRequirement(oTemplate);
if(nTemplateHD > nShifterHD)
{
bReturn = FALSE;
// "You need X more character levels before you can take on that form."
SendMessageToPC(oShifter, GetStringByStrRef(STRREF_YOUNEED) + " " + IntToString(nTemplateHD - nShifterHD) + " " + GetStringByStrRef(STRREF_MORECHARLVL));
}
if(nShifterType == SHIFTER_TYPE_ALTER_SELF && nTemplateHD > 5)
{
bReturn = FALSE;
SendMessageToPC(oShifter, "This creature is too high a level to copy with this spell.");
}
}// end if - Checking HD or CR
// Move onto shifting type-specific checks if there haven't been any problems yet
if(bReturn)
{
if(nShifterType == SHIFTER_TYPE_SHIFTER)
{
int nShifterLevel = GetLevelByClass(CLASS_TYPE_PNP_SHIFTER, oShifter);
int nRacialType = MyPRCGetRacialType(oTemplate);
// Fey and shapechangers are forbidden targets for PnP Shifter
if(nRacialType == RACIAL_TYPE_FEY || nRacialType == RACIAL_TYPE_SHAPECHANGER)
{
bReturn = FALSE;
SendMessageToPCByStrRef(oShifter, STRREF_PNPSFHT_FEYORSSHIFT); // "You cannot use PnP Shifter abilities to polymorph into this creature."
}
else
{
// Test level required
int nLevelRequired = _prc_inc_shifting_ShifterLevelRequirement(oTemplate);
if(nLevelRequired > nShifterLevel)
{
bReturn = FALSE;
// "You need X more PnP Shifter levels before you can take on that form."
SendMessageToPC(oShifter, GetStringByStrRef(STRREF_YOUNEED) + " " + IntToString(nLevelRequired - nShifterLevel) + " " + GetStringByStrRef(STRREF_PNPSHFT_MORELEVEL));
}
}// end else - Not outright forbidden due to target being Fey or Shapeshifter
}// end if - PnP Shifter checks
//Change Shape checks
else if(nShifterType == SHIFTER_TYPE_HUMANOIDSHAPE)
{
int nTargetSize = PRCGetCreatureSize(oTemplate);
int nRacialType = MyPRCGetRacialType(oTemplate);
int nShifterSize = PRCGetCreatureSize(oShifter);
int nSizeDiff = nTargetSize - nShifterSize;
if(nSizeDiff > 1 || nSizeDiff < -1)
{
bReturn = FALSE;
SendMessageToPC(oShifter, "This creature is too large or too small.");
}
if(!(nRacialType == RACIAL_TYPE_DWARF ||
nRacialType == RACIAL_TYPE_ELF ||
nRacialType == RACIAL_TYPE_GNOME ||
nRacialType == RACIAL_TYPE_HUMAN ||
nRacialType == RACIAL_TYPE_HALFORC ||
nRacialType == RACIAL_TYPE_HALFELF ||
nRacialType == RACIAL_TYPE_HALFLING ||
nRacialType == RACIAL_TYPE_HUMANOID_ORC ||
nRacialType == RACIAL_TYPE_HUMANOID_REPTILIAN
))
{
bReturn = FALSE;
SendMessageToPC(oShifter, "This creature is not a humanoid racial type.");
}
}
//Changeling check
else if(nShifterType == SHIFTER_TYPE_DISGUISE_SELF && GetRacialType(oShifter) == RACIAL_TYPE_CHANGELING)
{
int nSize = PRCGetCreatureSize(oTemplate);
int nRacialType = MyPRCGetRacialType(oTemplate);
int nShifterSize = PRCGetCreatureSize(oShifter);
if(nSize != nShifterSize)
{
bReturn = FALSE;
SendMessageToPC(oShifter, "This creature is too large or too small.");
}
if(!(nRacialType == RACIAL_TYPE_DWARF ||
nRacialType == RACIAL_TYPE_ELF ||
nRacialType == RACIAL_TYPE_GNOME ||
nRacialType == RACIAL_TYPE_HUMAN ||
nRacialType == RACIAL_TYPE_HALFORC ||
nRacialType == RACIAL_TYPE_HALFELF ||
nRacialType == RACIAL_TYPE_HALFLING ||
nRacialType == RACIAL_TYPE_HUMANOID_ORC ||
nRacialType == RACIAL_TYPE_HUMANOID_REPTILIAN
))
{
bReturn = FALSE;
SendMessageToPC(oShifter, "This creature is not a humanoid racial type.");
}
}
//Generic check
else if(nShifterType == SHIFTER_TYPE_CHANGESHAPE
|| nShifterType == SHIFTER_TYPE_ALTER_SELF
|| (nShifterType == SHIFTER_TYPE_DISGUISE_SELF && GetRacialType(oShifter) != RACIAL_TYPE_CHANGELING))
{
int nTargetSize = PRCGetCreatureSize(oTemplate);
int nTargetRacialType = MyPRCGetRacialType(oTemplate);
int nShifterSize = PRCGetCreatureSize(oShifter);
int nShifterRacialType = MyPRCGetRacialType(oShifter);
int nSizeDiff = nTargetSize - nShifterSize;
if(nSizeDiff > 1 || nSizeDiff < -1)
{
bReturn = FALSE;
SendMessageToPC(oShifter, "This creature is too large or too small.");
}
if(!(nTargetRacialType == nShifterRacialType ||
//check for humanoid type
((nTargetRacialType == RACIAL_TYPE_DWARF ||
nTargetRacialType == RACIAL_TYPE_ELF ||
nTargetRacialType == RACIAL_TYPE_GNOME ||
nTargetRacialType == RACIAL_TYPE_HUMAN ||
nTargetRacialType == RACIAL_TYPE_HALFORC ||
nTargetRacialType == RACIAL_TYPE_HALFELF ||
nTargetRacialType == RACIAL_TYPE_HALFLING ||
nTargetRacialType == RACIAL_TYPE_HUMANOID_ORC ||
nTargetRacialType == RACIAL_TYPE_HUMANOID_REPTILIAN
) &&
(nShifterRacialType == RACIAL_TYPE_DWARF ||
nShifterRacialType == RACIAL_TYPE_ELF ||
nShifterRacialType == RACIAL_TYPE_GNOME ||
nShifterRacialType == RACIAL_TYPE_HUMAN ||
nShifterRacialType == RACIAL_TYPE_HALFORC ||
nShifterRacialType == RACIAL_TYPE_HALFELF ||
nShifterRacialType == RACIAL_TYPE_HALFLING ||
nShifterRacialType == RACIAL_TYPE_HUMANOID_ORC ||
nShifterRacialType == RACIAL_TYPE_HUMANOID_REPTILIAN
))
))
{
bReturn = FALSE;
SendMessageToPC(oShifter, "This creature is a different racial type.");
}
}
}// end if - Check shifting list specific stuff
}
// Failed one of the basic checks
else
bReturn = FALSE;
return bReturn;
}
int ShiftIntoCreature(object oShifter, int nShifterType, object oTemplate, int bGainSpellLikeAbilities = FALSE)
{
// Just grab the resref and move on
return ShiftIntoResRef(oShifter, nShifterType, GetResRef(oTemplate), bGainSpellLikeAbilities);
}
int ShiftIntoResRef(object oShifter, int nShifterType, string sResRef, int bGainSpellLikeAbilities = FALSE)
{
if (!GetPersistantLocalInt(oShifter, "PRC_UNI_SHIFT_SCRIPT"))
{
SetPersistantLocalInt(oShifter, "PRC_UNI_SHIFT_SCRIPT", 1);
ExecuteScript("prc_uni_shift", oShifter);
}
// Make sure there is nothing that would prevent the successful execution of the shift from happening
if(!_prc_inc_shifting_GetCanShift(oShifter))
return FALSE;
/* Create the template to shift into */
object oTemplate = _prc_inc_load_template_from_resref(sResRef, GetHitDice(oShifter));
// Make sure the template creature was successfully created. We have nothing to do if it wasn't
if(!GetIsObjectValid(oTemplate))
{
if(DEBUG) DoDebug("prc_inc_shifting: ShiftIntoResRef(): ERROR: Failed to create creature from template resref: " + sResRef);
SendMessageToPCByStrRef(oShifter, STRREF_TEMPLATE_FAILURE); // "Polymorph failed: Failed to create a template of the creature to polymorph into."
}
else
{
// See if the shifter can in fact shift into the given template
if(GetCanShiftIntoCreature(oShifter, nShifterType, oTemplate))
{
// It can - activate mutex
SetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX, TRUE);
SetLocalInt(oShifter, SHIFTER_ORIGINALMAXHP, 0);
int nShapeGeneration = GetLocalInt(oShifter, PRC_Shifter_ShapeGeneration);
SetLocalInt(oShifter, PRC_Shifter_ShapeGeneration, nShapeGeneration+1);
if (DEBUG_APPLY_PROPERTIES)
DoDebug("ShiftIntoResRef, Shifter Index: " + IntToString(nShapeGeneration+1));
// Unshift if already shifted and then proceed with shifting into the template
// Also, give other stuff 100ms to execute in between
if(GetPersistantLocalInt(oShifter, SHIFTER_ISSHIFTED_MARKER))
DelayCommand(0.1f, _prc_inc_shifting_UnShiftAux(oShifter, nShifterType, oTemplate, bGainSpellLikeAbilities));
else
DelayCommand(0.1f, _prc_inc_shifting_ShiftIntoTemplateAux(oShifter, nShifterType, oTemplate, bGainSpellLikeAbilities));
// Return that we were able to successfully start shifting
return TRUE;
}
}
// We didn't reach the success branch for some reason
return FALSE;
}
int GWSPay(object oShifter, int bEpic)
{
int nFeat = 0;
if(!bEpic)
{
// First try paying using Greater Wildshape uses
if(GetHasFeat(FEAT_PRESTIGE_SHIFTER_GWSHAPE_1, oShifter))
{
DecrementRemainingFeatUses(oShifter, FEAT_PRESTIGE_SHIFTER_GWSHAPE_1);
nFeat = FEAT_PRESTIGE_SHIFTER_GWSHAPE_1;
// If we would reach 0 uses this way, see if we could pay with Druid Wildshape uses instead
if(!GetHasFeat(FEAT_PRESTIGE_SHIFTER_GWSHAPE_1, oShifter) &&
GetPersistantLocalInt(oShifter, "PRC_Shifter_UseDruidWS") &&
GetHasFeat(FEAT_WILD_SHAPE, oShifter)
)
{
IncrementRemainingFeatUses(oShifter, FEAT_PRESTIGE_SHIFTER_GWSHAPE_1);
DecrementRemainingFeatUses(oShifter, FEAT_WILD_SHAPE);
nFeat = FEAT_WILD_SHAPE;
}
}
// Otherwise try paying with Druid Wildshape uses
else if(GetPersistantLocalInt(oShifter, "PRC_Shifter_UseDruidWS") &&
GetHasFeat(FEAT_WILD_SHAPE, oShifter)
)
{
DecrementRemainingFeatUses(oShifter, FEAT_WILD_SHAPE);
nFeat = FEAT_WILD_SHAPE;
}
}
// Epic shift, uses Epic Greater Wildshape
else if(GetHasFeat(FEAT_PRESTIGE_SHIFTER_EGWSHAPE_1, oShifter))
{
DecrementRemainingFeatUses(oShifter, FEAT_PRESTIGE_SHIFTER_EGWSHAPE_1);
nFeat = FEAT_PRESTIGE_SHIFTER_EGWSHAPE_1;
}
return nFeat;
}
void GWSRefund(object oShifter, int nRefundFeat)
{
IncrementRemainingFeatUses(oShifter, nRefundFeat);
}
int UnShift(object oShifter, int bRemovePoly = TRUE, int bIgnoreShiftingMutex = FALSE)
{
// Shifting mutex is set and we are not told to ignore it, so abort right away
if(GetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX) && !bIgnoreShiftingMutex)
{
DelayCommand(SHIFTER_MUTEX_UNSET_DELAY, SetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX, FALSE)); //In case the mutex got stuck, unstick it
return UNSHIFT_FAIL;
}
// Check for polymorph effects
int bHadPoly = FALSE;
effect eTest = GetFirstEffect(oShifter);
while(GetIsEffectValid(eTest))
{
if(GetEffectType(eTest) == EFFECT_TYPE_POLYMORPH)
// Depending on whether we are supposed to remove them or not either remove the effect or abort
if(bRemovePoly)
{
bHadPoly = UNSHIFT_FAIL;
RemoveEffect(oShifter, eTest);
}
else
return FALSE;
eTest = GetNextEffect(oShifter);
}
// Check for true form being stored
if(!GetPersistantLocalInt(oShifter, SHIFTER_TRUEAPPEARANCE))
return UNSHIFT_FAIL;
// The unshifting should always proceed succesfully from this point on, so set the mutex
SetLocalInt(oShifter, SHIFTER_SHIFT_MUTEX, TRUE);
SetLocalInt(oShifter, SHIFTER_ORIGINALMAXHP, 0);
DeletePersistantLocalString(oShifter, "PRC_SHIFTING_TEMPLATE_RESREF");
DeletePersistantLocalInt(oShifter, "PRC_SHIFTING_SHIFTER_TYPE");
int nShapeGeneration = GetLocalInt(oShifter, PRC_Shifter_ShapeGeneration);
SetLocalInt(oShifter, PRC_Shifter_ShapeGeneration, nShapeGeneration+1);
if (DEBUG_APPLY_PROPERTIES)
DoDebug("UnShift, Shifter Index: " + IntToString(nShapeGeneration+1));
// If we had a polymorph effect present, start the removal monitor
if(bHadPoly)
{
DelayCommand(0.1f, _prc_inc_shifting_UnShiftAux_SeekPolyEnd(oShifter, GetItemInSlot(INVENTORY_SLOT_CARMOUR, oShifter)));
return UNSHIFT_SUCCESS_DELAYED;
}
else
{
_prc_inc_shifting_UnShiftAux(oShifter, SHIFTER_TYPE_NONE, OBJECT_INVALID, FALSE);
DeletePersistantLocalInt(oShifter, "TempShifted");
return UNSHIFT_SUCCESS;
}
}
// Appearance data functions
struct appearancevalues GetAppearanceData(object oTemplate)
{
struct appearancevalues appval;
// The appearance type
appval.nAppearanceType = GetAppearanceType(oTemplate);
// Body parts
appval.nBodyPart_RightFoot = GetCreatureBodyPart(CREATURE_PART_RIGHT_FOOT, oTemplate);
appval.nBodyPart_LeftFoot = GetCreatureBodyPart(CREATURE_PART_LEFT_FOOT, oTemplate);
appval.nBodyPart_RightShin = GetCreatureBodyPart(CREATURE_PART_RIGHT_SHIN, oTemplate);
appval.nBodyPart_LeftShin = GetCreatureBodyPart(CREATURE_PART_LEFT_SHIN, oTemplate);
appval.nBodyPart_RightThigh = GetCreatureBodyPart(CREATURE_PART_RIGHT_THIGH, oTemplate);
appval.nBodyPart_LeftThight = GetCreatureBodyPart(CREATURE_PART_LEFT_THIGH, oTemplate);
appval.nBodyPart_Pelvis = GetCreatureBodyPart(CREATURE_PART_PELVIS, oTemplate);
appval.nBodyPart_Torso = GetCreatureBodyPart(CREATURE_PART_TORSO, oTemplate);
appval.nBodyPart_Belt = GetCreatureBodyPart(CREATURE_PART_BELT, oTemplate);
appval.nBodyPart_Neck = GetCreatureBodyPart(CREATURE_PART_NECK, oTemplate);
appval.nBodyPart_RightForearm = GetCreatureBodyPart(CREATURE_PART_RIGHT_FOREARM, oTemplate);
appval.nBodyPart_LeftForearm = GetCreatureBodyPart(CREATURE_PART_LEFT_FOREARM, oTemplate);
appval.nBodyPart_RightBicep = GetCreatureBodyPart(CREATURE_PART_RIGHT_BICEP, oTemplate);
appval.nBodyPart_LeftBicep = GetCreatureBodyPart(CREATURE_PART_LEFT_BICEP, oTemplate);
appval.nBodyPart_RightShoulder = GetCreatureBodyPart(CREATURE_PART_RIGHT_SHOULDER, oTemplate);
appval.nBodyPart_LeftShoulder = GetCreatureBodyPart(CREATURE_PART_LEFT_SHOULDER, oTemplate);
appval.nBodyPart_RightHand = GetCreatureBodyPart(CREATURE_PART_RIGHT_HAND, oTemplate);
appval.nBodyPart_LeftHand = GetCreatureBodyPart(CREATURE_PART_LEFT_HAND, oTemplate);
appval.nBodyPart_Head = GetCreatureBodyPart(CREATURE_PART_HEAD, oTemplate);
// Wings
appval.nWingType = GetCreatureWingType(oTemplate);
// Tail
appval.nTailType = GetCreatureTailType(oTemplate);
// Portrait ID
appval.nPortraitID = GetPortraitId(oTemplate);
// Portrait resref
appval.sPortraitResRef = GetPortraitResRef(oTemplate);
// Footstep type
appval.nFootStepType = GetFootstepType(oTemplate);
// Gender
appval.nGender = GetGender(oTemplate);
/* Commented out until 1.69
// Skin color
appval.nSkinColor = GetColor(oTemplate, COLOR_CHANNEL_SKIN);
// Hair color
appval.nHairColor = GetColor(oTemplate, COLOR_CHANNEL_HAIR);
// Tattoo 1 color
appval.nTat1Color = GetColor(oTemplate, COLOR_CHANNEL_TATTOO_1);
// Tattoo 2 color
appval.nTat2Color = GetColor(oTemplate, COLOR_CHANNEL_TATTOO_2);
*/
return appval;
}
void SetAppearanceData(object oTarget, struct appearancevalues appval)
{
//DoDebug("Setting the appearance of " + DebugObject2Str(oTarget) + "to:\n" + DebugAppearancevalues2Str(appval));
// The appearance type
SetCreatureAppearanceType(oTarget, appval.nAppearanceType);
// Body parts - Delayed, since it seems not delaying this makes the body part setting fail, instead resulting in no visible
// parts. Some interaction with SetCreatureAppearance(), maybe?
// Applies to NWN1 1.68. Kudos to Primogenitor for originally figuring this out - Ornedan
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_RIGHT_FOOT , appval.nBodyPart_RightFoot , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_LEFT_FOOT , appval.nBodyPart_LeftFoot , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_RIGHT_SHIN , appval.nBodyPart_RightShin , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_LEFT_SHIN , appval.nBodyPart_LeftShin , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_RIGHT_THIGH , appval.nBodyPart_RightThigh , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_LEFT_THIGH , appval.nBodyPart_LeftThight , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_PELVIS , appval.nBodyPart_Pelvis , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_TORSO , appval.nBodyPart_Torso , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_BELT , appval.nBodyPart_Belt , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_NECK , appval.nBodyPart_Neck , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_RIGHT_FOREARM , appval.nBodyPart_RightForearm , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_LEFT_FOREARM , appval.nBodyPart_LeftForearm , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_RIGHT_BICEP , appval.nBodyPart_RightBicep , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_LEFT_BICEP , appval.nBodyPart_LeftBicep , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_RIGHT_SHOULDER , appval.nBodyPart_RightShoulder , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_LEFT_SHOULDER , appval.nBodyPart_LeftShoulder , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_RIGHT_HAND , appval.nBodyPart_RightHand , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_LEFT_HAND , appval.nBodyPart_LeftHand , oTarget));
DelayCommand(1.0f, SetCreatureBodyPart(CREATURE_PART_HEAD , appval.nBodyPart_Head , oTarget));
// Wings
SetCreatureWingType(appval.nWingType, oTarget);
// Tail
SetCreatureTailType(appval.nTailType, oTarget);
// Footstep type
SetFootstepType(appval.nFootStepType, oTarget);
/* Portrait stuff */
// If the portrait ID is not PORTRAIT_INVALID, use it. This will also set the resref
if(appval.nPortraitID != PORTRAIT_INVALID)
SetPortraitId(oTarget, appval.nPortraitID);
// Otherwise, use the portrait resref. This will set portrait ID to PORTRAIT_INVALID
else
SetPortraitResRef(oTarget, appval.sPortraitResRef);
//replace with SetGender if 1.69 adds it
if(GetGender(oTarget) != appval.nGender && appval.nAppearanceType < 7)
{
if(GetPrimaryArcaneClass(oTarget) != CLASS_TYPE_INVALID)
SetPortraitId(oTarget, 1061); //generic wizard port
else if(GetPrimaryDivineClass(oTarget) != CLASS_TYPE_INVALID)
SetPortraitId(oTarget, 1033); //generic cleric port
else
SetPortraitId(oTarget, 1043); //generic fighter port
}
/* Commented out until 1.69
// Skin color
SetColor(oTarget, COLOR_CHANNEL_SKIN, appval.nSkinColor);
// Hair color
SetColor(oTemplate, COLOR_CHANNEL_HAIR, appval.nHairColor);
// Tattoo 2 color
SetColor(oTemplate, COLOR_CHANNEL_TATTOO_1, appval.nTat1Color);
// Tattoo 1 color
SetColor(oTemplate, COLOR_CHANNEL_TATTOO_2, appval.nTat2Color);
*/
}
struct appearancevalues GetLocalAppearancevalues(object oStore, string sName)
{
struct appearancevalues appval;
// The appearance type
appval.nAppearanceType = GetPersistantLocalInt(oStore, sName + "nAppearanceType");
// Body parts
appval.nBodyPart_RightFoot = GetPersistantLocalInt(oStore, sName + "nBodyPart_RightFoot");
appval.nBodyPart_LeftFoot = GetPersistantLocalInt(oStore, sName + "nBodyPart_LeftFoot");
appval.nBodyPart_RightShin = GetPersistantLocalInt(oStore, sName + "nBodyPart_RightShin");
appval.nBodyPart_LeftShin = GetPersistantLocalInt(oStore, sName + "nBodyPart_LeftShin");
appval.nBodyPart_RightThigh = GetPersistantLocalInt(oStore, sName + "nBodyPart_RightThigh");
appval.nBodyPart_LeftThight = GetPersistantLocalInt(oStore, sName + "nBodyPart_LeftThight");
appval.nBodyPart_Pelvis = GetPersistantLocalInt(oStore, sName + "nBodyPart_Pelvis");
appval.nBodyPart_Torso = GetPersistantLocalInt(oStore, sName + "nBodyPart_Torso");
appval.nBodyPart_Belt = GetPersistantLocalInt(oStore, sName + "nBodyPart_Belt");
appval.nBodyPart_Neck = GetPersistantLocalInt(oStore, sName + "nBodyPart_Neck");
appval.nBodyPart_RightForearm = GetPersistantLocalInt(oStore, sName + "nBodyPart_RightForearm");
appval.nBodyPart_LeftForearm = GetPersistantLocalInt(oStore, sName + "nBodyPart_LeftForearm");
appval.nBodyPart_RightBicep = GetPersistantLocalInt(oStore, sName + "nBodyPart_RightBicep");
appval.nBodyPart_LeftBicep = GetPersistantLocalInt(oStore, sName + "nBodyPart_LeftBicep");
appval.nBodyPart_RightShoulder = GetPersistantLocalInt(oStore, sName + "nBodyPart_RightShoulder");
appval.nBodyPart_LeftShoulder = GetPersistantLocalInt(oStore, sName + "nBodyPart_LeftShoulder");
appval.nBodyPart_RightHand = GetPersistantLocalInt(oStore, sName + "nBodyPart_RightHand");
appval.nBodyPart_LeftHand = GetPersistantLocalInt(oStore, sName + "nBodyPart_LeftHand");
appval.nBodyPart_Head = GetPersistantLocalInt(oStore, sName + "nBodyPart_Head");
// Wings
appval.nWingType = GetPersistantLocalInt(oStore, sName + "nWingType");
// Tail
appval.nTailType = GetPersistantLocalInt(oStore, sName + "nTailType");
// Portrait ID
appval.nPortraitID = GetPersistantLocalInt(oStore, sName + "nPortraitID");
// Portrait resref
appval.sPortraitResRef = GetPersistantLocalString(oStore, sName + "sPortraitResRef");
// Footstep type
appval.nFootStepType = GetPersistantLocalInt(oStore, sName + "nFootStepType");
// Gender
appval.nGender = GetPersistantLocalInt(oStore, sName + "nGender");
// Skin color
appval.nSkinColor = GetPersistantLocalInt(oStore, sName + "nSkinColor");
// Hair color
appval.nHairColor = GetPersistantLocalInt(oStore, sName + "nHairColor");
// Tattoo 1 color
appval.nTat1Color = GetPersistantLocalInt(oStore, sName + "nTat1Color");
// Tattoo 2 color
appval.nTat2Color = GetPersistantLocalInt(oStore, sName + "nTat2Color");
return appval;
}
void SetLocalAppearancevalues(object oStore, string sName, struct appearancevalues appval)
{
// The appearance type
SetPersistantLocalInt(oStore, sName + "nAppearanceType" , appval.nAppearanceType );
// Body parts
SetPersistantLocalInt(oStore, sName + "nBodyPart_RightFoot" , appval.nBodyPart_RightFoot );
SetPersistantLocalInt(oStore, sName + "nBodyPart_LeftFoot" , appval.nBodyPart_LeftFoot );
SetPersistantLocalInt(oStore, sName + "nBodyPart_RightShin" , appval.nBodyPart_RightShin );
SetPersistantLocalInt(oStore, sName + "nBodyPart_LeftShin" , appval.nBodyPart_LeftShin );
SetPersistantLocalInt(oStore, sName + "nBodyPart_RightThigh" , appval.nBodyPart_RightThigh );
SetPersistantLocalInt(oStore, sName + "nBodyPart_LeftThight" , appval.nBodyPart_LeftThight );
SetPersistantLocalInt(oStore, sName + "nBodyPart_Pelvis" , appval.nBodyPart_Pelvis );
SetPersistantLocalInt(oStore, sName + "nBodyPart_Torso" , appval.nBodyPart_Torso );
SetPersistantLocalInt(oStore, sName + "nBodyPart_Belt" , appval.nBodyPart_Belt );
SetPersistantLocalInt(oStore, sName + "nBodyPart_Neck" , appval.nBodyPart_Neck );
SetPersistantLocalInt(oStore, sName + "nBodyPart_RightForearm" , appval.nBodyPart_RightForearm );
SetPersistantLocalInt(oStore, sName + "nBodyPart_LeftForearm" , appval.nBodyPart_LeftForearm );
SetPersistantLocalInt(oStore, sName + "nBodyPart_RightBicep" , appval.nBodyPart_RightBicep );
SetPersistantLocalInt(oStore, sName + "nBodyPart_LeftBicep" , appval.nBodyPart_LeftBicep );
SetPersistantLocalInt(oStore, sName + "nBodyPart_RightShoulder", appval.nBodyPart_RightShoulder );
SetPersistantLocalInt(oStore, sName + "nBodyPart_LeftShoulder" , appval.nBodyPart_LeftShoulder );
SetPersistantLocalInt(oStore, sName + "nBodyPart_RightHand" , appval.nBodyPart_RightHand );
SetPersistantLocalInt(oStore, sName + "nBodyPart_LeftHand" , appval.nBodyPart_LeftHand );
SetPersistantLocalInt(oStore, sName + "nBodyPart_Head" , appval.nBodyPart_Head );
// Wings
SetPersistantLocalInt(oStore, sName + "nWingType" , appval.nWingType );
// Tail
SetPersistantLocalInt(oStore, sName + "nTailType" , appval.nTailType );
// Portrait ID
SetPersistantLocalInt(oStore, sName + "nPortraitID" , appval.nPortraitID );
// Portrait resref
SetPersistantLocalString(oStore, sName + "sPortraitResRef" , appval.sPortraitResRef );
// Footstep type
SetPersistantLocalInt(oStore, sName + "nFootStepType" , appval.nFootStepType );
//Gender
SetPersistantLocalInt(oStore, sName + "nGender" , appval.nGender );
// Skin color
SetPersistantLocalInt(oStore, sName + "nSkinColor" , appval.nSkinColor );
// Hair color
SetPersistantLocalInt(oStore, sName + "nHairColor" , appval.nHairColor );
// Tattoo 1 color
SetPersistantLocalInt(oStore, sName + "nTat1Color" , appval.nTat1Color );
// Tattoo 2 color
SetPersistantLocalInt(oStore, sName + "nTat2Color" , appval.nTat2Color );
}
void DeleteLocalAppearancevalues(object oStore, string sName)
{
// The appearance type
DeletePersistantLocalInt(oStore, sName + "nAppearanceType");
// Body parts
DeletePersistantLocalInt(oStore, sName + "nBodyPart_RightFoot");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_LeftFoot");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_RightShin");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_LeftShin");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_RightThigh");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_LeftThight");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_Pelvis");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_Torso");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_Belt");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_Neck");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_RightForearm");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_LeftForearm");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_RightBicep");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_LeftBicep");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_RightShoulder");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_LeftShoulder");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_RightHand");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_LeftHand");
DeletePersistantLocalInt(oStore, sName + "nBodyPart_Head");
// Wings
DeletePersistantLocalInt(oStore, sName + "nWingType");
// Tail
DeletePersistantLocalInt(oStore, sName + "nTailType");
// Portrait ID
DeletePersistantLocalInt(oStore, sName + "nPortraitID");
// Portrait resref
DeletePersistantLocalString(oStore, sName + "sPortraitResRef");
// Footstep type
DeletePersistantLocalInt(oStore, sName + "nFootStepType");
// Gender
DeletePersistantLocalInt(oStore, sName + "nGender");
// Skin color
DeletePersistantLocalInt(oStore, sName + "nSkinColor");
// Hair color
DeletePersistantLocalInt(oStore, sName + "nHairColor");
// Tattoo 1 color
DeletePersistantLocalInt(oStore, sName + "nTat1Color");
// Tattoo 2 color
DeletePersistantLocalInt(oStore, sName + "nTat2Color");
}
struct appearancevalues GetPersistantLocalAppearancevalues(object oStore, string sName)
{
return GetLocalAppearancevalues(oStore, sName);
}
void SetPersistantLocalAppearancevalues(object oStore, string sName, struct appearancevalues appval)
{
SetLocalAppearancevalues(oStore, sName, appval);
}
void DeletePersistantLocalAppearancevalues(object oStore, string sName)
{
DeleteLocalAppearancevalues(oStore, sName);
}
void ForceUnshift(object oShifter, int nShiftedNumber)
{
if(GetPersistantLocalInt(oShifter, "nTimesShifted") == nShiftedNumber)
UnShift(oShifter);
}
string DebugAppearancevalues2Str(struct appearancevalues appval)
{
return "Appearance type = " + IntToString(appval.nAppearanceType) + "\n"
+ "Body part - Right Foot = " + IntToString(appval.nBodyPart_RightFoot ) + "\n"
+ "Body part - Left Foot = " + IntToString(appval.nBodyPart_LeftFoot ) + "\n"
+ "Body part - Right Shin = " + IntToString(appval.nBodyPart_RightShin ) + "\n"
+ "Body part - Left Shin = " + IntToString(appval.nBodyPart_LeftShin ) + "\n"
+ "Body part - Right Thigh = " + IntToString(appval.nBodyPart_RightThigh ) + "\n"
+ "Body part - Left Thigh = " + IntToString(appval.nBodyPart_LeftThight ) + "\n"
+ "Body part - Pelvis = " + IntToString(appval.nBodyPart_Pelvis ) + "\n"
+ "Body part - Torso = " + IntToString(appval.nBodyPart_Torso ) + "\n"
+ "Body part - Belt = " + IntToString(appval.nBodyPart_Belt ) + "\n"
+ "Body part - Neck = " + IntToString(appval.nBodyPart_Neck ) + "\n"
+ "Body part - Right Forearm = " + IntToString(appval.nBodyPart_RightForearm ) + "\n"
+ "Body part - Left Forearm = " + IntToString(appval.nBodyPart_LeftForearm ) + "\n"
+ "Body part - Right Bicep = " + IntToString(appval.nBodyPart_RightBicep ) + "\n"
+ "Body part - Left Bicep = " + IntToString(appval.nBodyPart_LeftBicep ) + "\n"
+ "Body part - Right Shoulder = " + IntToString(appval.nBodyPart_RightShoulder) + "\n"
+ "Body part - Left Shoulder = " + IntToString(appval.nBodyPart_LeftShoulder ) + "\n"
+ "Body part - Right Hand = " + IntToString(appval.nBodyPart_RightHand ) + "\n"
+ "Body part - Left Hand = " + IntToString(appval.nBodyPart_LeftHand ) + "\n"
+ "Body part - Head = " + IntToString(appval.nBodyPart_Head ) + "\n"
+ "Wings = " + IntToString(appval.nWingType) + "\n"
+ "Tail = " + IntToString(appval.nTailType) + "\n"
+ "Portrait ID = " + (appval.nPortraitID == PORTRAIT_INVALID ? "PORTRAIT_INVALID" : IntToString(appval.nPortraitID)) + "\n"
+ "Portrait ResRef = " + appval.sPortraitResRef + "\n"
+ "Footstep type = " + IntToString(appval.nFootStepType) + "\n"
+ "Gender = " + IntToString(appval.nGender) + "\n"
+ "Skin color = " + IntToString(appval.nSkinColor) + "\n"
+ "Hair color = " + IntToString(appval.nHairColor) + "\n"
+ "Tattoo 1 color = " + IntToString(appval.nTat1Color) + "\n"
+ "Tattoo 2 color = " + IntToString(appval.nTat2Color) + "\n"
;
}
int IsPolymorphed(object oPC)
{
effect eTest = GetFirstEffect(oPC);
while(GetIsEffectValid(eTest))
{
if(GetEffectType(eTest) == EFFECT_TYPE_POLYMORPH)
return TRUE;
eTest = GetNextEffect(oPC);
}
return FALSE;
}
void HandleTrueShape(object oPC)
{
if(!GetPersistantLocalInt(oPC, SHIFTER_TRUEAPPEARANCE))
StoreCurrentAppearanceAsTrueAppearance(oPC, TRUE);
if(!GetPersistantLocalInt(oPC, SHIFTER_TRUE_RACE))
StoreCurrentRaceAsTrueRace(oPC);
}
void HandleApplyShiftEffects(object oPC)
{
if (IsPolymorphed(oPC))
return;
DelayCommand(0.0f, _prc_inc_shifting_ApplyEffects(oPC, TRUE));
}
void HandleApplyShiftTemplate(object oPC)
{
if (IsPolymorphed(oPC))
return;
string sResRef;
int nShifterType;
int nShapeGeneration;
object oTemplate;
if(GetLocalInt(oPC, SHIFTER_SHIFT_MUTEX))
{
//If shifting, the following is already being handled
//by the shifting code so don't do it again here.
return;
}
sResRef = GetPersistantLocalString(oPC, "PRC_SHIFTING_TEMPLATE_RESREF");
if(sResRef != "")
{
oTemplate = _prc_inc_load_template_from_resref(sResRef, GetHitDice(oPC));
if(GetIsObjectValid(oTemplate))
{
nShifterType = GetPersistantLocalInt(oPC, "PRC_SHIFTING_SHIFTER_TYPE");
nShapeGeneration = GetLocalInt(oPC, PRC_Shifter_ShapeGeneration);
if (DEBUG_APPLY_PROPERTIES)
DoDebug("HandleApplyShiftTemplate, Shifter Index: " + IntToString(nShapeGeneration));
DelayCommand(0.0f, _prc_inc_shifting_ApplyTemplate(oPC, nShapeGeneration, nShifterType, oTemplate, FALSE, GetPCSkin(oPC)));
}
}
}
int PnPShifterFeats()
{
if(GetPRCSwitch(PRC_NWNX_FUNCS))
{
//If any stats have been changed by NWNX, this could qualify the PC for feats they should
//not actually qualify for, so force unshifting before levelling up.
if(GetPersistantLocalInt(OBJECT_SELF, "Shifting_NWNXSTRAdjust")
|| GetPersistantLocalInt(OBJECT_SELF, "Shifting_NWNXDEXAdjust")
|| GetPersistantLocalInt(OBJECT_SELF, "Shifting_NWNXCONAdjust"))
{
FloatingTextStringOnCreature("You must unshift before levelling up.", OBJECT_SELF, FALSE); //TODO: TLK entry
return TRUE;
}
}
return FALSE;
}
// Test main
// void main(){}