3112 lines
140 KiB
Plaintext
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(){}
|