Scripting problem - henchman

I have a script problem. As far as I can see everything should work, but when testing it doesn’t quite do what I want.

I have this henchman (creature) who is activated through an item (but that’s beside the point). Everything works with him spawning in. However, when the creature’s master (one of the companions, although in the script I believe the PC is perhaps deemed the master) dies or rather gets her hp below 5 %, I want him destroyed (that he should disappear/die).

Ingame, the only thing that happens is that when the master dies, or is dying rather, the hp of the henchman/creature drops to something like 0, but he won’t disappear whatever I do.

Here are my scripts. I’ve done so many variations of these scripts now that I’m at my wits end (and I’m getting confused as to what I have and haven’t tried yet). I don’t know what to do to get it working. At one test I think it actually worked, but then when testing again, it still didn’t.

OnDamage script of the henchman:

//::///////////////////////////////////////////////
//:: Associate: On Damaged
//:: gb_assoc_damage
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    If already fighting then ignore, else determine
    combat round
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Nov 19, 2001
//:://////////////////////////////////////////////

#include "hench_i0_ai"
#include "hench_i0_assoc"

void PrepForDestruction(object oTarget)
{
	SetPlotFlag(oTarget,FALSE);
    SetImmortal(oTarget,FALSE);
    AssignCommand(oTarget,SetIsDestroyable(TRUE,FALSE,FALSE));
}

void KillSprif(object oSprif)
{

	PrepForDestruction(oSprif);
 	AssignCommand(GetModule(), DestroyObject(oSprif));
	

}

void main()
{

	object oPC1 = GetFirstPC();
	
	//SendMessageToPC(oPC1,"Sprif heartbeat script is running");
	
	object oFreya = GetObjectByTag("freya");
	object oSprif = GetObjectByTag("sprif");
	
	if(!GetIsObjectValid(oFreya) || GetArea(oSprif) != GetArea(oFreya))
	{
	       object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return; 
		
	}
	
	else if(GetIsObjectValid(oFreya) && GetArea(oSprif) == GetArea(oFreya) && GetHPPercentage(oSprif) <5)
	{
	
	       object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;
	
	}
	
	else if(GetIsObjectValid(oFreya) && GetArea(oSprif) == GetArea(oFreya) && GetHPPercentage(oFreya) <5)
	{
	
	       object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;

	}


    else if(!GetAssociateState(NW_ASC_IS_BUSY))
    {
        // Auldar: Make a check for taunting before running Ondamaged.
        if(!GetAssociateState(NW_ASC_MODE_STAND_GROUND) && (GetCurrentAction() != ACTION_FOLLOW)
            && (GetCurrentAction() != ACTION_TAUNT))
        {
            if ((GetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE) == HENCH_HEAL_SELF_WAIT) &&
                (GetPercentageHPLoss(OBJECT_SELF) < 30))
            {
                // force heal
                HenchDetermineCombatRound(OBJECT_INVALID, TRUE);
            }
            else
            {
                // Auldar: Use combat checks from OnPerceive.
                if(!GetIsObjectValid(GetAttemptedAttackTarget()) &&
                   !GetIsObjectValid(GetAttackTarget()) &&
                   !GetIsObjectValid(GetAttemptedSpellTarget()))
                {
                    if(GetIsObjectValid(GetLastHostileActor()))
                    {
                        if(GetAssociateState(NW_ASC_MODE_DEFEND_MASTER))
                        {
                            if(!GetIsObjectValid(GetLastHostileActor(HenchGetDefendee())))
                            {
                                HenchDetermineCombatRound();
                            }
                        }
                        else
                        {
                            HenchDetermineCombatRound(GetLastDamager());
                        }
                    }
                }
            }
        }
    }
    if(GetSpawnInCondition(NW_FLAG_DAMAGED_EVENT))
    {
        SignalEvent(OBJECT_SELF, EventUserDefined(EVENT_DAMAGED));
    }
}

OnHeartbeat script of the henchman:

//::///////////////////////////////////////////////
//:: Associate: Heartbeat
//:: gb_assoc_heart
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    Move towards master or wait for him
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Nov 21, 2001
//:://////////////////////////////////////////////
//:: JSH-OEI: Commented out infinite buffs.

#include "hench_i0_act"
#include "hench_i0_ai"
#include "hench_i0_equip"
#include "hench_i0_target"
#include "hench_i0_initialize"
#include "hench_i0_assoc"
#include "x2_inc_summscale"
#include "x2_inc_spellhook"
#include "x0_inc_henai"
#include "ginc_group"

void PrepForDestruction(object oTarget)
{
	SetPlotFlag(oTarget,FALSE);
    SetImmortal(oTarget,FALSE);
    AssignCommand(oTarget,SetIsDestroyable(TRUE,FALSE,FALSE));
}

void KillSprif(object oSprif)
{

	PrepForDestruction(oSprif);
	AssignCommand(GetModule(), DestroyObject(oSprif));
}


