Custom boss script

I’ve got an idea for a Boss fight, but it is going to need some scriptwork to help bring it to life. Any scripting guru’s keen to give it a shot?

It shouldn’t be anything too drastic, hopefully, as it should be a short script to check a few things. Once checked, then stop the fight, switch the boss and his “minions” to commoner faction and then start a conversation. I can do the rest of the script-work from a conversation from there.

Ideally it would be something like this?
(Boss will be set to immortal - players can hit it down to 1hp, but cannot kill it)

  • on almost dead -less than 10% hp, have the boss fully heal and perhaps add an local int of some kind to signify this has happened once.
  • then on the second time the boss is low on health, the PC’s will need to lure the boss inside a zone (trigger) and use an item on it to finish the fight and start a conversation.

EDIT: if the zone/trigger part is too hard, happy to do without it.

What do you reckon?
Thanks in advance to any replies, however significant.

Here’s a thought… Why have the boss fully heal when you can just double the hit points ? It makes no difference. Bosses healing can be annoying because you get all excited you’re nearly there and then have to start again.

You could also set a good dose of regeneration on the boss or put heal potions in his inventory.

As for the item to kill the boss you could look at how trolls die because they need fire or acid maybe you can just fiddle with that to fit your item. I think it’s on the on damaged script which is good because then you can put a start conversation on the on death.

Don’t know about the zone stuff.

2 Likes

I can make an attempt at this, but if I know these forums well, then any of the other script gurus will come and “correct” me and do a better script. But still, maybe it can be a good excercise for me.
In my first module I had something similar as to what you’re describing. When a certain amount of damage was done all enemies changed faction and a conversation started.

1 Like

Why have the boss fully heal when you can just double the hit points ?

It’s kinda part of the story, without giving anything away. It’s made quite clear beforehand, that defeating it by normal means, is not an option and that players will be required to lure it “to a certain spot” first. Despite this, you make a valid point and regen and higher HP could work.

Also thought it worth mentioning, the boss has 6 potential “minons” if that’s important. They may be dead by the time he is, but if not, ideally get them to stop fighting would be great. Tags can be c_minion1 - c_minion6 if it helps.

With the minions you can put a faction change on the boss death script before the conversation starts. Or you could put it on the first line of the conversation but that will mean that fighting is still going on for a very brief moment ( should be unnoticeable ).

1 Like

@tfunke - Here’s my attempt. I found one of my old scripts for my first module (probably made by kevL_s if I have to guess) and edited it quite a bit. This is untested. It might work, I don’t know. You need to add some proper tags in the script and a conversation name. I hope you can see where you need to apply this in the script.

//::///////////////////////////////////////////////
//:: Default On Damaged
//:: NW_C2_DEFAULT6
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    If already fighting then ignore, else determine
    combat round
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Oct 16, 2001
//:://////////////////////////////////////////////

#include "hench_i0_ai"
#include "ginc_behavior"
#include "ginc_group"
#include "ginc_ipspeaker"
#include "x0_i0_assoc" // SetAssociateState()

/*
    OnDamaged script for the boss.
    Stops combat when the boss drops below 10 hitpoints. Starts a dialog.
*/


