//:://////////////////////////////////////////////////
//:: 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"
#include "inc_prc_ecl"


/**********************************************************************
 * 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;
    int nMaxHenchmen = GetMaxHenchmen();

    // 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 = GetECL(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 == "belror")
                {
                    LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_WEAPON_MASTER, 40, 102,113);
                }
                else if (sTag == "boibo")
                {
                    LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_SHADOWDANCER, 20, PACKAGE_ROGUE_SHADOWDANCER,PACKAGE_SHADOWDANCER);
                }
                else if (sTag == "eowarar")
                {
                    LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_PALE_MASTER, 40, PACKAGE_WIZARD_PALEMASTER,PACKAGE_PALE_MASTER);
                }
                else if (sTag == "merrenton")
                {
                    LevelHenchmanUpTo(oAssociate, nLevel, CLASS_TYPE_SHIFTER, 20, PACKAGE_DRUID_SHIFTER,PACKAGE_SHIFTER);
                }
                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() {} /* */