void main()
{
//	Jug_Debug("*****" + GetName(OBJECT_SELF) + " heartbeat action " + IntToString(GetCurrentAction()) + " PC " + IntToString(GetIsPC(OBJECT_SELF)));
//  Jug_Debug(GetName(OBJECT_SELF) + " faction leader " + GetName(GetFactionLeader(OBJECT_SELF)));
//  Jug_Debug(GetName(OBJECT_SELF) + " distance " + FloatToString(GetDistanceToObject(GetMaster())));

        // destroy self if pseudo summons and master not valid
 /*   if (GetLocalInt(OBJECT_SELF, sHenchPseudoSummon) && !GetIsObjectValid(GetMaster()))
    {
        DestroyObject(OBJECT_SELF, 0.1);
        ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_IMP_UNSUMMON), GetLocation(OBJECT_SELF));
        return;
    } */
	object oPC = GetFirstPC(FALSE);

	//SendMessageToPC(oPC1,"Sprif heartbeat script is running");
	
	object oFreya = GetObjectByTag("freya");
	object oSprif = GetObjectByTag("sprif");
	
	if(!GetIsObjectValid(oFreya) || GetArea(oSprif) != GetArea(oFreya))
	{
            object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;

	}
	
	else if(!GetIsObjectValid(oFreya) || GetArea(oSprif) != GetArea(oFreya))
	{
	  		object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;
		
	}
	
	else if(GetIsObjectValid(oFreya) && GetArea(oSprif) == GetArea(oFreya) && GetHPPercentage(oSprif) <5)
	{
			object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;
	
	}
	
	else if(GetIsObjectValid(oFreya) && GetArea(oSprif) == GetArea(oFreya) && GetHPPercentage(oFreya) <5)
	{
			object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;
	
	}

        // BMA-OEI 7/04/06 - Check if in group and using group campaign flag
    // May not be needed if we change nwn2_scriptsets.2da PCDominate entry to use nw_g0_dominate
    if (GetGlobalInt(CAMPAIGN_SWITCH_FORCE_KILL_DOMINATED_GROUP) == TRUE )
    {
        string sGroupName = GetGroupName( OBJECT_SELF );
        if ( sGroupName != "" )
        {
            if ( GetHasEffectType(OBJECT_SELF, EFFECT_TYPE_DOMINATED) == TRUE )
            {
                if ( GetIsGroupDominated(sGroupName) == TRUE )
                {
                    RemoveEffectsByType( OBJECT_SELF, EFFECT_TYPE_DOMINATED );
                    ApplyEffectToObject( DURATION_TYPE_INSTANT, EffectDeath(), OBJECT_SELF );
                }
            }
        }
    }

    // GZ: Fallback for timing issue sometimes preventing epic summoned creatures from leveling up to their master's level.
    // There is a timing issue with the GetMaster() function not returning the fof a creature
    // immediately after spawn. Some code which might appear to make no sense has been added
    // to the nw_ch_ac1 and x2_inc_summon files to work around this
    // This code is only run at the first heartbeat
    int nLevel = SSMGetSummonFailedLevelUp(OBJECT_SELF);
    if (nLevel != 0)
    {
        int nRet;
        if (nLevel == -1) // special shadowlord treatment
        {
            SSMScaleEpicShadowLord(OBJECT_SELF);
        }
        else if  (nLevel == -2)
        {
            SSMScaleEpicFiendishServant(OBJECT_SELF);
        }
        else
        {
            nRet = SSMLevelUpCreature(OBJECT_SELF, nLevel, CLASS_TYPE_INVALID);
            if (nRet == FALSE)
            {
                WriteTimestampedLogEntry("WARNING - nw_ch_ac1:: could not level up " + GetTag(OBJECT_SELF) + "!");
            }
        }

        // regardless if the actual levelup worked, we give up here, because we do not
        // want to run through this script more than once.
        SSMSetSummonLevelUpOK(OBJECT_SELF);
    }

    // Check if concentration is required to maintain this creature
    X2DoBreakConcentrationCheck();

    // * if I am dominated, ask for some help
    // TK removed SendForHelp
//    if (GetHasEffect(EFFECT_TYPE_DOMINATED, OBJECT_SELF) == TRUE && GetIsEncounterCreature(OBJECT_SELF) == FALSE)
//    {
//        SendForHelp();
//    }

        // restore associate settings
    HenchGetDefSettings();

	// JWR-OEI Added per TTP bug 553
	if (GetScriptHidden(OBJECT_SELF))
	{
		// in case they are script hidden (like on overland map)
		// they'll stop buffing themselves.
		ClearAllActions();
		return;
	}
    if (GetAssociateState(NW_ASC_IS_BUSY))
    {
        return;
    }
    if (!GetIAmNotDoingAnything())
    {
        return;
    }

    object oRealMaster = GetCurrentMaster();
    if (!GetIsObjectValid(oRealMaster))
    {
        return;
    }

    if (!GetAssociateState(NW_ASC_MODE_STAND_GROUND) && !GetAssociateState(NW_ASC_MODE_PUPPET))
    {
        if (HenchCheckHeartbeatCombat())
        {
            HenchResetCombatRound();
        }
        if (HenchGetIsEnemyPerceived())
        {
//          Jug_Debug(GetName(OBJECT_SELF) + " heartbeat determine combat round");
            HenchDetermineCombatRound();
            return;
        }
        if (GetLocalInt(OBJECT_SELF, sHenchLastHeardOrSeen))
        {
//      Jug_Debug(GetName(OBJECT_SELF) + " moving to last seen and heard");
            // continue to move to target
            MoveToLastSeenOrHeard(FALSE);
            return;
        }
    }
    HenchResetCombatRound();

    if ((GetLocalObject(OBJECT_SELF,"NW_L_FORMERMASTER") != OBJECT_INVALID)
        && (GetLocalInt(OBJECT_SELF, "haveCheckedFM") != 1))
    {
        // Auldar: For a little OnHeartbeat efficiency, I'll set a localint so we don't
        // keep checking stealth mode etc. This will be cleared in NW_CH_JOIN, as will
        // the LocalObject for NW_L_FORMERMASTER.
        // A little quirk with this behavior - the ActionUseSkill's do not execute until the henchman rejoins
        // however if the player re-loads, or leaves the area and returns, the henchman will no longer be in stealth etc.
        // I couldn't find any way around that odd behavior, but this works for the most part.
        SetLocalInt(OBJECT_SELF, "haveCheckedFM", 1);
        SetAssociateState(NW_ASC_AGGRESSIVE_SEARCH, FALSE);
        SetLocalInt(OBJECT_SELF, sHenchStealthMode, 0);
        SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, FALSE);
        SetActionMode(OBJECT_SELF, ACTION_MODE_DETECT, FALSE);
    }

	HenchCheckOutOfCombatStealth(oRealMaster);

    CleanCombatVars();
    SetLocalInt(OBJECT_SELF, HENCH_AI_SCRIPT_POLL, FALSE);

    if (GetLocalInt(OBJECT_SELF, henchHealCountStr))
    {
//        Jug_Debug(GetName(OBJECT_SELF) + " HB checking heal count " + IntToString(GetLocalInt(OBJECT_SELF, henchHealCountStr)));
        ActionDoCommand(ActionWait(2.5));
        ActionDoCommand(ExecuteScript("hench_o0_heal", OBJECT_SELF));
        return;
    }

        //25% chance that..
    if ((d4() == 1) &&
        !GetAssociateState(NW_ASC_MODE_PUPPET) &&
        (GetLocalInt(OBJECT_SELF, sHenchStopCasting) != 10) &&
        !GetLocalInt(OBJECT_SELF, sHenchDontAttackFlag) &&
        // if we're not excluding the ability to use feats and abilities
        !GetLocalIntState(OBJECT_SELF, N2_TALENT_EXCLUDE, TALENT_EXCLUDE_ABILITY))
    {
        //and if we have the summon familiar feat
        if (GetHenchPartyState(HENCH_PARTY_SUMMON_FAMILIARS) && (GetHasFeat(FEAT_SUMMON_FAMILIAR, OBJECT_SELF) || GetHasSpell(SPELLABILITY_SUMMON_FAMILIAR, OBJECT_SELF)))
        {
            object oAssociate = GetAssociate(ASSOCIATE_TYPE_FAMILIAR, OBJECT_SELF);
            // with no current familiar stat
            if (!GetIsObjectValid(oAssociate))
            {
                // summon my familiar and decrement use
                SummonFamiliar();
                DecrementRemainingFeatUses(OBJECT_SELF, FEAT_SUMMON_FAMILIAR);
            }
        }
        // or if we have the animal companion feat
        else if (GetHenchPartyState(HENCH_PARTY_SUMMON_COMPANIONS) && (GetHasFeat(FEAT_ANIMAL_COMPANION, OBJECT_SELF) || GetHasSpell(SPELLABILITY_SUMMON_ANIMAL_COMPANION, OBJECT_SELF)))
        {
            object oAssociate = GetAssociate(ASSOCIATE_TYPE_ANIMALCOMPANION, OBJECT_SELF);
            // and no current companion
            if (!GetIsObjectValid(oAssociate))
            {
                //summon companion and decrement us
                SummonAnimalCompanion();
                DecrementRemainingFeatUses(OBJECT_SELF, FEAT_ANIMAL_COMPANION);
            }
        }
    }
    // TODO add auto cure????   if (NonCombatCureEffects())

    
	/*if (!GetHenchAssociateState(HENCH_ASC_DISABLE_INFINITE_BUFF) &&
        !GetAssociateState(NW_ASC_MODE_PUPPET) &&
        (GetLocalInt(OBJECT_SELF, sHenchStopCasting) != 10) &&
        !GetLocalInt(OBJECT_SELF, sHenchDontAttackFlag) &&
        (GetDistanceToObject(oRealMaster) <= 10.0))
    {
        if (GetLevelByClass(CLASS_TYPE_WARLOCK))
        {
            if (!(GetLocalInt(OBJECT_SELF, N2_TALENT_EXCLUDE) & TALENT_EXCLUDE_SPELL))
            {
                if (TryCastWarlockBuffSpells(FALSE))
                {
                    return;
                }
            }
            else
            {
                gbFoundInfiniteBuffSpell = TRUE;
            }
        }
        if (GetLevelByClass(CLASS_TYPE_BARD) &&
            !(GetCreatureNegEffects(OBJECT_SELF) & HENCH_EFFECT_TYPE_SILENCE))
        {
            if (!(GetLocalInt(OBJECT_SELF, N2_TALENT_EXCLUDE) & TALENT_EXCLUDE_ABILITY))
            {
                if (TryCastBardBuffSpells())
                {
                    return;
                }
            }
            else
            {
                gbFoundInfiniteBuffSpell = TRUE;
            }
        }

        if (!gbFoundInfiniteBuffSpell)
        {
            // didn't find anything, don't keep looking
            SetHenchAssociateState(HENCH_ASC_DISABLE_INFINITE_BUFF, TRUE, OBJECT_SELF);
        }
    }*/

    if (HenchCheckArea())
    {
        return;
    }
       // Pausanias: Hench tends to get stuck on follow.