void main()
{

	object oDamager = GetLastDamager();
	object oWeaponUsed = GetLastWeaponUsed(oDamager);
	object oWeapon = GetObjectByTag("tagofweapon");
		
    int iHp = GetCurrentHitPoints(OBJECT_SELF);
	
	if (iHp < 10 && !GetLocalInt(OBJECT_SELF,"healedonetime"))
    {
       	AssignCommand(OBJECT_SELF, ClearAllActions(TRUE));

		int nCurrentHP;
    	int nHP;
		int nHealPercent = 100;
       	effect eFX;
		effect eVisual = EffectNWN2SpecialEffectFile("sp_cure_critical");
		
		SetLocalInt(OBJECT_SELF,"healedonetime",TRUE);
		
		nCurrentHP = GetCurrentHitPoints( OBJECT_SELF );
       	nHP = FloatToInt( IntToFloat(GetMaxHitPoints(OBJECT_SELF)) * IntToFloat(nHealPercent) / 100 ) - nCurrentHP;
        eFX = EffectHeal( nHP );
        ApplyEffectToObject( DURATION_TYPE_PERMANENT,eFX,OBJECT_SELF );
		ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisual, OBJECT_SELF);
	
		return;
    }
	
	
    else if (iHp < 10 && GetLocalInt(OBJECT_SELF,"insidetrigger") && oWeaponUsed == oWeapon)
    {
        GroupChangeToStandardFaction("group_badguys", STANDARD_FACTION_COMMONER);
        ChangeToStandardFaction(OBJECT_SELF, STANDARD_FACTION_COMMONER);
		AssignCommand(OBJECT_SELF, ClearAllActions(TRUE));
		GroupClearAllActions("group_badguys", TRUE);

        object oPC = GetFirstPC(FALSE);
        ClearPartyActions(oPC, TRUE);
		
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion"));
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion2"));
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion3"));
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion4"));
		
        CreateIPSpeaker("tagofthisboss", "bossconversation", GetLocation(oPC), 1.f);
		return;
    }
	

    int iFocused = GetIsFocused();
	
	// I've been damaged so no longer partially focused
	if (iFocused == FOCUSED_PARTIAL)
	{
		SetLocalInt(OBJECT_SELF, VAR_FOCUSED, FOCUSED_STANDARD); // no longer focused
	}
    if (iFocused == FOCUSED_FULL)
	{
        // remain focused
    }
	else if(GetFleeToExit())
	{
        // We're supposed to run away, do nothing
    }
    else if (GetSpawnInCondition(NW_FLAG_SET_WARNINGS))
    {
        // don't do anything?
    }
    else
    {
        object oDamager = GetLastDamager();
        if (!GetIsObjectValid(oDamager))
        {
        // don't do anything, we don't have a valid damager
        }
        else if (!GetIsFighting(OBJECT_SELF))
        {
            if ((GetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE) == HENCH_HEAL_SELF_WAIT) &&
                (GetPercentageHPLoss(OBJECT_SELF) < 30))
            {
                // force heal
                HenchDetermineCombatRound(OBJECT_INVALID, TRUE);
            }
            else if (!GetIsObjectValid(GetAttemptedAttackTarget()) && !GetIsObjectValid(GetAttemptedSpellTarget()))
            {
//    Jug_Debug(GetName(OBJECT_SELF) + " responding to damage");
                if (GetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL))
                {
                    HenchDetermineSpecialBehavior(oDamager);
                }
                else
                {
                    HenchDetermineCombatRound(oDamager);
                }
            }
        }
    }
    if(GetSpawnInCondition(NW_FLAG_DAMAGED_EVENT))
    {
        SignalEvent(OBJECT_SELF, EventUserDefined(EVENT_DAMAGED));
    }


}

The OnEnter script for the zone trigger:

void main()
{

object oEnter = GetEnteringObject();
object oBoss = GetObjectByTag("tagofboss");

	if(oEnter == oBoss && GetLocalInt(oBoss,"healedonetime"))
	{

		SetLocalInt(oBoss,"insidetrigger",TRUE);

	}


}

The OnExit script of the zone trigger:

void main()
{

object oExit = GetExitingObject();
object oBoss = GetObjectByTag("tagofboss");

	if(oExit == oBoss)
	{

		SetLocalInt(oBoss,"insidetrigger",FALSE);

	}


}

EDIT: I’m not at all sure about the GroupChangeToStandardFaction as I don’t remember if you need to set that in advance and how to do that properly. So there might be some glitch where the minions won’t turn to commoner faction at the moment.

That’s sweet @andgalf thank you! I’ll test it first thing in the morning, as this looks like it might take me a bit to setup and test. I should mention, the item to be used on the boss, is a useable miscellaneous item and not a weapon,which I think your script is alluding to at the moment, from what I can tell.
To make the item, I might copy a blueprint of a throwable item, or an item that can be used on an NPC.

@Tsongo a faction change is a good idea. I’ve got another faction made up, called “bandits” that is hostile to everything, including hostiles, except for commoners.

Making an item to use to trigger conversation is a bit like the OnHit script for the frog you made.

