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