/*    if (GetCurrentAction(OBJECT_SELF) == ACTION_FOLLOW)
    {
        if (GetDistanceToObject(oRealMaster) >= 2.2 &&
            GetAssociateState(NW_ASC_DISTANCE_2_METERS)) return;
        if (GetDistanceToObject(oRealMaster) >= 4.2 &&
            GetAssociateState(NW_ASC_DISTANCE_4_METERS)) return;
        if (GetDistanceToObject(oRealMaster) >= 6.2 &&
            GetAssociateState(NW_ASC_DISTANCE_6_METERS)) return;
        ClearAllActions();
    } */

    ClearWeaponStates();

    if (GetLocalInt(OBJECT_SELF, HENCH_AI_COMBAT_EQUIP))
    {
        DeleteLocalInt(OBJECT_SELF, HENCH_AI_COMBAT_EQUIP);
        if (!GetHenchAssociateState(HENCH_ASC_DISABLE_AUTO_WEAPON_SWITCH))
        {
            ClearAllActions();
            if (GetHenchPartyState(HENCH_PARTY_UNEQUIP_WEAPONS))
            {
                UnequipWeapons();
            }
            else
            {
				HenchEquipDefaultWeapons();
            }
            return;
        }
    }

    int bIsScouting = GetLocalInt(OBJECT_SELF, sHenchScoutingFlag);
    if (bIsScouting)
    {
        if (GetDistanceToObject(oRealMaster) < 6.0)
        {
            SpeakString(sHenchGetOutofWay);
        }
        object oScoutTarget = GetLocalObject(OBJECT_SELF, sHenchScoutTarget);
        if (GetDistanceBetween(oScoutTarget, oRealMaster) > henchMaxScoutDistance)
        {
            DeleteLocalInt(OBJECT_SELF, sHenchScoutingFlag);
            bIsScouting = FALSE;
        }
        else
        {
            if (CheckStealth() && !GetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH))
            {
                SetActionMode(OBJECT_SELF, ACTION_MODE_STEALTH, TRUE);
            }
            ActionMoveToObject(oScoutTarget, FALSE, 1.0);
        }
    }

	//	Familiars always follow their master, even in puppet mode.
	if (GetAssociateType(OBJECT_SELF) == ASSOCIATE_TYPE_FAMILIAR)
	{
		//SpeakString("I'm a familiar!");
		if(!bIsScouting && !GetAssociateState(NW_ASC_MODE_STAND_GROUND) &&
	        (GetNumActions(OBJECT_SELF) == 0) && !GetIsFighting(OBJECT_SELF) &&
	        (GetDistanceToObject(HenchGetFollowLeader()) > GetFollowDistance()))
	    {
			ClearAllActions();
	        HenchFollowLeader();
	    }
	}
	else
	{
		if(!bIsScouting && !GetAssociateState(NW_ASC_MODE_STAND_GROUND) && !GetAssociateState(NW_ASC_MODE_PUPPET) &&
	        (GetNumActions(OBJECT_SELF) == 0) && !GetIsFighting(OBJECT_SELF) &&
	        (GetDistanceToObject(HenchGetFollowLeader()) > GetFollowDistance()))
	    {
	        ClearAllActions();
	        HenchFollowLeader();
	    }
	}

    if(GetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT))
    {
        SignalEvent(OBJECT_SELF, EventUserDefined(EVENT_HEARTBEAT));
    }
}