First make the item you want, and add the item property “Cast Spell: Activate Item”.
Secondly, make a script with the following name: “i_[ITEMTAG]_ac”, so if the tag of your item is “fightendingitem”, name the script “i_fightendingitem_ac”.

For the script itself, you can try this in it:

void main()
{
	object oPC = GetItemActivator();
	object oTarget = GetItemActivatedTarget();
	object oTrigger = GetObjectByTag("trigger_that_you_want_it_to_stand_in");
	if (GetTag(oTarget) == "boss_tag" && (GetIsInSubArea(oTarget, oTrigger))
	{
		ChangeToStandardFaction(oTarget, 1);
		AssignCommand(oTarget, ClearAllActions(TRUE));
		AssignCommand(oTarget, ActionStartConversation(oPC));
	}
}
1 Like

If that’s the case then I don’t think that the script will work properly as I use the GetLastWeaponUsed.
As long as it is an item that damages the boss everything should work though.

However, you can do like Akhacha suggests and have the item, when it’s activated, run the conversation, but I’m still not sure that would cover everything. You would still need to use the change of faction and make your companions stop fighting, like I do in my script.

1 Like

You likely would want more things in there! It wasn’t intended to be a complete script for this particular situation, just a basic framework to show how things could potentially work, with the remaining blanks to be filled in.

A word on luring the boss into a zone to become killable. This technique was used in late game boss fight in BGR and while it works in theory, it can be problematic during game play.

The last time I played BGR this fight took four tries to complete, not because my party wasn’t strong enough, but because the multiple scripts, magical effects, and triggers could fail to work in the chaos of a fight.

If possible, have the boss chase the PCs into a large enough kill zone while not in combat to ensure the change in invulnerability is applied while not in combat.

Hope that helps.

2 Likes

Woohoo! @andgalf : it all works as planned, well done!
I had some issues earlier with the script not compiling, but remembered @Lance_Botelle 's advice about rebooting the computer from time to time. After I rebooted it, the script compiled and it all worked.

Thanks heaps!
Just to clarify, the “tagofcompanion” 1-4, are they my companions or the NPC’s companions?

2 Likes

That’s the tags of the PCs (the player’s) companions.

1 Like

@tfunke
Really odd that you should need to do that. Can’t remember ever having to do that for it to compile. However, just as Lance says, you do need to reboot the computer from time to time because the game gets laggy after a while, and for me that solves that particular issue.

Another important thing to keep in mind. I often use the function CreateIPSpeaker in my scripts. I know that Lance isn’t too fond of this function. The good thing with it is that if it fails it will continue to try and run again, but it does in fact fail from time to time. Still, that function is the safest way to make sure a conversation fires when there’s a lot more going on. I often tend to use SetCutsceneMode in conjunction with this to make sure the player can’t run away and do something else. However, this may cause, and has caused, players to believe there’s a bug and that the game freezes for no reason when in reality it just waits for the CreateIPSpeaker to really run.
Maybe in this script I should have used ActionStartConversation in this case…

1 Like

Oh thanks, that’s good to keep in mind.
I did notice when I tested it, that directly after the conversation had ended (after the fight), for about 10 seconds I couldn’t bring up the Escape menu to exit the game. Was odd, but it did come up after about 10 seconds.
Is it possible to give the Ipoint speaker a new tag after it is created, that way I can destroy it in the conversation afterwards and it wont keep having its heartbeat script firing, for the remainder of the module. Doubtful it would be a big deal either way, though.
Also, it might be nice to have a text pop up over the PC’s head, to tell them the NPC has restored its wounds (on the first heal?) Am i now getting greedy? haha

As for ActionStartConversation… which do you think would be better?

Thanks again.

I have never had to destroy an ipoint speaker when using CreateIPSpeaker. I always, maybe wrongly but it has always worked, given the ipoint speaker the same tag as someone I want to own the conversation, like the boss in this case or a companion or an NPC that’s in the area near the PC.

ActionStartConversation is a lot better to use in most cases, BUT when it comes to firing a conversation where a lot could go wrong, like in this case when all the characters are to stop fighting and whatnot, it isn’t reliable, I’m sad to say. I’ve often wanted to use that function instead but have had to use CreateIPSpeaker to make sure the conversation fires everytime. The big problem is that that like 50 % of the time CreateIPSpeaker fires right away, but if you’re unlucky it can take up to 5 min for it to fire. The good thing is that it ALWAYS fires (ok, I think kevL_s pointed that there are som REALLY rare instances where it can fail, but anyway). That’s why I always go into SetCutsceneMode to make sure the player doesn’t run away and do other stuff, but that makes the player sometimes wondering if the game is buggy and has gotten stuck for some reason.
I bet someone like @Lance_Botelle has some ideas about this, and I would guess has some really complex scripts to make sure that the ActionStartConversation really fires, but this is the way I’ve always done it.
When I can though, I always try to use ActionStartConversation. I have a script that always works for me when the party is running around, enters a trigger and I need a conversation to start between a PC and a companion. That script that I use for that occation never fails (but this is in peaceful situations where the party is just running around in an area and there are no hostiles), but now we’re in the middle of a battle where the game needs to stop everyone from fighting, and then all sorts of stuff can go wrong because (if I may be so bold) how badly programmed this whole game is.

I think I can do that. A moment…

//::///////////////////////////////////////////////
//:: Default On Damaged
//:: NW_C2_DEFAULT6
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    If already fighting then ignore, else determine
    combat round
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Oct 16, 2001
//:://////////////////////////////////////////////

#include "hench_i0_ai"
#include "ginc_behavior"
#include "ginc_group"
#include "ginc_ipspeaker"
#include "x0_i0_assoc" // SetAssociateState()

/*
    OnDamaged script for the boss.
    Stops combat when the boss drops below 10 hitpoints. Starts a dialog.
*/


void main()
{

	object oDamager = GetLastDamager();
	object oWeaponUsed = GetLastWeaponUsed(oDamager);
	object oWeapon = GetObjectByTag("tagofweapon");
		
    int iHp = GetCurrentHitPoints(OBJECT_SELF);
	
	if (iHp < 10 && !GetLocalInt(OBJECT_SELF,"healedonetime"))
    {
       	AssignCommand(OBJECT_SELF, ClearAllActions(TRUE));

		int nCurrentHP;
    	int nHP;
		int nHealPercent = 100;
       	effect eFX;
		effect eVisual = EffectNWN2SpecialEffectFile("sp_cure_critical");
		
		SetLocalInt(OBJECT_SELF,"healedonetime",TRUE);
		
		object oPC = GetFirstPC();
		
		FloatingTextStringOnCreature("The boss has healed up.", oPC, FALSE);	
		
		nCurrentHP = GetCurrentHitPoints( OBJECT_SELF );
       	nHP = FloatToInt( IntToFloat(GetMaxHitPoints(OBJECT_SELF)) * IntToFloat(nHealPercent) / 100 ) - nCurrentHP;
        eFX = EffectHeal( nHP );
        ApplyEffectToObject( DURATION_TYPE_PERMANENT,eFX,OBJECT_SELF );
		ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisual, OBJECT_SELF);
	
		return;
    }
	
	
    else if (iHp < 10 && GetLocalInt(OBJECT_SELF,"insidetrigger") && oWeaponUsed == oWeapon)
    {
        GroupChangeToStandardFaction("group_badguys", STANDARD_FACTION_COMMONER);
        ChangeToStandardFaction(OBJECT_SELF, STANDARD_FACTION_COMMONER);
		AssignCommand(OBJECT_SELF, ClearAllActions(TRUE));
		GroupClearAllActions("group_badguys", TRUE);

        object oPC = GetFirstPC(FALSE);
        ClearPartyActions(oPC, TRUE);
		
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion"));
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion2"));
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion3"));
		SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tagofcompanion4"));
		
        CreateIPSpeaker("tagofthisboss", "bossconversation", GetLocation(oPC), 1.f);
		return;
    }
	

    int iFocused = GetIsFocused();
	
	// I've been damaged so no longer partially focused
	if (iFocused == FOCUSED_PARTIAL)
	{
		SetLocalInt(OBJECT_SELF, VAR_FOCUSED, FOCUSED_STANDARD); // no longer focused
	}
    if (iFocused == FOCUSED_FULL)
	{
        // remain focused
    }
	else if(GetFleeToExit())
	{
        // We're supposed to run away, do nothing
    }
    else if (GetSpawnInCondition(NW_FLAG_SET_WARNINGS))
    {
        // don't do anything?
    }
    else
    {
        object oDamager = GetLastDamager();
        if (!GetIsObjectValid(oDamager))
        {
        // don't do anything, we don't have a valid damager
        }
        else if (!GetIsFighting(OBJECT_SELF))
        {
            if ((GetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE) == HENCH_HEAL_SELF_WAIT) &&
                (GetPercentageHPLoss(OBJECT_SELF) < 30))
            {
                // force heal
                HenchDetermineCombatRound(OBJECT_INVALID, TRUE);
            }
            else if (!GetIsObjectValid(GetAttemptedAttackTarget()) && !GetIsObjectValid(GetAttemptedSpellTarget()))
            {
//    Jug_Debug(GetName(OBJECT_SELF) + " responding to damage");
                if (GetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL))
                {
                    HenchDetermineSpecialBehavior(oDamager);
                }
                else
                {
                    HenchDetermineCombatRound(oDamager);
                }
            }
        }
    }
    if(GetSpawnInCondition(NW_FLAG_DAMAGED_EVENT))
    {
        SignalEvent(OBJECT_SELF, EventUserDefined(EVENT_DAMAGED));
    }


}
1 Like

