1579 lines
51 KiB
Plaintext
1579 lines
51 KiB
Plaintext
//:://////////////////////////////////////////////////
|
|
//:: X0_I0_HENCHMAN
|
|
//:: Copyright (c) 2002 Floodgate Entertainment
|
|
//:://////////////////////////////////////////////////
|
|
/*
|
|
|
|
* MODIFIED April 2/03
|
|
* Make is so when a henchmen is hired he automatically levels up to
|
|
* the level of his master
|
|
|
|
* MODIFIED 2/4/2000
|
|
* Added a hack to HireHenchman so that it will skip some problem areas
|
|
* that might surface with my 'keeping the henchman' stuff in the death script.
|
|
* Felt it was worth it because now you can use potions to bring the henchman back.
|
|
|
|
* MODIFIED 1/31/2003
|
|
* Removed the SetAdditionalListeningPatterns function because this
|
|
* library will now be included in x0_inc_henai, so we can just include
|
|
* that and then use bkSetListeningPatterns in the henchman spawn script.
|
|
*
|
|
* MODIFIED 1/3/2003
|
|
* Removed personal item code and added code to handle setting up
|
|
* henchmen using campaign variables instead of local variables,
|
|
* so the information will persist between modules. Also added
|
|
* code for storing/retrieving henchman as you move between sequel
|
|
* modules.
|
|
*
|
|
* MODIFIED 12/6/2002
|
|
* Added functions to handle henchman death. Henchmen do not die normally;
|
|
* they are insta-resurrected to 1 HP and are kept there, playing the 'dead'
|
|
* animation, until time runs out, the master flees the area/dies, or the
|
|
* master heals the henchman.
|
|
*
|
|
* MODIFIED 11/16/2002
|
|
* Added a new function, "SetAdditionalListeningPatterns", to set the
|
|
* added listening patterns for the new henchman AI from Bioware.
|
|
* Since only our OnSpawn script needs to be different, it's easier to
|
|
* duplicate this one function.
|
|
*
|
|
* IMPORTANT NOTE:
|
|
* This include file REPLACES the original henchman include file from
|
|
* campaign one; both should not be included in the same script.
|
|
* Many functions here have the same names/parameters as functions
|
|
* from Bioware's henchman include file to facilitate code reuse,
|
|
* so dual includes will result in major compile errors due to
|
|
* function redefinition.
|
|
*
|
|
* ** Function behavior may and does differ from the originals. **
|
|
*
|
|
* The primary difference is that X0 henchmen can be hired by
|
|
* more than one person during the course of the game. The
|
|
* current master of the henchman will be stored in a local var
|
|
* on the henchman itself; campaign variables on the player will
|
|
* indicate whether the player has ever hired the henchman.
|
|
*
|
|
*/
|
|
//:://////////////////////////////////////////////////
|
|
//:: Created By: Naomi Novik
|
|
//:: Created On: 09/12/2002
|
|
//:://////////////////////////////////////////////////
|
|
|
|
#include "x0_i0_common"
|
|
#include "nw_i0_plot"
|
|
#include "nw_i0_generic"
|
|
#include "nw_i0_spells"
|
|
|
|
|
|
/**********************************************************************
|
|
* CONSTANTS
|
|
**********************************************************************/
|
|
|
|
/**** Number of henchmen ****/
|
|
const int X0_NUMBER_HENCHMEN = 3;
|
|
|
|
const int X2_NUMBER_HENCHMEN = 2; // This won't be the same as the GetMaxHenchmen() function due to followers
|
|
|
|
|
|
/**** XP1 Henchmen tags ****/
|
|
const string sDeekin = "X0_HEN_DEE";
|
|
const string sDorna = "X0_HEN_DOR";
|
|
const string sXandos = "X0_HEN_XAN";
|
|
|
|
/**** variable names and suffixes ****/
|
|
const string sHenchmanDeathVarname = "NW_L_HEN_I_DIED";
|
|
const string sIsHiredVarname = "X0_IS_CURRENTLY_HIRED";
|
|
const string sLastMasterVarname = "X0_LAST_MASTER_TAG";
|
|
const string sHenchmanKilledSuffix = "_GOTKILLED";
|
|
const string sHenchmanResurrectedSuffix = "_RESURRECTED";
|
|
const string sHenchmanDyingVarname = "X0_HEN_IS_DYING";
|
|
const string sStoredHenchmanVarname = "X0_HEN_STORED";
|
|
|
|
// Amount of time to pass between respawn checks
|
|
const float DELAY_BETWEEN_RESPAWN_CHECKS = 3.0f;
|
|
|
|
// * duration henchmen play their die animations
|
|
const float HENCHMEN_DIE_ANIM_DURATION = 6500000000.0f;
|
|
|
|
// The maximum length of the wait before respawn
|
|
const float MAX_RESPAWN_WAIT = 60.0f;
|
|
|
|
/**** Script names ****/
|
|
const string sGoHomeScript = "x0_ch_hen_gohome";
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* FUNCTION PROTOTYPES
|
|
**********************************************************************/
|
|
|
|
/**** GENERAL FUNCTIONS ****/
|
|
|
|
// Copy all henchman-related local variables from source to target.
|
|
// Used when henchmen level up to keep variables consistent between
|
|
// the two copies of the henchman.
|
|
// This is a good function to look at to see what the local variables
|
|
// used on henchmen are.
|
|
void CopyHenchmanLocals(object oSource, object oTarget);
|
|
|
|
/**** GREETING & MEETING FUNCTIONS ****/
|
|
|
|
// Use when the player first meets the henchman/NPC
|
|
// This uses local variables, not campaign variables.
|
|
void SetHasMet(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Returns TRUE if the player has met this henchman
|
|
// This uses local variables, not campaign variables.
|
|
int GetHasMet(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
|
|
/**** HIRING FUNCTIONS ****/
|
|
|
|
// Can be used for both initial hiring and rejoining.
|
|
void HireHenchman(object oPC, object oHench=OBJECT_SELF, int bAdd=TRUE);
|
|
|
|
// Use to fire the henchman
|
|
void FireHenchman(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Used when the henchman quits
|
|
void QuitHenchman(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Returns TRUE if the henchman is currently hired
|
|
int GetIsHired(object oHench=OBJECT_SELF);
|
|
|
|
// Set the last master
|
|
void SetLastMaster(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Returns the last master of this henchman (useful for death situations)
|
|
object GetLastMaster(object oHench=OBJECT_SELF);
|
|
|
|
// Indicate whether the player has ever hired this henchman
|
|
void SetPlayerHasHired(object oPC, object oHench=OBJECT_SELF, int bHired=TRUE);
|
|
|
|
// Determine whether the player has ever hired this henchman
|
|
int GetPlayerHasHired(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Indicate whether the player has ever hired this henchman in this
|
|
// campaign.
|
|
void SetPlayerHasHiredInCampaign(object oPC, object oHench=OBJECT_SELF, int bHired=TRUE);
|
|
|
|
// Indicate whether the player has ever hired this henchman in this
|
|
// campaign.
|
|
int GetPlayerHasHiredInCampaign(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Determine whether the henchman is currently working
|
|
// for this PC.
|
|
int GetWorkingForPlayer(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Set whether the henchman quit this player's employ
|
|
void SetDidQuit(object oPC, object oHench=OBJECT_SELF, int bQuit=TRUE);
|
|
|
|
// Determine if the henchman quit
|
|
int GetDidQuit(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
/**** LEVELING UP ****/
|
|
|
|
// Checks to see if the henchman can level up.
|
|
// Can only level up if player is 2 or more levels
|
|
// higher than henchman.
|
|
// MAX = Level 14
|
|
int GetCanLevelUp(object oPC, object oHench = OBJECT_SELF);
|
|
|
|
// Levels the henchman up to be one level less than player.
|
|
// Returns the new creature.
|
|
object DoLevelUp(object oPC, object oHench = OBJECT_SELF);
|
|
|
|
// Store all items in the henchman's inventory in the campaign DB,
|
|
// skipping those items which have the henchman's tag in their
|
|
// name.
|
|
// This is paired with RetrieveHenchmanItems for the leveling-up
|
|
// process.
|
|
void StoreHenchmanItems(object oPC, object oHench);
|
|
|
|
// Retrieve (and then delete) all henchman inventory items out of
|
|
// the campaign DB, putting them in the inventory of the henchman.
|
|
// This is paired with StoreHenchmanItems for the leveling-up
|
|
// process.
|
|
void RetrieveHenchmanItems(object oPC, object oHench);
|
|
|
|
/*** DEATH FUNCTIONS ***/
|
|
// * Wrapper function added to fix bugs in the dying-state
|
|
// * process. Need to figure out whenever his value changes.
|
|
void SetHenchmanDying(object oHench=OBJECT_SELF, int bIsDying=TRUE);
|
|
|
|
|
|
// Set on the henchman to indicate s/he died; can also be used to
|
|
// unset this variable.
|
|
void SetDidDie(int bDie=TRUE, object oHench=OBJECT_SELF);
|
|
|
|
// Returns TRUE if the henchman died.
|
|
// UNLIKE original, does NOT reset the value -- use
|
|
// SetDidDie(FALSE) to do that.
|
|
int GetDidDie(object oHench=OBJECT_SELF);
|
|
|
|
// Set got killed
|
|
void SetKilled(object oPC, object oHench=OBJECT_SELF, int bKilled=TRUE);
|
|
|
|
// Determine if this PC got the henchman killed
|
|
int GetKilled(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Set that this PC resurrected the henchman
|
|
void SetResurrected(object oPC, object oHench=OBJECT_SELF, int bResurrected=TRUE);
|
|
|
|
// Determine if this PC resurrected the henchman
|
|
int GetResurrected(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Respawn the henchman, by preference at the master's current
|
|
// respawn point, or at the henchman's starting location otherwise.
|
|
void RespawnHenchman(object oHench=OBJECT_SELF);
|
|
|
|
// Keep dead by playing the appropriate death animation for the
|
|
// maximum wait until respawn.
|
|
void KeepDead(object oHench=OBJECT_SELF);
|
|
|
|
// Stop keeping dead by playing the 'woozy' standing animation.
|
|
void StopKeepingDead(object oHench=OBJECT_SELF);
|
|
|
|
// Raise and freeze henchman to 1 hp so s/he can be stabilized
|
|
void RaiseForRespawn(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// See if our maximum wait time has passed
|
|
int GetHasMaxWaitPassed(int nChecks);
|
|
|
|
// Do the checking to see if we respawn -- this function works
|
|
// in a circle with DoRespawnCheck.
|
|
void RespawnCheck(object oPC, int nChecks=0, object oHench=OBJECT_SELF);
|
|
|
|
// Perform a single respawn check -- this function works
|
|
// in a circle with RespawnCheck.
|
|
void DoRespawnCheck(object oPC, int nChecks, object oHench=OBJECT_SELF);
|
|
|
|
// This function actually invokes the respawn.
|
|
void DoRespawn(object oPC, object oHench=OBJECT_SELF);
|
|
|
|
// Set up before the respawn
|
|
void PreRespawnSetup(object oHench=OBJECT_SELF);
|
|
|
|
// Clean up after the respawn.
|
|
void PostRespawnCleanup(object oHench=OBJECT_SELF);
|
|
|
|
// Determine if this henchman is currently dying
|
|
int GetIsHenchmanDying(object oHench=OBJECT_SELF);
|
|
|
|
// levels up the henchman assigned to oPC
|
|
void LevelUpXP1Henchman(object oPC);
|
|
|
|
|
|
/***** MODULE TRANSFER FUNCTIONS *****/
|
|
|
|
// Call this function when the PC is about to leave a module
|
|
// to enable restoration of the henchman on re-entry into the
|
|
// sequel module. Both modules must use the same campaign db
|
|
// for this to work.
|
|
void StoreCampaignHenchman(object oPC);
|
|
|
|
// Call this function when a PC enters a sequel module to restore
|
|
// the henchman (complete with inventory). The function
|
|
// StoreCampaignHenchman must have been called first, and both
|
|
// modules must use the same campaign db. (See notes in x0_i0_campaign.)
|
|
//
|
|
// The restored henchman will automatically be re-hired and will be
|
|
// created next to the PC.
|
|
void RetrieveCampaignHenchman(object oPC);
|
|
|
|
// Levels a henchman up to the given level, alternating
|
|
// between the first and second classes if they are multiclassed.
|
|
void LevelHenchmanUpTo(object oHenchman, int nLevel, int nClass2=CLASS_TYPE_INVALID, int nMaxLevelInSecondClass=0, int nPackageClass1=PACKAGE_INVALID, int nPackageClass2=PACKAGE_INVALID);
|
|
|
|
// *returns true if oHench is a follower
|
|
int GetIsFollower(object oHench);
|
|
// * sets whether oHench is a follower or not
|
|
void SetIsFollower(object oHench, int bValue=TRUE);
|
|
// * removes all followers
|
|
// * if bRemoveAll=TRUe then remove normal hencies too
|
|
void RemoveAllFollowers(object oPC, int bRemoveAll = FALSE);
|
|
|
|
/**********************************************************************
|
|
* FUNCTION DEFINITIONS
|
|
**********************************************************************/
|
|
|
|
// * had to add this commandable wrapper to track down a bug in the henchmen
|
|
void WrapCommandable(int bCommand, object oHench)
|
|
{
|
|
/* string s ="";
|
|
if (bCommand)
|
|
s = "TRUE";
|
|
else
|
|
s = "FALSE";
|
|
SendMessageToPC(GetFirstPC(), GetName(OBJECT_SELF) + " commandable set to " + s);*/
|
|
while (GetCommandable(oHench) != bCommand)
|
|
{
|
|
SetCommandable(bCommand, oHench);
|
|
}
|
|
}
|
|
|
|
void brentDebug(string s)
|
|
{
|
|
// SendMessageToPC(GetFirstPC(), s);
|
|
}
|
|
|
|
/**** GENERAL FUNCTIONS ****/
|
|
|
|
// Copy all henchman-related local variables from source to target.
|
|
// Used when henchmen level up to keep variables consistent between
|
|
// the two copies of the henchman.
|
|
// This is a good function to look at to see what the local variables
|
|
// used on henchmen are.
|
|
void CopyHenchmanLocals(object oSource, object oTarget)
|
|
{
|
|
if ( !GetIsObjectValid(oTarget) || !GetIsObjectValid(oSource))
|
|
return;
|
|
|
|
// This copies over our current associate state, so we
|
|
// keep whatever settings we had before.
|
|
SetLocalInt(oTarget,
|
|
sAssociateMasterConditionVarname,
|
|
GetLocalInt(oSource, sAssociateMasterConditionVarname));
|
|
|
|
}
|
|
|
|
|
|
/**** GREETING & MEETING FUNCTIONS ****/
|
|
|
|
// Use when the player first meets the henchman
|
|
void SetHasMet(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
SetBooleanValue(oPC, GetTag(oHench) + sHasMetSuffix);
|
|
}
|
|
|
|
// Returns TRUE if the player has met this henchman
|
|
int GetHasMet(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
return GetBooleanValue(oPC, GetTag(oHench) + sHasMetSuffix);
|
|
}
|
|
|
|
|
|
/**** HIRING FUNCTIONS ****/
|
|
|
|
// *returns true if oHench is a follower
|
|
int GetIsFollower(object oHench)
|
|
{
|
|
return GetLocalInt(oHench, "X2_JUST_A_FOLLOWER");
|
|
}
|
|
// * sets whether oHench is a follower or not
|
|
void SetIsFollower(object oHench, int bValue=TRUE)
|
|
{
|
|
SetLocalInt(oHench, "X2_JUST_A_FOLLOWER", bValue);
|
|
}
|
|
|
|
// * removes all followers
|
|
// * if bRemoveAll=true, it removes
|
|
// * all henchmen.
|
|
void RemoveAllFollowers(object oPC, int bRemoveAll = FALSE)
|
|
{
|
|
//int bDone = FALSE;
|
|
object oHench = OBJECT_INVALID;
|
|
int i = 0;
|
|
int j;
|
|
|
|
// * have to count down because creatures are being deleted
|
|
for (j=10; j>0; j--)
|
|
{
|
|
oHench = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oPC, j);
|
|
if (GetIsObjectValid(oHench) == TRUE)
|
|
{
|
|
// * if the creature is marked as a follower
|
|
// * dump them
|
|
if ( (GetIsFollower(oHench) == TRUE) || (bRemoveAll == TRUE))
|
|
{
|
|
// * Take them out of stealth mode too (Nov 1 - BK)
|
|
SetActionMode(oHench, ACTION_MODE_STEALTH, FALSE);
|
|
// * Remove invisibility type effects off of henchmen (Nov 7 - BK)
|
|
RemoveSpellEffects(SPELL_INVISIBILITY, oHench, oHench);
|
|
RemoveSpellEffects(SPELL_IMPROVED_INVISIBILITY, oHench, oHench);
|
|
RemoveSpellEffects(SPELL_SANCTUARY, oHench, oHench);
|
|
RemoveSpellEffects(SPELL_ETHEREALNESS, oHench, oHench);
|
|
|
|
FireHenchman(oPC, oHench);
|
|
//bDone = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// * count number of henchman
|
|
// * if nFollowersInstead = TRUE then count the # of
|
|
int X2_GetNumberOfHenchmen(object oPC, int bFollowersInstead=FALSE)
|
|
{
|
|
int i = 1;
|
|
int nCount = 0;
|
|
int bDone = FALSE;
|
|
object oHench = OBJECT_INVALID;
|
|
|
|
do
|
|
{
|
|
oHench = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oPC, i);
|
|
i++;
|
|
if (GetIsObjectValid(oHench) == TRUE)
|
|
{
|
|
// * if the creature is marked as a follower
|
|
// * they do not count against the henchman limit
|
|
if (bFollowersInstead == FALSE && GetIsFollower(oHench) == FALSE)
|
|
nCount++;
|
|
else
|
|
if (bFollowersInstead == TRUE && GetIsFollower(oHench) == TRUE)
|
|
nCount++;
|
|
}
|
|
else
|
|
{
|
|
bDone = TRUE;
|
|
}
|
|
}
|
|
while (bDone == FALSE);
|
|
|
|
return nCount;
|
|
|
|
}
|
|
|
|
// * Fires the first henchman who is not
|
|
// * a follower
|
|
void X2_FireFirstHenchman(object oPC)
|
|
{
|
|
int i = 1;
|
|
int bDone = FALSE;
|
|
object oHench = OBJECT_INVALID;
|
|
|
|
do
|
|
{
|
|
oHench = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oPC, i);
|
|
i++;
|
|
if (GetIsObjectValid(oHench) == TRUE)
|
|
{
|
|
// * if the creature is marked as a follower
|
|
// * they do not count against the henchman limit
|
|
if (GetIsFollower(oHench) == FALSE)
|
|
{
|
|
FireHenchman(oPC, oHench);
|
|
bDone = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bDone = TRUE;
|
|
}
|
|
}
|
|
while (bDone == FALSE);
|
|
|
|
|
|
}
|
|
// Can be used for both initial hiring and rejoining.
|
|
void HireHenchman(object oPC, object oHench=OBJECT_SELF, int bAdd=TRUE)
|
|
{
|
|
if ( !GetIsObjectValid(oPC) || !GetIsObjectValid(oHench) )
|
|
{
|
|
return;
|
|
}
|
|
// SpawnScriptDebugger();
|
|
|
|
// Fire the PC's former henchman if necessary
|
|
// object oFormerHench = GetAss*ociate(ASSOCIATE_TYPE_HENCHMAN, oPC, 1);
|
|
int nCountHenchmen = X2_GetNumberOfHenchmen(oPC);
|
|
int nNumberOfFollowers = X2_GetNumberOfHenchmen(oPC, TRUE);
|
|
// * The true number of henchmen are the number of hired
|
|
nCountHenchmen = nCountHenchmen ;
|
|
|
|
int nMaxHenchmen = X2_NUMBER_HENCHMEN;
|
|
|
|
// Adding this henchman would exceed the module imposed
|
|
// henchman limit.
|
|
// Fire the first henchman
|
|
// The third slot is reserved for the follower
|
|
if ( (nCountHenchmen >= nMaxHenchmen) && bAdd == TRUE)
|
|
{
|
|
X2_FireFirstHenchman(oPC);
|
|
}
|
|
|
|
/* if (GetIsObjectValid(oFormerHench) && bAdd == TRUE)
|
|
{
|
|
DBG_msg("Firing former henchman");
|
|
FireHenchman(oPC, oFormerHench);
|
|
}
|
|
else
|
|
{
|
|
DBG_msg("No valid former henchman");
|
|
}
|
|
*/
|
|
// Mark the henchman as working for the given player
|
|
if (!GetPlayerHasHired(oPC, oHench))
|
|
{
|
|
// This keeps track if the player has EVER hired this henchman
|
|
// Floodgate only (XP1). Should never store info to a database as game runs, only between modules or in Persistent setting
|
|
if (GetLocalInt(GetModule(), "X2_L_XP2") != 1)
|
|
{
|
|
SetPlayerHasHiredInCampaign(oPC, oHench);
|
|
}
|
|
SetPlayerHasHired(oPC, oHench);
|
|
}
|
|
SetLastMaster(oPC, oHench);
|
|
|
|
// Clear the 'quit' setting in case we just persuaded
|
|
// the henchman to rejoin us.
|
|
SetDidQuit(oPC, oHench, FALSE);
|
|
|
|
// If we're hooking back up with the henchman after s/he
|
|
// died, clear that.
|
|
SetDidDie(FALSE, oHench);
|
|
SetKilled(oPC, oHench, FALSE);
|
|
SetResurrected(oPC, oHench, FALSE);
|
|
|
|
// Turn on standard henchman listening patterns
|
|
SetAssociateListenPatterns(oHench);
|
|
|
|
// By default, companions come in with Attack Nearest and Follow
|
|
// modes enabled.
|
|
SetLocalInt(oHench,
|
|
"NW_COM_MODE_COMBAT",ASSOCIATE_COMMAND_ATTACKNEAREST);
|
|
SetLocalInt(oHench,
|
|
"NW_COM_MODE_MOVEMENT",ASSOCIATE_COMMAND_FOLLOWMASTER);
|
|
|
|
// Add the henchman
|
|
if (bAdd == TRUE)
|
|
{
|
|
AddHenchman(oPC, oHench);
|
|
DelayCommand(1.0, AssignCommand(oHench, LevelUpXP1Henchman(oPC)));
|
|
}
|
|
|
|
}
|
|
|
|
// Use to fire the PC's current henchman
|
|
void FireHenchman(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
if ( !GetIsObjectValid(oPC) || !GetIsObjectValid(oHench) )
|
|
{
|
|
//DBG_msg("Invalid PC or henchman!");
|
|
return;
|
|
}
|
|
// * turn off stealth mode
|
|
SetActionMode(oHench, ACTION_MODE_STEALTH, FALSE);
|
|
// If we're firing the henchman after s/he died,
|
|
// clear that first, since we're not really "hired"
|
|
SetDidDie(FALSE, oHench);
|
|
SetKilled(oPC, oHench, FALSE);
|
|
SetResurrected(oPC, oHench, FALSE);
|
|
|
|
// Now double-check that this is actually our master
|
|
if (!GetIsHired(oHench) || GetMaster(oHench) != oPC)
|
|
{
|
|
//DBG_msg("FireHenchman: not hired or this PC isn't her master.");
|
|
return;
|
|
}
|
|
|
|
// Remove the henchman
|
|
AssignCommand(oHench, ClearActions(CLEAR_X0_I0_HENCHMAN_Fire));
|
|
RemoveHenchman(oPC, oHench);
|
|
|
|
//Store former henchmen for retrieval in Interlude
|
|
// April 28 2003. This storage only happens in Chapter 1
|
|
string sModTag = GetTag(GetModule());
|
|
if (sModTag == "x0_module1")
|
|
{
|
|
if (GetTag(oHench) == "x0_hen_xan")
|
|
StoreCampaignObject("dbHenchmen", "xp0_hen_xan", oHench);
|
|
else if (GetTag(oHench) == "x0_hen_dor")
|
|
StoreCampaignObject("dbHenchmen", "xp0_hen_dor", oHench);
|
|
}
|
|
|
|
//DBG_msg("Removed henchman");
|
|
// Clear everything that was previously set, EXCEPT
|
|
// that the player has hired -- that info we want to
|
|
// keep for the future.
|
|
|
|
// Clear this out so if the henchman gets killed while
|
|
// unhired, she won't think this PC is still her master
|
|
SetLastMaster(OBJECT_INVALID, oHench);
|
|
|
|
// Clear dialogue events
|
|
ClearAllDialogue(oPC, oHench);
|
|
|
|
// Send the henchman home
|
|
// APril 2003: Cut this. Make them stay where they are.
|
|
// ExecuteScript(sGoHomeScript, oHench);
|
|
}
|
|
|
|
// Used when the henchman quits
|
|
void QuitHenchman(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
SetDidQuit(oPC, oHench, TRUE);
|
|
FireHenchman(oPC, oHench);
|
|
}
|
|
|
|
|
|
// Returns TRUE if the henchman is currently hired
|
|
int GetIsHired(object oHench=OBJECT_SELF)
|
|
{
|
|
return GetIsObjectValid(GetMaster(oHench));
|
|
}
|
|
|
|
// Set the last master
|
|
void SetLastMaster(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
//DBG_msg("Set last master to " + GetName(oPC));
|
|
SetLocalObject(oHench, sLastMasterVarname, oPC);
|
|
}
|
|
|
|
// Returns the last master of this henchman (useful for death situations)
|
|
object GetLastMaster(object oHench=OBJECT_SELF)
|
|
{
|
|
//DBG_msg("Getting last master: "
|
|
// + GetName(GetLocalObject(oHench, sLastMasterVarname)));
|
|
return GetLocalObject(oHench, sLastMasterVarname);
|
|
}
|
|
|
|
// Indicate whether the player has ever hired this henchman
|
|
void SetPlayerHasHired(object oPC, object oHench=OBJECT_SELF, int bHired=TRUE)
|
|
{
|
|
if (!GetIsObjectValid(oHench)) {return;}
|
|
SetBooleanValue(oPC, GetTag(oHench) + sHasHiredSuffix, bHired);
|
|
}
|
|
|
|
// Determine whether the player has ever hired this henchman
|
|
int GetPlayerHasHired(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
if (!GetIsObjectValid(oHench)) {return FALSE;}
|
|
return GetBooleanValue(oPC, GetTag(oHench) + sHasHiredSuffix);
|
|
}
|
|
|
|
|
|
// Indicate whether the player has ever hired this henchman in this
|
|
// campaign.
|
|
void SetPlayerHasHiredInCampaign(object oPC, object oHench=OBJECT_SELF, int bHired=TRUE)
|
|
{
|
|
if (!GetIsObjectValid(oHench)) {return;}
|
|
SetCampaignBooleanValue(oPC, GetTag(oHench) + sHasHiredSuffix, bHired);
|
|
}
|
|
|
|
// Indicate whether the player has ever hired this henchman in this
|
|
// campaign.
|
|
int GetPlayerHasHiredInCampaign(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
if (!GetIsObjectValid(oHench)) {return FALSE;}
|
|
return GetCampaignBooleanValue(oPC, GetTag(oHench) + sHasHiredSuffix);
|
|
}
|
|
|
|
|
|
|
|
// Determine whether the henchman is currently working
|
|
// for this PC.
|
|
int GetWorkingForPlayer(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
if (!GetIsObjectValid(oHench) || !GetIsObjectValid(oPC)) {return FALSE;}
|
|
return (GetMaster(oHench) == oPC);
|
|
}
|
|
|
|
// Set whether the henchman quit this player's employ
|
|
void SetDidQuit(object oPC, object oHench=OBJECT_SELF, int bQuit=TRUE)
|
|
{
|
|
if (!GetIsObjectValid(oHench)) {return;}
|
|
SetBooleanValue(oPC, GetTag(oHench) + sDidQuitSuffix, bQuit);
|
|
}
|
|
|
|
// Determine if the henchman quit
|
|
int GetDidQuit(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
if (!GetIsObjectValid(oHench)) {return FALSE;}
|
|
return GetBooleanValue(oPC, GetTag(oHench) + sDidQuitSuffix);
|
|
}
|
|
|
|
/**** LEVELING UP ****/
|
|
|
|
// Checks to see if the henchman can level up.
|
|
// Can only level up if player is 2 or more levels
|
|
// higher than henchman.
|
|
// MIN = Level 4
|
|
// MAX = Level 14
|
|
int GetCanLevelUp(object oPC, object oHench = OBJECT_SELF)
|
|
{
|
|
// SpeakString("This function no longer does nothing. Should not be called");
|
|
return FALSE;
|
|
}
|
|
|
|
// Levels the henchman up to be one level less than player.
|
|
// Returns the new creature.
|
|
object DoLevelUp(object oPC, object oHench = OBJECT_SELF)
|
|
{
|
|
// SpeakString("This function no longer does anything. Should not be called");
|
|
return OBJECT_INVALID;
|
|
}
|
|
|
|
// Store all items in the henchman's inventory in the campaign DB.
|
|
void StoreHenchmanItems(object oPC, object oHench)
|
|
{
|
|
string sHenchTag = GetTag(oHench);
|
|
|
|
string sTag;
|
|
object oItem;
|
|
int nNth = 0;
|
|
string sItemName; string sVarname;
|
|
|
|
// Mark and store equipped items
|
|
int i;
|
|
for (i=0; i < NUM_INVENTORY_SLOTS; i++) {
|
|
oItem = GetItemInSlot(i, oHench);
|
|
if (GetIsObjectValid(oItem)) {
|
|
sItemName = GetTag(oItem);
|
|
//DBG_msg("Found equipped item " + sItemName);
|
|
|
|
// store the slot number + 1 so when we
|
|
// retrieve a 0 can be treated as unequipped
|
|
SetLocalInt(oPC, "HENCH_HAS_EQUIPPED_" + sItemName, i+1);
|
|
|
|
if (FindSubString(sItemName, sHenchTag) == -1) {
|
|
// put it in the db
|
|
sVarname = sHenchTag + "_ITEM_" + IntToString(nNth);
|
|
//DBG_msg("Storing equipped item: " + sItemName
|
|
// + ", varname " + sVarname);
|
|
nNth++;
|
|
StoreCampaignDBObject(oPC, sVarname, oItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store all the henchman inventory in the campaign db
|
|
oItem = GetFirstItemInInventory(oHench);
|
|
while (GetIsObjectValid(oItem)) {
|
|
sItemName = GetTag(oItem);
|
|
//DBG_msg("Found item " + sItemName);
|
|
if (FindSubString(sItemName, sHenchTag) == -1) {
|
|
// put it in the db
|
|
sVarname = sHenchTag + "_ITEM_" + IntToString(nNth);
|
|
//DBG_msg("Storing item: " + sItemName + ", varname " + sVarname);
|
|
nNth++;
|
|
StoreCampaignDBObject(oPC, sVarname, oItem);
|
|
}
|
|
|
|
oItem = GetNextItemInInventory(oHench);
|
|
}
|
|
}
|
|
|
|
|
|
// Retrieve (and then delete) all henchman inventory items out of
|
|
// the campaign DB, putting them in the inventory of the henchman.
|
|
void RetrieveHenchmanItems(object oPC, object oHench)
|
|
{
|
|
location lHench = GetLocation(oHench);
|
|
string sHenchTag = GetTag(oHench);
|
|
int nNth = 0; int nSlot = -1;
|
|
object oCurItem = OBJECT_INVALID;
|
|
|
|
string sVarname = sHenchTag + "_ITEM_0";
|
|
|
|
object oItem = RetrieveCampaignDBObject(oPC, sVarname, lHench, oHench);
|
|
string sItemName = GetTag(oItem);
|
|
|
|
//DBG_msg("Retrieving item " + sItemName + ", varname: " + sVarname);
|
|
while (GetIsObjectValid(oItem)) {
|
|
DeleteCampaignDBVariable(oPC, sVarname);
|
|
nNth++;
|
|
sVarname = sHenchTag + "_ITEM_" + IntToString(nNth);
|
|
oItem = RetrieveCampaignDBObject(oPC, sVarname, lHench, oHench);
|
|
sItemName = GetTag(oItem);
|
|
//DBG_msg("Retrieving item " + sItemName + ", varname: " + sVarname);
|
|
}
|
|
|
|
// Now run through inventory and restore equipped items
|
|
oItem = GetFirstItemInInventory(oHench);
|
|
while (GetIsObjectValid(oItem)) {
|
|
sItemName = GetTag(oItem);
|
|
|
|
// Above, we stored the slot + 1 so we could treat a 0
|
|
// as meaning "not equipped".
|
|
nSlot = GetLocalInt(oPC, "HENCH_HAS_EQUIPPED_" + sItemName) - 1;
|
|
if (nSlot != -1) {
|
|
//DBG_msg("Item was equipped in slot " + IntToString(nSlot));
|
|
DeleteLocalInt(oPC, "HENCH_HAS_EQUIPPED_" + sItemName);
|
|
oCurItem = GetItemInSlot(nSlot, oHench);
|
|
if (GetIsObjectValid(oCurItem)) {
|
|
AssignCommand(oHench, ActionUnequipItem(oCurItem));
|
|
}
|
|
AssignCommand(oHench, ActionEquipItem(oItem, nSlot));
|
|
}
|
|
oItem = GetNextItemInInventory(oHench);
|
|
}
|
|
}
|
|
|
|
|
|
/*** DEATH FUNCTIONS ***/
|
|
|
|
// Set on the henchman to indicate s/he died; can also be used to
|
|
// unset this variable.
|
|
void SetDidDie(int bDie=TRUE, object oHench=OBJECT_SELF)
|
|
{
|
|
SetBooleanValue(oHench, sHenchmanDeathVarname, bDie);
|
|
}
|
|
|
|
// Returns TRUE if the henchman died
|
|
int GetDidDie(object oHench=OBJECT_SELF)
|
|
{
|
|
return GetBooleanValue(oHench, sHenchmanDeathVarname);
|
|
}
|
|
|
|
// Set got killed
|
|
void SetKilled(object oPC, object oHench=OBJECT_SELF, int bKilled=TRUE)
|
|
{
|
|
SetBooleanValue(oPC, GetTag(oHench) + sHenchmanKilledSuffix, bKilled);
|
|
}
|
|
|
|
// Determine if this PC got the henchman killed
|
|
int GetKilled(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
return GetBooleanValue(oPC, GetTag(oHench) + sHenchmanKilledSuffix);
|
|
}
|
|
|
|
// Set that this PC resurrected the henchman
|
|
void SetResurrected(object oPC, object oHench=OBJECT_SELF, int bResurrected=TRUE)
|
|
{
|
|
SetBooleanValue(oPC, GetTag(oHench) + sHenchmanResurrectedSuffix, bResurrected);
|
|
}
|
|
|
|
// Determine if this PC resurrected the henchman
|
|
int GetResurrected(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
return GetBooleanValue(oPC, GetTag(oHench) + sHenchmanResurrectedSuffix);
|
|
}
|
|
|
|
|
|
|
|
// Handle the respawning of the henchman back at either the
|
|
// respawn location or the starting location
|
|
void RespawnHenchman(object oHench=OBJECT_SELF)
|
|
{
|
|
|
|
// : REMINDER: The delay is here for a reason
|
|
// Remove effects on the henchman
|
|
DelayCommand(0.1, RemoveEffects(oHench));
|
|
|
|
// Resurrect
|
|
DelayCommand(0.2,
|
|
ApplyEffectToObject(DURATION_TYPE_PERMANENT,
|
|
EffectResurrection(),
|
|
oHench));
|
|
|
|
// Heal back to full hp
|
|
DelayCommand(0.3,
|
|
ApplyEffectToObject(DURATION_TYPE_PERMANENT,
|
|
EffectHeal(GetMaxHitPoints(oHench)),
|
|
oHench));
|
|
|
|
// Set back to destroyable
|
|
DelayCommand(5.1,
|
|
AssignCommand(oHench,
|
|
SetIsDestroyable(TRUE, TRUE, TRUE)));
|
|
|
|
|
|
// Handle sending back to respawn point
|
|
location lRespawn = GetRespawnLocation(oHench);
|
|
|
|
// Check for validity
|
|
if (GetIsObjectValid(GetAreaFromLocation(lRespawn)))
|
|
{
|
|
DelayCommand(0.3, JumpToLocation(lRespawn));
|
|
}// else
|
|
//{
|
|
// DelayCommand(0.3, ActionSpeakString("NO VALID RESPAWN POINT FOUND"));
|
|
//}
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep dead by playing the appropriate death animation for the
|
|
// maximum wait until respawn.
|
|
void KeepDead(object oHench=OBJECT_SELF)
|
|
{ // SpawnScriptDebugger();
|
|
DelayCommand(0.1, WrapCommandable(TRUE, oHench));
|
|
DelayCommand(0.2,
|
|
AssignCommand(oHench,
|
|
ActionPlayAnimation(ANIMATION_LOOPING_DEAD_FRONT,
|
|
1.0, HENCHMEN_DIE_ANIM_DURATION)));
|
|
DelayCommand(0.3, WrapCommandable(FALSE, oHench));
|
|
}
|
|
|
|
// Stop keeping dead by playing the 'woozy' standing animation.
|
|
void StopKeepingDead(object oHench=OBJECT_SELF)
|
|
{
|
|
DelayCommand(0.1, WrapCommandable(TRUE, oHench));
|
|
DelayCommand(0.2,
|
|
AssignCommand(oHench,
|
|
PlayAnimation(ANIMATION_LOOPING_PAUSE_DRUNK,
|
|
1.0, 6.0)));
|
|
DelayCommand(0.3, WrapCommandable(FALSE, oHench));
|
|
}
|
|
|
|
// Does a partial restoration to get rid of negative effects
|
|
void PartialRes(object oHench)
|
|
{
|
|
RemoveEffects(oHench);
|
|
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectResurrection(), oHench);
|
|
}
|
|
|
|
// Raise and freeze henchman to 1 hp so s/he can be stabilized
|
|
void RaiseForRespawn(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
// Resurrect
|
|
DelayCommand(0.1, PartialRes(oHench));
|
|
|
|
// * May 13 2003
|
|
// * if something weird has happened and my hitpoints are restored
|
|
// * then bring back to life (i.e., a con penalty going away might restore
|
|
// * hitpoints).
|
|
if (GetCurrentHitPoints(oHench) > 1)
|
|
{
|
|
DoRespawn(oPC, OBJECT_SELF);
|
|
return;
|
|
}
|
|
KeepDead(oHench);
|
|
|
|
//DBG_msg("Henchman " + GetTag(oHench) + " raised for respawn");
|
|
}
|
|
|
|
|
|
// See if our maximum wait time has passed
|
|
int GetHasMaxWaitPassed(int nChecks)
|
|
{
|
|
return ( (nChecks * DELAY_BETWEEN_RESPAWN_CHECKS) >= MAX_RESPAWN_WAIT ) ;
|
|
}
|
|
|
|
|
|
// Do the checking to see if we respawn -- this function works
|
|
// in a circle with DoRespawnCheck.
|
|
void RespawnCheck(object oPC, int nChecks=0, object oHench=OBJECT_SELF)
|
|
{
|
|
//DBG_msg("Doing respawn check " + IntToString(nChecks + 1));
|
|
DelayCommand(DELAY_BETWEEN_RESPAWN_CHECKS,
|
|
DoRespawnCheck(oPC, nChecks+1, oHench));
|
|
}
|
|
|
|
|
|
// Perform a single respawn check -- this function works
|
|
// in a circle with RespawnCheck.
|
|
void DoRespawnCheck(object oPC, int nChecks, object oHench=OBJECT_SELF)
|
|
{
|
|
//brentDebug("In RespawnCheck");
|
|
// * if a healing spell has been used on henchmen, they ain't dead no more
|
|
if (GetIsHenchmanDying(oHench) == FALSE)
|
|
return;
|
|
|
|
// SpawnScriptDebugger();
|
|
if ( GetCurrentHitPoints(oHench) == 1 && GetHasMaxWaitPassed(nChecks))
|
|
{
|
|
//DBG_msg("Maximum wait reached, respawning");
|
|
DoRespawn(oPC, oHench);
|
|
}
|
|
else if (GetCurrentHitPoints(oHench) == 1
|
|
&&
|
|
( GetArea(oPC) != GetArea(oHench) || GetIsDead(oPC)) )
|
|
{
|
|
//DBG_msg("Master left or died, respawning");
|
|
DoRespawn(oPC, oHench);
|
|
}
|
|
else if (GetCurrentHitPoints(oHench) > 1 && !GetResurrected(oPC))
|
|
{
|
|
// We're alive, must have been resurrected
|
|
// Do the 'respawn' anyway to clean up after death
|
|
//DBG_msg("Master stabilized us, respawning");
|
|
DoRespawn(oPC, oHench);
|
|
}
|
|
else
|
|
{
|
|
// We aren't resurrecting yet, but keep checking
|
|
RespawnCheck(oPC, nChecks, oHench);
|
|
}
|
|
}
|
|
|
|
// This function actually invokes the respawn.
|
|
void DoRespawn(object oPC, object oHench=OBJECT_SELF)
|
|
{
|
|
// SpawnScriptDebugger();
|
|
StopKeepingDead(oHench);
|
|
|
|
// Set henchman commandable
|
|
DelayCommand(0.4,
|
|
WrapCommandable(TRUE, oHench));
|
|
|
|
// if (GetCurrentHitPoints(oHench) > 0)
|
|
if (GetLocalInt(oHench, "X0_L_WAS_HEALED") == 10)
|
|
{
|
|
SetLocalInt(oHench, "X0_L_WAS_HEALED",0);
|
|
// Hey, we've been stabilized! Good on you, master.
|
|
SetResurrected(oPC, oHench);
|
|
|
|
// Automatically re-add the henchman BK 2003 Don't rehire them completely since they were never not hired.
|
|
HireHenchman(oPC, oHench, FALSE);
|
|
}
|
|
else
|
|
{
|
|
|
|
// * only in Chapter 1 will the henchmen respawn
|
|
// * somewhere, otherwise they'll stay where they are.
|
|
if (GetTag(GetModule()) == "x0_module1")
|
|
{
|
|
// Indicate that this master got us killed
|
|
SetKilled(oPC, oHench);
|
|
RemoveHenchman(oPC, oHench);
|
|
// Do the respawn
|
|
DelayCommand(1.0, RespawnHenchman(oHench));
|
|
}
|
|
}
|
|
PostRespawnCleanup(oHench);
|
|
}
|
|
|
|
void PreRespawnSetup(object oHench=OBJECT_SELF)
|
|
{
|
|
// Mark us as in the process of dying
|
|
SetHenchmanDying(oHench, TRUE);
|
|
|
|
// Indicate the henchman died
|
|
SetDidDie(TRUE, oHench);
|
|
|
|
// Mark henchman PLOT & Busy
|
|
SetPlotFlag(oHench, TRUE);
|
|
SetAssociateState(NW_ASC_IS_BUSY, TRUE, oHench);
|
|
|
|
// Make henchman's corpse stick around,
|
|
// be raiseable, and selectable
|
|
AssignCommand(oHench, SetIsDestroyable(FALSE, TRUE, TRUE));
|
|
|
|
AssignCommand(oHench, ClearActions(CLEAR_X0_I0_HENCHMAN_PreRespawn, TRUE));
|
|
}
|
|
|
|
|
|
void PostRespawnCleanup(object oHench=OBJECT_SELF)
|
|
{
|
|
DelayCommand(1.0,
|
|
SetHenchmanDying(oHench, FALSE));
|
|
|
|
// Clear henchman being busy
|
|
DelayCommand(1.1,
|
|
SetAssociateState(NW_ASC_IS_BUSY, FALSE, oHench));
|
|
|
|
// Clear the plot flag
|
|
DelayCommand(1.2, SetPlotFlag(oHench, FALSE));
|
|
|
|
}
|
|
|
|
// Determine if this henchman is currently dying
|
|
int GetIsHenchmanDying(object oHench=OBJECT_SELF)
|
|
{
|
|
int bHenchmanDying = GetAssociateState(NW_ASC_MODE_DYING, oHench);
|
|
if (bHenchmanDying == TRUE)
|
|
{
|
|
//brentDebug("henchman is dying");
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
//brentDebug("Henchman is not dying");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// * Wrapper function added to fix bugs in the dying-state
|
|
// * process. Need to figure out whenever his value changes.
|
|
void SetHenchmanDying(object oHench=OBJECT_SELF, int bIsDying=TRUE)
|
|
{
|
|
SetAssociateState(NW_ASC_MODE_DYING, bIsDying, oHench);
|
|
//brentDebug("In SetHenchmanDying. Value for " + GetName(oHench) + " is " + IntToString(bIsDying));
|
|
// GetIsHenchmanDying();
|
|
}
|
|
/***** MODULE TRANSFER FUNCTIONS *****/
|
|
|
|
// Call this function when the PC is about to leave a module
|
|
// to enable restoration of the henchman on re-entry into the
|
|
// sequel module. Both modules must use the same campaign db
|
|
// for this to work.
|
|
void StoreCampaignHenchman(object oPC)
|
|
{
|
|
object oHench = GetHenchman(oPC);
|
|
if (!GetIsObjectValid(oHench)) {
|
|
//DBG_msg("No valid henchman to store");
|
|
return;
|
|
}
|
|
|
|
//DBG_msg("Storing henchman: " + GetTag(oHench));
|
|
int ret = StoreCampaignDBObject(oPC, sStoredHenchmanVarname, oHench);
|
|
/* if (!ret) {
|
|
DBG_msg("Error attempting to store henchman");
|
|
} else {
|
|
DBG_msg("Henchman stored successfully");
|
|
}*/
|
|
}
|
|
|
|
// Call this function when a PC enters a sequel module to restore
|
|
// the henchman (complete with inventory). The function
|
|
// StoreCampaignHenchman must have been called first, and both
|
|
// modules must use the same campaign db. (See notes in x0_i0_campaign.)
|
|
//
|
|
// The restored henchman will automatically be re-hired and will be
|
|
// created next to the PC.
|
|
//
|
|
// Any object in the module with the same tag as the henchman will be
|
|
// destroyed (to remove duplicates).
|
|
void RetrieveCampaignHenchman(object oPC)
|
|
{
|
|
location lLoc = GetLocation(oPC);
|
|
object oHench = RetrieveCampaignDBObject(oPC, sStoredHenchmanVarname, lLoc);
|
|
|
|
// Delete the henchman object from the db
|
|
DelayCommand(0.5, DeleteCampaignDBVariable(oPC, sStoredHenchmanVarname));
|
|
|
|
if (GetIsObjectValid(oHench)) {
|
|
DelayCommand(0.5, HireHenchman(oPC, oHench));
|
|
|
|
object oHenchDupe = GetNearestObjectByTag(GetTag(oHench),
|
|
oHench);
|
|
if (GetIsObjectValid(oHenchDupe) && oHenchDupe != oHench) {
|
|
DestroyObject(oHenchDupe);
|
|
}
|
|
}// else {
|
|
// DBG_msg("No valid henchman retrieved");
|
|
//}
|
|
}
|
|
|
|
// Levels a henchman up to the given level, alternating
|
|
// between the first and second classes if they are multiclassed.
|
|
// 0 as a max level means they will try to keep their levels balanced
|
|
void LevelHenchmanUpTo(object oHenchman, int nLevel, int nClass2=CLASS_TYPE_INVALID,
|
|
int nMaxLevelInSecondClass=0, int nPackageClass1=PACKAGE_INVALID, int nPackageClass2=PACKAGE_INVALID)
|
|
{
|
|
|
|
int nPackageToUse = nPackageClass1;
|
|
|
|
|
|
if ( !GetIsObjectValid(oHenchman) || GetHitDice(oHenchman) >= nLevel)
|
|
return;
|
|
|
|
// * she has 3 rogue levels, decrement nLevel by this
|
|
if (GetTag(oHenchman) == "x2_hen_nathyra" && nClass2 == CLASS_TYPE_ASSASSIN)
|
|
{
|
|
nLevel = nLevel - 3;
|
|
}
|
|
|
|
int nClass1 = GetClassByPosition(1, oHenchman);
|
|
if (nClass2 == CLASS_TYPE_INVALID)
|
|
{
|
|
nClass2 = GetClassByPosition(2, oHenchman);
|
|
}
|
|
|
|
int nLevel1 = GetLevelByClass(nClass1, oHenchman);
|
|
int nLevel2 = GetLevelByClass(nClass2, oHenchman);
|
|
|
|
int nClassToLevelUp;
|
|
|
|
while ( (nLevel1 + nLevel2) < nLevel )
|
|
{
|
|
if ( nClass2 != CLASS_TYPE_INVALID && (nLevel1 > nLevel2) )
|
|
{
|
|
nClassToLevelUp = nClass2;
|
|
nLevel2++;
|
|
nPackageToUse = nPackageClass2;
|
|
}
|
|
else
|
|
{
|
|
nClassToLevelUp = nClass1;
|
|
nPackageToUse = nPackageClass1;
|
|
nLevel1++;
|
|
}
|
|
|
|
// * if you have exceeded your max level in the second class
|
|
// * only level up in the first class from this point forward
|
|
if (nLevel2 > nMaxLevelInSecondClass)
|
|
{
|
|
nClassToLevelUp = nClass1;
|
|
nPackageToUse = nPackageClass1;
|
|
}
|
|
|
|
// * Additional Rules
|
|
// * The player can choose a levelup stratedgy for the henchman
|
|
// * 0 = Normal, as per designer rules
|
|
// * 1 = Secondary Class: only take levels in your second class
|
|
// * 2 = First class: only take levels in your first class
|
|
// * Note: This choice overrides the above nMaxLevelInSecondClass
|
|
int nRule = GetLocalInt(oHenchman, "X0_L_LEVELRULES");
|
|
|
|
// HACK: If in XP2, reverse the rules
|
|
if (GetLocalInt(GetModule(), "X2_L_XP2") == 1)
|
|
{
|
|
if (nRule == 1)
|
|
nRule = 2;
|
|
else
|
|
if (nRule == 2)
|
|
nRule = 1;
|
|
}
|
|
|
|
if (nRule == 1)
|
|
{
|
|
nClassToLevelUp = nClass2;
|
|
nPackageToUse = nPackageClass2;
|
|
}
|
|
else
|
|
if (nRule == 2)
|
|
{
|
|
nClassToLevelUp = nClass1;
|
|
}
|
|
if (!LevelUpHenchman(oHenchman, nClassToLevelUp, FALSE, nPackageToUse))
|
|
{
|
|
// * In case the levelup failed (july 2003) for a prestige class
|
|
// * try one more time to levelup the primary class.
|
|
// * this way classes with an alternate prestige class will attempt
|
|
// * always to gain that class but fail until they meet the prereqs
|
|
// Feb. 11, 2004 - JE: Made this more generic, to fix evil aribeth
|
|
// at high levels. Instead of trying class 1, it tries the OTHER class,
|
|
// since it's possible for the first class to fail.
|
|
int nClassToLevelUp2;
|
|
if(nClassToLevelUp==nClass2)
|
|
{
|
|
nClassToLevelUp2 = nClass1;
|
|
nPackageToUse = nPackageClass1;
|
|
}
|
|
else
|
|
{
|
|
nClassToLevelUp2 = nClass2;
|
|
nPackageToUse = nPackageClass2;
|
|
}
|
|
if (nClassToLevelUp2==CLASS_TYPE_INVALID ||
|
|
!LevelUpHenchman(oHenchman, nClassToLevelUp2, FALSE, nPackageToUse))
|
|
{
|
|
SendMessageToPC(GetFirstPC(), "Level Up Failed For "
|
|
+ GetName(oHenchman)
|
|
+ " in class "
|
|
+ IntToString(nClassToLevelUp));
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// * Adjusts the levels for the henchmen
|
|
int AdjustXP2Levels(int nLevel, int nMin=13, int nAdjust=2)
|
|
{
|
|
nLevel = nLevel - nAdjust;
|
|
if (nLevel < nMin)
|
|
nLevel = nMin;
|
|
return nLevel;
|
|
}
|
|
// * levels up the henchman assigned to oPC
|
|
// * Modified for XP2 so that it cycles through
|
|
// * all the available henchmen and levels them all up
|
|
// *
|
|
void LevelUpXP1Henchman(object oPC)
|
|
{
|
|
if ( !GetIsObjectValid(oPC) )
|
|
return;
|
|
|
|
int i = 1;
|
|
object oAssociate;
|
|
for (i=1; i<= GetMaxHenchmen(); i++)
|
|
{
|
|
oAssociate = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oPC, i);
|
|
|
|
if ( GetIsObjectValid(oAssociate) )
|
|
{
|
|
// * Followers do not level up
|
|
if (GetLocalInt(oAssociate, "X2_JUST_A_FOLLOWER") == FALSE)
|
|
{
|
|
int nResult;
|
|
int nLevel = GetHitDice(oPC);
|
|
string sTag = GetStringLowerCase(GetTag(oAssociate));
|
|
|
|
|
|
|
|
// ********************************
|
|
// XP2 Stuff
|
|
// * if a mini henchman
|
|
// * nLevel = nLevel - 2;
|
|
// * because they are always 2 levels
|
|
// * behind the player.
|
|
// ********************************
|
|
if (sTag == "x2_hen_deekin")
|
|
{
|
|
//nLevel = AdjustXP2Levels(nLevel);
|
|
LevelHenchmanUpTo(oAssociate, nLevel, 37, 40, 72, 117);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_daelan")
|
|
{
|
|
// * druid would have to be exposed
|
|
nLevel = AdjustXP2Levels(nLevel);
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_DRUID, 0, 105);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_sharwyn")
|
|
{
|
|
nLevel = AdjustXP2Levels(nLevel);
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_FIGHTER, 40, 106, 114);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_linu")
|
|
{
|
|
// * leveling up as a fighter would have to be exposed
|
|
nLevel = AdjustXP2Levels(nLevel);
|
|
LevelHenchmanUpTo(oAssociate, nLevel,CLASS_TYPE_FIGHTER , 0, 104);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_tomi")
|
|
{ //SpawnScriptDebugger();
|
|
nLevel = AdjustXP2Levels(nLevel);
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_SHADOWDANCER, 40, 103, 116);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_nathyra")
|
|
{
|
|
// After one level of wizard
|
|
// she will take one level of rogue.
|
|
|
|
if (GetHitDice(oAssociate) <= 6)
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, 12, CLASS_TYPE_ROGUE, 3, 101, 8);
|
|
if (nLevel >= 14)
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_ASSASSIN, 40, 101, 115);
|
|
}
|
|
else
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel , CLASS_TYPE_ASSASSIN, 40, 101, 115);
|
|
}
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_valen")
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_WEAPON_MASTER, 40, 102,113);
|
|
}
|
|
else
|
|
// * Aribeth
|
|
if (sTag =="h2_aribeth") {
|
|
/* Aribeth has special rules
|
|
- if she is good, she'll level up as a paladin
|
|
- if she is evil, she'll level up as a blackguard
|
|
*/
|
|
if (GetAlignmentGoodEvil(oAssociate) == ALIGNMENT_GOOD)
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_INVALID, 0, 129);
|
|
}
|
|
else
|
|
// Blackguard
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_BLACKGUARD, 40, 129, 130);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// ********************************
|
|
// XP1 Stuff
|
|
// ********************************
|
|
if ( sTag == "x0_hen_xan" )
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_BARBARIAN, 2);
|
|
}
|
|
else if (sTag == "x0_hen_dor")
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_CLERIC, 20);
|
|
}
|
|
else if (sTag == "x0_hen_dee")
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_ROGUE, 0);
|
|
}
|
|
else if (sTag == "garey")
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_CLERIC, 20);
|
|
}
|
|
else if (sTag == "olisha")
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_BARD, 20);
|
|
}
|
|
else if (sTag == "hench_elvalith")
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_CLERIC, 20);
|
|
}
|
|
else
|
|
{
|
|
LevelHenchmanUpTo(oAssociate, nLevel);
|
|
}
|
|
} // Follower
|
|
} // valid associate
|
|
} // Loop
|
|
}
|
|
|
|
//::///////////////////////////////////////////////
|
|
//:: LevelUpAribeth
|
|
//:: Copyright (c) 2003 Bioware Corp.
|
|
//:://////////////////////////////////////////////
|
|
/*
|
|
Initial Aribeth you meet in Chapter 3.
|
|
Levels her up to level 16 Paladin,
|
|
Level 6 Blackguard.
|
|
|
|
Does some tricky alignment juggling to allow
|
|
this.
|
|
*/
|
|
//:://////////////////////////////////////////////
|
|
//:: Created By: Brent
|
|
//:: Created On:
|
|
//:://////////////////////////////////////////////
|
|
void LevelUpAribeth(object oAribeth)
|
|
{
|
|
// * set alignment good
|
|
AdjustAlignment(oAribeth, ALIGNMENT_GOOD, 100);
|
|
AdjustAlignment(oAribeth, ALIGNMENT_LAWFUL, 100);
|
|
// * give 16 levels of paladin
|
|
LevelHenchmanUpTo(oAribeth, 16, CLASS_TYPE_INVALID, 0, 129);
|
|
|
|
// * set alignment lawful evil
|
|
AdjustAlignment(oAribeth, ALIGNMENT_EVIL, 100);
|
|
AdjustAlignment(oAribeth, ALIGNMENT_LAWFUL, 100);
|
|
|
|
// * give 6 levels of blackguard
|
|
LevelHenchmanUpTo(oAribeth, 22, CLASS_TYPE_BLACKGUARD, 40, 129, 130);
|
|
|
|
|
|
// * set alignment chaotic neutral
|
|
// AdjustAlignment(oAribeth, ALIGNMENT_NEUTRAL, 100);
|
|
// AdjustAlignment(oAribeth, ALIGNMENT_LAWFUL, 100);
|
|
}
|
|
|
|
//::///////////////////////////////////////////////
|
|
//:: SetNumberOfRandom
|
|
//:: Copyright (c) 2003 Bioware Corp.
|
|
//:://////////////////////////////////////////////
|
|
/*
|
|
Sets the number of random popups or
|
|
interjections that the henchman has
|
|
Should be called in henchman
|
|
spawn scripts
|
|
|
|
In the format
|
|
1|2|3|4|
|
|
*/
|
|
//:://////////////////////////////////////////////
|
|
//:: Created By:
|
|
//:: Created On:
|
|
//:://////////////////////////////////////////////
|
|
void SetNumberOfRandom(string sVariableName, object oHench, int nNum)
|
|
{
|
|
int i = 0;
|
|
string s = "";
|
|
|
|
for (i=1; i<= nNum; i++)
|
|
{
|
|
s = s + IntToString(i) + "|";
|
|
}
|
|
|
|
SetLocalString(oHench, sVariableName, s);
|
|
}
|
|
|
|
// * Oct 14 - added the oHench parameters
|
|
string GetDialogFile(object oPC, string sHenchmenDlg, string sPreHenchDlg, object oHench=OBJECT_SELF)
|
|
{
|
|
if ( GetPlayerHasHired(oPC, oHench) == TRUE)
|
|
{
|
|
return sHenchmenDlg;
|
|
}
|
|
else
|
|
{
|
|
return sPreHenchDlg;
|
|
|
|
}
|
|
}
|
|
|
|
//::///////////////////////////////////////////////
|
|
//:: GetDialogFileToUse
|
|
//:: Copyright (c) 2001 Bioware Corp.
|
|
//:://////////////////////////////////////////////
|
|
/*
|
|
Returns the filename for the appropriate
|
|
dialog file to be used.
|
|
|
|
Henchmen have various dialog files throughout
|
|
the game.
|
|
*/
|
|
//:://////////////////////////////////////////////
|
|
//:: Created By: Brent
|
|
//:: Created On: August 2003
|
|
//:://////////////////////////////////////////////
|
|
|
|
string GetDialogFileToUse(object oPC, object oHench = OBJECT_SELF)
|
|
{
|
|
string sTag = GetTag(oHench);
|
|
string sModuleTag = GetTag(GetModule());
|
|
// * Chapter 2
|
|
|
|
// * Chapter 1 only
|
|
if (sModuleTag == "x0_module1")
|
|
{
|
|
if (sTag == "x2_hen_sharwyn")
|
|
{
|
|
return GetDialogFile(oPC, "xp2_hen_shar", "q2asharwyn", oHench);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_daelan")
|
|
{
|
|
return GetDialogFile(oPC, "xp2_hen_dae", "q2adaelan", oHench);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_tomi")
|
|
{
|
|
return GetDialogFile(oPC, "xp2_hen_tomi", "q2atomi", oHench);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_linu")
|
|
{
|
|
return GetDialogFile(oPC, "xp2_hen_linu", "q2alinu", oHench);
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_deekin")
|
|
{
|
|
return GetDialogFile(oPC, "xp2_hen_dee", "pre_deekin", oHench);
|
|
}
|
|
}
|
|
else
|
|
if (sModuleTag == "x0_module2" || sModuleTag == "x0_module3")
|
|
{
|
|
// * valen and nathyrra have area specific dialog
|
|
string sAreaTag = GetTag(GetArea(oPC));
|
|
if (sTag == "x2_hen_valen")
|
|
{
|
|
if (sAreaTag == "q2a1_temple" && !GetIsObjectValid(GetMaster(oHench)))
|
|
{
|
|
return "xp2_valen";
|
|
}
|
|
|
|
return "xp2_hen_val";
|
|
}
|
|
else
|
|
if (sTag == "x2_hen_nathyra" )
|
|
{
|
|
if (sAreaTag == "q2a1_temple" && !GetIsObjectValid(GetMaster(oHench)))
|
|
{
|
|
return "xp2_nathyrra";
|
|
}
|
|
|
|
return "xp2_hen_nat";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/**********************************************************************
|
|
* PRIVATE FUNCTIONS
|
|
*
|
|
* Note -- these are not really private, it's simply that they're
|
|
* unprototyped and therefore won't show up in the function list.
|
|
**********************************************************************/
|
|
|
|
// For debugging only. Close the comment below to enable.
|
|
// Make sure that the main() function in x0_i0_common is disabled
|
|
// or else you'll get duplicate function errors.
|
|
|
|
/* void main() {} /* */
|