OnDeath of the henchman:

// gb_assoc_death
/*
	Associate On Death

*/
// ChazM 2/15/07

#include "nw_i0_generic"
#include "nw_i0_plot"


// -----------------------------------------------------------------------------
// Georg, 2003-10-08
// Rewrote that jump part to get rid of the DelayCommand Code that was prone to
// timing problems. If want to see a really back hack, this function is just that.
// -----------------------------------------------------------------------------
void WrapJump(string sTarget)
{
    if (GetIsDead(OBJECT_SELF))
    {
        // * Resurrect and heal again, just in case
        ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectResurrection(), OBJECT_SELF);
        ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectHeal(GetMaxHitPoints(OBJECT_SELF)), OBJECT_SELF);

        // * recursively call self until we are alive again
        DelayCommand(1.0f,WrapJump( sTarget));
        return;
    }
    // * since the henchmen are teleporting very fast now, we leave a bloodstain on the ground
    object oBlood = CreateObject(OBJECT_TYPE_PLACEABLE,"plc_bloodstain", GetLocation(OBJECT_SELF));

    // * Remove blood after a while
    DestroyObject(oBlood,30.0f);

    // * Ensure the action queue is open to modification again
    SetCommandable(TRUE,OBJECT_SELF);

    // * Jump to Target
    JumpToObject(GetObjectByTag(sTarget), FALSE);

    // * Unset busy state
    ActionDoCommand(SetAssociateState(NW_ASC_IS_BUSY, FALSE));

    // * Make self vulnerable
    SetPlotFlag(OBJECT_SELF, FALSE);

    // * Set destroyable flag to leave corpse
    DelayCommand(6.0f, SetIsDestroyable(TRUE, TRUE, TRUE));
}