Ahh I see, sounds pretty hectic and complicated, but I’m grateful that others have spent time figuring it out. Thanks also for explaining all that, I’m grateful for your help. I’ve tested your scripts above now, in a live environment of the module (with mobs around etc) and it works each time. Sometimes though, you may need to lure the boss away from the trigger and back onto it… as a result, I have changed the dialog beforehand, to give a roleplay reason, why a player might need to do this :slight_smile:
Only issue I’ve noticing, is that after the dialog (after the fight) is over, the former guardians of the boss who didn’t die, reappear after about 5 seconds and immediately continue wailing on the party hehe.
I tried to delete all instances of them via the conversation, but they are still reappearing, so I’m guessing your script somehow “locks them away” somewhere safely, until after the dialog is over?

@andgalf
@tfunke

The key thing for most situation regarding conversations is to make sure the conversation only fires when other situations are out of the way that may interfere. So, if there is a risk of combat, I usually try to avoid situations of conversations starting until a combat is over.

That said, I do have a few functions/scripts for handling conversations. Most hinge around a function I call “ForceConversation”, which is basically a function that calls itself every 0.2 secs that resets actions and forces the conversation as soon as possible. I would say that (to date) it has been 100% reliable for me. (No point me posting, as it has stuff that would mean nothing to anyone else. But, that extra stuff is the kind of thing others may include to ensure their own conversations work as expected. The key point is that it calls itself until the conversation is underway.)

There are times, however, when that function is called around other script actions, such as setting cutscene modes and releasing just prior calling this function.

The function itself, however, may call objects, creatures, different conversations, and even work with iPoints. Like everything else, it has evolved as the campaign has grown, but the end result is a solid function, for me at least.

2 Likes

Reappear? I don’t lock any creatures away to my knowledge. I don’t understand what you’re describing to be honest…do they disappear when the conversation starts or what? How do they do that? This whole thing you’re describing seems to be totally outside of anything in my script.

From your description, that actually sounds a lot like what the CreateIPSpeaker is already doing, but maybe that shorter delay makes your function work even better than CreateIPSpeaker, I don’t know. I’ve always found CreateIPSpeaker reliable enough for players. I usually just mention that if it appears that nothing is happening, to wait a few minutes for the conversation to fire. I don’t think it has ever failed for me in my tests.

1 Like

No, I wasn’t implying your script did the ‘locking away’ as such, more that the way it is setup, must be similar to how a speak trigger with the variable CombatCutScene=1 is set up.
If you lay down a speak trigger and set it to CombatCutscene=1, which I generally always do anyway just to be safe, then any enemies that were in combat when the conversation starts, disappear, only to then reappear after the conversation has ended.