// -----------------------------------------------------------------------------
// Georg, 2003-10-08
// Changed to run the bad recursive function above.
// -----------------------------------------------------------------------------
void BringBack()
{
    object oSelf = OBJECT_SELF;
    SetLocalObject(oSelf,"NW_L_FORMERMASTER", GetMaster());
    RemoveEffects(oSelf);
    ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectResurrection(), OBJECT_SELF);
    ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectHeal(GetMaxHitPoints(OBJECT_SELF)), OBJECT_SELF);

    object oWay = GetObjectByTag("NW_DEATH_TEMPLE");

    if (GetIsObjectValid(oWay) == TRUE)
    {
        // * if in Source stone area, respawn at opening to area
        if (GetTag(GetArea(oSelf)) == "M4Q1D2")
        {
            DelayCommand(1.0, WrapJump("M4QD07_ENTER"));
        }
        else
        {
            DelayCommand(1.0, WrapJump(GetTag(oWay)));
        }
    }
    else
    {
        WriteTimestampedLogEntry("UT: No place to go");
    }

}

void PrepForDestruction(object oTarget)
{
	SetPlotFlag(oTarget,FALSE);
    SetImmortal(oTarget,FALSE);
    AssignCommand(oTarget,SetIsDestroyable(TRUE,FALSE,FALSE));
}

void KillSprif(object oSprif)
{

	PrepForDestruction(oSprif);
 	DelayCommand(0.2,AssignCommand(GetModule(), DestroyObject(oSprif)));
	

}

void DoTheKill(object oSprif)
{
	
		object oMaster = GetMaster(oSprif);
		RemoveHenchman(oMaster, oSprif);			
		DelayCommand(0.2,KillSprif(oSprif));
		return;


}


void main()
{
	
	object oFreya = GetObjectByTag("freya");
	object oSprif = GetObjectByTag("sprif");


	AssignCommand(GetModule(),DoTheKill(oSprif));	
	
	string sDeathScript = GetLocalString(OBJECT_SELF, "DeathScript");
	if (sDeathScript != "")
		ExecuteScript(sDeathScript, OBJECT_SELF);
	
    // * This is used by the advanced henchmen
    // * Let Brent know if it interferes with animal
    // * companions et cetera
    if (GetIsObjectValid(GetMaster()) == TRUE)
    {
        object oMe = OBJECT_SELF;
        if (GetAssociateType(oMe) == ASSOCIATE_TYPE_HENCHMAN
            // * this is to prevent 'double hits' from stopping
            // * the henchmen from moving to the temple of tyr
            // * I.e., henchmen dies 'twice', once after leaving  your party
            || GetLocalInt(oMe, "NW_L_HEN_I_DIED") == TRUE)
        {
            // -----------------------------------------------------------------------------
            // Georg, 2003-10-08
            // Rewrote code from here.
            // -----------------------------------------------------------------------------

           SetPlotFlag(oMe, TRUE);
           SetAssociateState(NW_ASC_IS_BUSY, TRUE);
           AddJournalQuestEntry("Henchman", 99, GetMaster(), FALSE, FALSE, FALSE);
           SetIsDestroyable(FALSE, TRUE, TRUE);
           SetLocalInt(OBJECT_SELF, "NW_L_HEN_I_DIED", TRUE);
           BringBack();

           // -----------------------------------------------------------------------------
           // End of rewrite
           // -----------------------------------------------------------------------------

        }
        else
        // * I am a familiar, give 1d6 damage to my master
        if (GetAssociate(ASSOCIATE_TYPE_FAMILIAR, GetMaster()) == OBJECT_SELF)
        {
            // April 2002: Made it so that familiar death can never kill the player
            // only wound them.
            int nDam =d6();
            if (nDam >= GetCurrentHitPoints(GetMaster()))
            {
                nDam = GetCurrentHitPoints(GetMaster()) - 1;
            }
            effect eDam = EffectDamage(nDam);
            FloatingTextStrRefOnCreature(63489, GetMaster(), FALSE);
            ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDam, GetMaster());
        }
    }
	
	
}

I have a suspicion as to what might be going on. If the PC is dead/dying when the companion dies/is dying, then maybe the game can’t remove the henchman since the PC is classed invalid as an object perhaps?
'Cause to me it seems like the henchman might actually be gone (it’s hard to tell within the fight 'cause the place of the fighting is a bit dark) BUT the henchman is still in the party with 0 hp. Could that be the case?

EDIT: From what I can see, I’m right in this assumption. I did a test where the companion died or got her hp under 5 % and then the henchman disappeared like he should, but when the PC died first and then the companion, the henchman was still around. Why can’t the game destroy the henchman and remove him completely from the game? It seems that because he’s in the PC’s party and hasn’t been removed, his portrait won’t disappear from the list of those in the party.
Gaah! Screw this buggy game sometimes!

EDIT2: I don’t know if one could use the function RemoveRosterMemberFromParty in this case? Is this henchman considered a roster member? Somehow I doubt that…

No you need to add them to roster with the roster functions.

You can remove the NPC when you resurect your character.

clarify? It sounds like you have a Companion who is the master of a henchman … and you want the henchman to be removed/destroyed if the Companion has less than 5% hp.

i ask for clarification because the scripts above appear to be for the henchman, but a Companion’s hp% (and the disappearing henchman) would be better determined in the Companion’s scripts … ?

1 Like

From NWN1 I know you must SetDestroyable (TRUE) in order to destroy a Henchi. It is somewhere in your script (didn’t analyze it fully) but there is a huge delay, so there is maybe a timing problem.

You may try (Hopefully this works in NWN2) to see if there is a general problem with your Henchi:

// goes to onDamage of then Henchman
void main()
{
SendMessageToPC (GetMaster (OBJECT_SELF), “Fired”);
if (GetCurrentHitPoints() > FloatToInt (GetMaxHitPoints()*0.1)) return;
SetIsDestroyable (TRUE);
DestroyObject (OBJECT_SELF, 1.0);
}

Yes, it might be the companion is the master of the henchman (or it is the PC since the henchman is part of the same party as the PC). The henchman is spawned through the activation of an item which is activated by the player with the companion (clicking activate item and then clicking on the companion).

That script looks like this:

int GetCanLevelUp(object oPC, object oSprif)
{
    int nMasterLevel = GetHitDice(oPC);
    int nMyLevel = GetHitDice(oSprif);

    if (nMasterLevel > (nMyLevel + 4))
    {
        return TRUE;
    }
	
    return FALSE;
}

void PrepForDestruction(object oTarget)
{
	SetPlotFlag(oTarget,FALSE);
    SetImmortal(oTarget,FALSE);
    AssignCommand(oTarget,SetIsDestroyable(TRUE,FALSE,FALSE));
}

void KillSprif(object oSprif)
{

	PrepForDestruction(oSprif);
	DestroyObject(oSprif);


}

void CheckLevel(object oPC, object oSprif)
{

	//SendMessageToPC(oPC,"Check level script is running");
	

	if (GetCanLevelUp(oPC, oSprif))
    {

		//SendMessageToPC(oPC,"Sprif should have leveled up");
        int nLevel = LevelUpHenchman(oSprif,CLASS_TYPE_MAGICAL_BEAST,FALSE,PACKAGE_MAGICBEAST);
        //SendMessageToPC(oPC,"Sprif should have leveled up");
				
		//int nLevel = GetLevelByClass(CLASS_TYPE_MAGICAL_BEAST,oSprif);
		string sLevel = IntToString(nLevel);
		SendMessageToPC(oPC,"Sprif has leveled up! Sprif's level is now (" + sLevel + ").");

	}
	/*
	else
	  SendMessageToPC(oPC,"Sprif can't level up for some reason.");
	  */

}

void AddSprif(object oPC, object oSprif)
{

AddHenchman(oPC,oSprif);

DelayCommand(0.1,CheckLevel(oPC, oSprif));

}


void main()
{

object oFreya = GetObjectByTag("freya");
object oSprif = GetObjectByTag("sprif");

object oPC = GetFirstPC(FALSE);

	if(GetIsObjectValid(oSprif))
	{
            object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;

	}


location lLocation = GetLocation(oFreya);

object oSprifNEW = CreateObject(OBJECT_TYPE_CREATURE, "sprif", lLocation,TRUE);

AddSprif(oPC, oSprifNEW);

}

This part works without problems.

I don’t understand what you’re saying here to be honest, or how that would help me in this situation.

It could be a timing problem, yes, but after a lot of testing it seems to me that the henchman is destroyed but the portrait is still there (so he’s still part of the party) but he has 0 hp. Since you can’t click on on a henchman to see where he is, and the area with the fighting is a bit dark, I don’t know if he’s still in the area, but I think he’s actually gone.
So, as I’ve tried to say before, I believe that the game fails to remove him from the party because the PC is dead/dying. In another test where just the companion reached below 5 % hp (the PC being alive), the henchman disappeared and it worked.

I have tried a few different delays but nothing has made any difference.

With my last tests, I switched all the scripts you see here for the stock henchman scripts, and instead created a script for the ModuleHeartbeat looking like this:

#include "nw_i0_generic"

void PrepForDestruction(object oTarget)
{
	SetPlotFlag(oTarget,FALSE);
    SetImmortal(oTarget,FALSE);
    AssignCommand(oTarget,SetIsDestroyable(TRUE,FALSE,FALSE));
}

void KillSprif(object oSprif)
{

	PrepForDestruction(oSprif);
	DestroyObject(oSprif);


}

void main()
{

object oFreya = GetObjectByTag("freya");
object oSprif = GetObjectByTag("sprif");

object oArea1 = GetArea(oFreya);
object oArea2 = GetArea(oSprif);

object oPC = GetFirstPC(FALSE);


	if(!GetIsObjectValid(oFreya) || GetArea(oSprif) != GetArea(oFreya))
	{
			
	       object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);
			RemoveSummonedAssociate(oMaster, oSprif);				
			DelayCommand(0.2,KillSprif(oSprif));
			return; 
		
	}
	
	else if(GetIsObjectValid(oFreya) && oArea1 == oArea2 && GetHPPercentage(oSprif) <5)
	{
	
	       object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);
			RemoveSummonedAssociate(oMaster, oSprif);				
			DelayCommand(0.2,KillSprif(oSprif));
			return;
	
	}
	
	else if(GetIsObjectValid(oFreya) && oArea1 == oArea2 && GetHPPercentage(oFreya) <5)
	{
	
	       object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);
			RemoveSummonedAssociate(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;

	}

}

Unfortunately, the results are still the same. I don’t know what to do. Maybe I should just make him a companion instead, but I’m afraid that would mess with all sort of ofther things.

Ok I elaborate a little more.

I think you nailed all the problems rights. And by the way there are set, you won’t be able to do exactly what you want. Which means you’ll have to compromise and turn around them.

About the roster, yes the roster and party are 2 differents things. It’s not beceause you are in a party that you are roster member, and it’s not beceause you are a roster member that you are a companions.

A roster NPC is just an NPC tied to the campaign, so it travels between modules and has its change saved. When you using, spanwing despawning roster NPC you need to use the rosters functions. I doubt it will change anything here if you changed your NPC into a roster member, since your problem seems to be tied of the way the “party” is handled by the game.

The solution I propose to you, is that since you can’t remove your NPC from the party when the player is “dead”, it means you can only remove it when the player is alive.

So instead of removing the NPC on the death events, perhaps you should remove it on the “respawn event”.

Or you make a “cutscene” Black screen, resurect the player, take the NPC out, kill the player again, and Fade out from the black screen.

1 Like

Now we get a bit too far into NWN2-specific stuff (for me).

If the henchi seems to be still in party, try “RemoveHenchman”, before destroying it.
If you don’t know if its still there, assign a script to any placeable nearby to check that and report the current state of the henchman…

If the problem still exists, I think it’s a good idea to create a minimalistic module with one area, the PC and the Henchies and the scripting (add some scripts to issue damage). That could be shared with the comunity.

@Shallina - Thanks for the clarification.

I think I have actually found a solution now. I have done two tests and it seems to work. I have kept the stock scripts on the henchman, but instead did some modifications on the module heartbeat script and the item activation script.

The item activation script now looks like this:

int GetCanLevelUp(object oPC, object oSprif)
{
    int nMasterLevel = GetHitDice(oPC);
    int nMyLevel = GetHitDice(oSprif);

    if (nMasterLevel > (nMyLevel + 4))
    {
        return TRUE;
    }
	
    return FALSE;
}

void PrepForDestruction(object oTarget)
{
	SetPlotFlag(oTarget,FALSE);
    SetImmortal(oTarget,FALSE);
    AssignCommand(oTarget,SetIsDestroyable(TRUE,FALSE,FALSE));
}

void KillSprif(object oSprif)
{

	PrepForDestruction(oSprif);
	DestroyObject(oSprif);


}

void CheckLevel(object oPC, object oSprif)
{

	//SendMessageToPC(oPC,"Check level script is running");
	

	if (GetCanLevelUp(oPC, oSprif))
    {

		//SendMessageToPC(oPC,"Sprif should have leveled up");
        int nLevel = LevelUpHenchman(oSprif,CLASS_TYPE_MAGICAL_BEAST,FALSE,PACKAGE_MAGICBEAST);
        //SendMessageToPC(oPC,"Sprif should have leveled up");
				
		//int nLevel = GetLevelByClass(CLASS_TYPE_MAGICAL_BEAST,oSprif);
		string sLevel = IntToString(nLevel);
		SendMessageToPC(oPC,"Sprif has leveled up! Sprif's level is now (" + sLevel + ").");

	}
	/*
	else
	  SendMessageToPC(oPC,"Sprif can't level up for some reason.");
	  */

}

void AddSprif(object oPC, object oSprif)
{

AddHenchman(oPC,oSprif);

DelayCommand(0.1,CheckLevel(oPC, oSprif));

AddRosterMemberToParty("sprif", oPC);
SetIsRosterMemberSelectable("sprif", 1);


}


void main()
{

object oFreya = GetObjectByTag("freya");
object oSprif = GetObjectByTag("sprif");

object oPC = GetFirstPC(FALSE);

	if(GetIsObjectValid(oSprif))
	{
            object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);			
			DelayCommand(0.2,KillSprif(oSprif));
			return;

	}


location lLocation = GetLocation(oFreya);

object oSprifNEW = CreateObject(OBJECT_TYPE_CREATURE, "sprif", lLocation,TRUE);

AddSprif(oPC, oSprifNEW);

}

and the Module Heartbeat script:

#include "nw_i0_generic"

void PrepForDestruction(object oTarget)
{
	SetPlotFlag(oTarget,FALSE);
    SetImmortal(oTarget,FALSE);
    AssignCommand(oTarget,SetIsDestroyable(TRUE,FALSE,FALSE));
}

void KillSprif(object oSprif)
{

	PrepForDestruction(oSprif);
	DestroyObject(oSprif);


}

void main()
{

object oFreya = GetObjectByTag("freya");
object oSprif = GetObjectByTag("sprif");

object oArea1 = GetArea(oFreya);
object oArea2 = GetArea(oSprif);

object oPC = GetFirstPC(FALSE);


	if(!GetIsObjectValid(oFreya) || GetArea(oSprif) != GetArea(oFreya))
	{
			
	       object oMaster = GetMaster(oSprif);
		   object oPC = GetFirstPC();
			RemoveHenchman(oMaster, oSprif);
			RemoveSummonedAssociate(oMaster, oSprif);
			RemoveRosterMemberFromParty("sprif",oPC,FALSE);				
			DelayCommand(0.2,KillSprif(oSprif));
			return; 
		
	}
	
	else if(GetIsObjectValid(oFreya) && oArea1 == oArea2 && GetHPPercentage(oSprif) <5)
	{
	
	       object oMaster = GetMaster(oSprif);
		    object oPC = GetFirstPC();
			RemoveHenchman(oMaster, oSprif);
			RemoveSummonedAssociate(oMaster, oSprif);
			RemoveRosterMemberFromParty("sprif",oPC,FALSE);					
			DelayCommand(0.2,KillSprif(oSprif));
			return;
	
	}
	
	else if(GetIsObjectValid(oFreya) && oArea1 == oArea2 && GetHPPercentage(oFreya) <5)
	{
	
	       object oMaster = GetMaster(oSprif);
			RemoveHenchman(oMaster, oSprif);
			RemoveSummonedAssociate(oMaster, oSprif);
			RemoveRosterMemberFromParty("sprif",oPC,FALSE);				
			DelayCommand(0.2,KillSprif(oSprif));
			return;

	}

}

If there are no caveats with this solution I have found, I will consider this solved, but I’ll let the NWN2 master scripters around here have a say first before moving on to other things with my module.

A lot of things stop to functions when a creature is dead. Each time it’s a bit of a “circus” to get “post death” event working correctly.

One of the thing I do often is call a script to be executed on an other object.

When an object cease to exist, all the scripts “handled by it” are shut down. So on the “death event” I call
ExecuteScript(“my_death_actions”,AnOtherObject);

But sometime it’s not enought.

1 Like

Yep, I know. That’s why I, from earlier instructions from you, always try to make the module execute a script when a creature dies, especially if there are delays.

But since you haven’t mentioned any caveats with my current solution, I think I’ll leave it as it is, until maybe someone like @kevL_s or @travus or @Lance_Botelle, or some other script master of NWN2, tell me to do otherwise.

Thank you everyone for trying to help! I’m glad I managed to find a solution, and on my own no less (with my limited scripting knowledge, I think that is quite a feat, so I pat myself on the back for this).

2 Likes

The thing is: “if it works it’s all good”.

It’s better to have something ugly which works, than something beautifull which doesn’t. The first criteria for something beautifull is to be functional.

We aren’t releasing mods for players to stare at our scripts :slight_smile:

1 Like

@andgalf

Sorry, I posted the wrong script. (I also note that you have your solution, so that’s good.)

For the record, here is my own custom function for removing creature/henchmen… which I fire from the companion’s OnDeath. (Avoids any heartbeat reliance.)

///////////////////////////////////////////////////////////////////////////////////////////////////
// UNSUMMON A CREATURE FROM PLAYER
///////////////////////////////////////////////////////////////////////////////////////////////////
void UnsummonCreature(object oCreature, object oPC);
void UnsummonCreature(object oCreature, object oPC) 
{
	location lTarget = GetLocation(oCreature);
	effect eVisual = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_3, FALSE);
    ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVisual, lTarget); 
		
	RemoveSummonedAssociate(oPC, oCreature); // ADD TO ALLOW FUNCTION VERSATILITY
	
	RemoveHenchman(oPC, oCreature);
	DestroyObject(oCreature, 0.01, FALSE);
}

Here is the sort of thing that calls the above function that I have, but you can adjust conditions to suit your own needs … In this case, it is called on the companion’s OnDeath, but you can have it call from elsewhere.

   object oArea = GetArea(OBJECT_SELF); 
   object oAssociate = GetFirstObjectInArea(oArea);
	
	while(oAssociate != OBJECT_INVALID)
	{		
		if(GetMaster(oAssociate) == OBJECT_SELF)
		{		
			int iTYPE = GetAssociateType(oAssociate);
			
			if(iTYPE == ASSOCIATE_TYPE_HENCHMAN)
			{
				AssignCommand(oAssociate, ClearAllActions(TRUE));
				UnsummonCreature(oAssociate, oFMDied);
			}
		}
		
		oAssociate = GetNextObjectInArea(oArea);
	}
2 Likes

@andgalf

in the function

void AddSprif(object oPC, object oSprif)
{
	AddHenchman(oPC,oSprif);

	DelayCommand(0.1,CheckLevel(oPC, oSprif));

	AddRosterMemberToParty("sprif", oPC);
	SetIsRosterMemberSelectable("sprif", 1);
}

if Sprif is merely a henchman i dont think he should be on the roster, nor added to the party as a Companion …

(untested but I just don’t believe that henchmen are rostermembers)

1 Like

@kevL_s - Making the henchman a roster member is what solved the whole thing for me.

EDIT: He is still not clickable, so it seems he’s technically, in some way, not a companion. I don’t know…

@Lance_Botelle - Since you also use the same functions as I did at first with RemoveHenchman and RemoveSummonedAssociate, I doubt your scripts would work for me or improve anything.
I’ll go with my solution for the time being, since I haven’t been able to get anything else to work without errors.

@andgalf

OK, I see where you use them …

Just saying, these are what worked for me. :smiley:

EDIT: But I only see you use the lines in your HB script and not your other scripts … so maybe you missed it there?

That is, you do not use this line (which may be what you needed) until your HB script. Maybe if you had it on your other scripts (like I did), it may have been of help?

RemoveSummonedAssociate(oPC, oCreature); // ADD TO ALLOW FUNCTION VERSATILITY

EDIT: OK, I see another script you posted where you also tried it, but I suspect it was due to another reason it failed there.

Anyway, it’s there if you ever want to try it …

EDIT: Also, I believe I had to do the loop check like I do to ensure the henchmen were found.

Alright, we’ll see. If I encounter problems with what I have now, I’ll go back and add this to all those scripts.
Thanks!

I hope me using this kind of HB script on the module won’t cause things to crash for the game or slow it down too much. I mean, there’s not that much happening in the script so…