Bumpable NPCs - help with scripting

I’m trying some weird stuff in a quest in my module right now…I would want the main character, and all the companions, to die if they bump into a certain NPC. How would one do that? Is that possible? I know of the SetBumpState function but…Where would one put a script on the NPC to make this possible? OnSpawn? OnPerception? I would guess I should just implement the script I’m using in the module properties for OnPlayerDeath but…where would you put such a script, and is there a function for get-last-bumped-into or something similar?

The NPC is walking around by the way, and the intent is for the player and the party to avoid the NPC.

what about a pseudo-heartbeat on the npc which fires 3-4 times a second, looping through the party and measuring the distance to the npc? a custom aura with a really small distance could also work.

Make a very small trigger. Place a waypoint at the center of that trigger (the flag being center). Spawn the NPC in question in at the waypoint when the party enters the area. Place this script on the OnExit of the trigger:

//	Place on the trigger OnExit handler.

void main()
{
	object oPC = GetFirstPC();
	object oNPC = GetObjectByTag("npc_tag");	// <--- the tag of the npc
	
	if (GetExitingObject() == oNPC)
	{
		object oFM = GetFirstFactionMember(oPC, FALSE);
		while (GetIsObjectValid(oFM))
		{
			ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(FALSE, TRUE, TRUE), oFM);
			oFM = GetNextFactionMember(oPC, FALSE);
		}
	}
}

If the NPC is bumped out of the trigger, the script executes - killing the whole party.

1 Like

Thanks for the replies guys. In this scene the NPC walks around and the player and the companions are to avoid the NPC. That’s why I don’t think that travus script may work as I intended the whole thing.

How would one do a custom aura @Semper?

Hi

Check out the creatures OnBlocked Script hook.

Cheers, Lance.

@Lance You mean like the NPC’s OnBlocked or one of the companion’s OnBlocked? I mean I can’t check the player character’s OnBlocked…or can I?

1 Like

Just corrected my post to read “creatures” (which I originally had) … but meant when a PC hits them. :slight_smile:

I have not checked it, but it may well fire when a PC hits them as much as a PC hitting them … BUMP!

PCs do not have the OnBlocked, so it has to be done from creature perspective.

Thanks, Lance.

EDIT: It may NOT work because I can only find GetBlockingDoor as a pickup. :frowning:

I just modified the creatures OnBlocked to this. Would this work or…?

//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    This will cause blocked creatures to open
    or smash down doors depending on int and
    str.
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Nov 23, 2001
//:://////////////////////////////////////////////

#include "hench_i0_generic"
#include "ginc_companion"


void main()
{
    object oDoor = GetBlockingDoor();
	object oPC = GetFirstPC();

//    Jug_Debug("******" + GetName(OBJECT_SELF) + " is blocked by " + GetName(oDoor));
	
    if (GetObjectType(oDoor) == OBJECT_TYPE_CREATURE)
    {
        object oFM = GetFirstFactionMember(oPC, FALSE);
		while (GetIsObjectValid(oFM))
		{
			ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(FALSE, TRUE, TRUE), oFM);
			oFM = GetNextFactionMember(oPC, FALSE);
		}
		
        
    }

 

    int iAggPursue = !GetLocalInt(OBJECT_SELF, sHenchScoutMode);
    if (!iAggPursue)
    {
        if (GetPlotFlag(oDoor))
        {
            return;
        }
        if (GetIsTrapped(oDoor))
        {
            return;
        }
    }

    int nSel;
    if (iAggPursue)
    {
        nSel = 0xffff;
    }
    else
    {
        nSel = GetHenchOption(HENCH_OPTION_UNLOCK | HENCH_OPTION_OPEN);
    }
    if((HENCH_OPTION_OPEN & nSel) && GetIsDoorActionPossible(oDoor, DOOR_ACTION_OPEN) &&
        GetAbilityScore(OBJECT_SELF, ABILITY_INTELLIGENCE) >= 7 &&
        GetCreatureUseItems(OBJECT_SELF))
    {
//	    Jug_Debug("******" + GetName(OBJECT_SELF) + " doing open" + GetName(oDoor) + " scout mode + " + IntToString( GetLocalInt(OBJECT_SELF, sHenchScoutMode)));

        DoDoorAction(oDoor, DOOR_ACTION_OPEN);
        if (!iAggPursue)
        {
            SetLocalInt(OBJECT_SELF,"OpenedDoor", TRUE);
        }
    }
    else if ((HENCH_OPTION_UNLOCK & nSel) && GetIsDoorActionPossible(oDoor, DOOR_ACTION_UNLOCK))
    {
        DoDoorAction(oDoor, DOOR_ACTION_UNLOCK);
    }
    else if((HENCH_OPTION_UNLOCK & nSel) && GetIsDoorActionPossible(oDoor, DOOR_ACTION_BASH))
    {
        DoDoorAction(oDoor, DOOR_ACTION_BASH);
    }
}

OK, tested it. It didn’t work. I thought you could fool the game with the

if (GetObjectType(oDoor) == OBJECT_TYPE_CREATURE)

but no…

Ok, I’ve now made a script (and this is above my head, so I don’t quite know what I’m doing now, I’m sorry to say, but at least I’m trying). I looked at some script that KevL_s did for me a while back…and I’m not sure I understand the pseudo-heartbeat thing entirely but…Well, it looks like this. One thing I don’t quite grasp is…do I put this on OnHeartbeat on the NPC (but the OnHeartbeat runs just every 6 seconds, right?) or where do I put it? Can I even use something like this?

(I stole small parts of this script from my second module and there it ran from a conversation I realized, but how would I do it in this instance?)

const float  DISTANCE_TO_PC = 0.2f;

void PseudoHeartbeat()
{
	object oPC = GetFirstPC();
	object oNPC1 = GetObjectByTag("commdollhout");
	object oNPC2 = GetObjectByTag("commdollh2");
	object oNPC3 = GetObjectByTag("commdollh3");
	
	if (GetDistanceBetween(oPC,oNPC1) < DISTANCE_TO_PC || GetDistanceBetween(oPC,oNPC2) < DISTANCE_TO_PC || GetDistanceBetween(oPC,oNPC3) < DISTANCE_TO_PC)
    {
		
		object oFM = GetFirstFactionMember(oPC, FALSE);
		while (GetIsObjectValid(oFM))
		{
			ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(FALSE, TRUE, TRUE), oFM);
			oFM = GetNextFactionMember(oPC, FALSE);
		}
		
	}
	
	DelayCommand(0.2f, PseudoHeartbeat());
	
}

void main()
{
	
DelayCommand(0.0f, PseudoHeartbeat());

}

I tried putting it on OnEnter of the area but nothing seems to happen when bumping into the NPCs.

You can use “SetCustomHeartbeat” to change the interval.
Besides, with:

your script will fire 5 times per second (you should probably add a way to exit the recursing loop)

Ok, so let’s say I write the function SetCustomHeartbeat to change the rate. Where would I put that? OnEnter of the area OnHeartbeat of the NPC or what? I don’t get it.

My script doesn’t work as it is so I am at a loss at what to do.

Is 5 times per second too much for the game to handle you mean? I guess I could maybe do with once every second instead, I don’t know…

@andgalf

You can apply a pseudo heartbeat from any script.

How often you call it is up to you, but be careful not to do so too often as that may cause performance issues. (Especially if you intend to call from every creature. Personally, I would consider from the MODULE perspective, which would amount to fewer calls.)

Normally, your only recourse for applying one to do what you are trying to do is from either one of the creatures hook scripts, or one of the PCs hook scripts. Although, as I say, I would probably go with the module heartbeat.

The best check would be to use distance between the PC and creature and determine what you would qualify as close enough to be a collision.

Lance.

A pseudo heartbeat within the heartbeat.
This checks every second if any party member is within DISTANCE_TO_PC of the NPCs. If so, kills party.

//	Place on area OnHeartbeat handler.

const float  DISTANCE_TO_PC = 0.2f;

void KillParty(object oPC)
{
	object oFM = GetFirstFactionMember(oPC, FALSE);
	while (GetIsObjectValid(oFM))
	{
		ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(FALSE, TRUE, TRUE), oFM);
		oFM = GetNextFactionMember(oPC, FALSE);
	}
}

void PseudoHeartbeat()
{
	object oPC = GetFirstPC();
	object oFM = GetFirstFactionMember(oPC, FALSE);
	while (GetIsObjectValid(oFM))
	{
		float fNPC1 = GetDistanceBetween(oFM, GetObjectByTag("commdollhout"));
		float fNPC2 = GetDistanceBetween(oFM, GetObjectByTag("commdollh2"));
		float fNPC3 = GetDistanceBetween(oFM, GetObjectByTag("commdollh3"));	
	
		if (fNPC1 < DISTANCE_TO_PC || fNPC2 < DISTANCE_TO_PC || fNPC3 < DISTANCE_TO_PC)
		{
			KillParty(oPC);
		}
		
		oFM = GetNextFactionMember(oPC, FALSE);
	}
}

void main()
{
	PseudoHeartbeat();	
	DelayCommand(1.0f, PseudoHeartbeat());
	DelayCommand(2.0f, PseudoHeartbeat());
	DelayCommand(3.0f, PseudoHeartbeat());
	DelayCommand(4.0f, PseudoHeartbeat());
	DelayCommand(5.0f, PseudoHeartbeat());	
}

@Travus,

PS: That is checking every 0.1 second, not 1.0 second.

Why not have the pseudo heartbeat call itself? (Every 1.0f) But include some kind of terminator. EDIT: Although, depending upon the placement, this may be easier to implement I suppose. :slight_smile: However, I would probably recommend using a variable to check started and have the pseudo heartbeat call itself … just to protect any potential over-zealous heartbeat checks accumulating.

I imagine you could start a faster pseudo heartbeat call on the module after a normal heartbeat script determined a start distance. (I do something similar for a door check.) i.e. When creature and PC distance < x (checked from normal heartbeat), start pseudo heartbeat. Terminator is if if the pseudo check goes above this again.

Once started, the pseudo checks more frequently until it either kills the party, or self terminates of the PC increases the distance beyond x.

Lance.

Oops, fixed.

1 Like

that’s a custom spell (spells.2da) as a spell like ability. you have to create one and give it to the creature in the special abilities tab. the impact script of this spell defines the area of effect. then you have to create a custom effect aoe via vfx_persistent.2da. in there you can define the shape, radius, onenter, onexit and a heartbeat script of the aura. while rather complex, this should be the most elegant solution.

to get an idea or a basis to work from, you can look at the ghast’s stench aura.

@Lance_Botelle - I’m not sure I’m following you here. It seems a bit too advanced for me. I don’t quite understand the thing with the pseudo heartbeat calling itself. You mean to somehow put the function within itself? How would that look coding-wise? I think I would be able to understand better if you gave an example of how this was to look.

I guess I could put the code into the module heartbeat but…well, travus’ way seems much simpler to implement.

@Semper - Ok, it seems indeed elegant but fairly complicated.

I think I’ll try @travus script since that is (for me) simple to understand. Although, I have to ask…The reason you go to 5.0 seconds is because after that the normal heartbeat kicks in? Is that correctly understood? Would I really need to put some kind of integer to make sure the pseudo heartbeat stops when entering a new area? Can it really continue to run once I’ve left the area? I guess though that would be a simple thing to implement. Like if I edited the code like this perhaps:

//	Place on area OnHeartbeat handler.

const float  DISTANCE_TO_PC = 0.2f;

void KillParty(object oPC)
{
	object oFM = GetFirstFactionMember(oPC, FALSE);
	while (GetIsObjectValid(oFM))
	{
		ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(FALSE, TRUE, TRUE), oFM);
		oFM = GetNextFactionMember(oPC, FALSE);
	}
}

void PseudoHeartbeat()
{
	object oPC = GetFirstPC();
	object oFM = GetFirstFactionMember(oPC, FALSE);
	while (GetIsObjectValid(oFM))
	{
		float fNPC1 = GetDistanceBetween(oFM, GetObjectByTag("commdollhout"));
		float fNPC2 = GetDistanceBetween(oFM, GetObjectByTag("commdollh2"));
		float fNPC3 = GetDistanceBetween(oFM, GetObjectByTag("commdollh3"));	
	
		if (fNPC1 < DISTANCE_TO_PC || fNPC2 < DISTANCE_TO_PC || fNPC3 < DISTANCE_TO_PC)
		{
			KillParty(oPC);
		}
		
		oFM = GetNextFactionMember(oPC, FALSE);
	}
}

void main()
{
        SetGlobalInt("killparty",1);
	PseudoHeartbeat();	
	DelayCommand(1.0f, PseudoHeartbeat());
	DelayCommand(2.0f, PseudoHeartbeat());
	DelayCommand(3.0f, PseudoHeartbeat());
	DelayCommand(4.0f, PseudoHeartbeat());
	DelayCommand(5.0f, PseudoHeartbeat());	
}

And then when entering the next area I could run a code like this?

If(GetGlobalInt("killparty))
{
SetGlobalInt("killparty",0);
}

Or maybe that wouldn’t work?

@andgalf

Yes, that’s the sort of thing. I will knock up an example for you, based on your/my scripts.

Sempers method is workable. I also used a system like this to aid in a creature detection system, but ended up removing it as it became quite a cumbersome piece of code for the small return. It also clashed with some newer code I was working on in my module. Nevertheless, the concept does work, but can involve more checks.

Thanks, Lance.

EDIT: Party Killer Based on Proximity to An Object …

QUICK TEST RESULTS

The script does work. You may need to experiment with what fCheckDistance works best for you. I found that 1.0 does work, but experiment with it for what you need. The pseudo heartbeat of 0.2 appears to be fine from a perspective of performance. I tested it in my own module heartbeat script (which already calls a lot of functions) and I did not notice any performance hit. The main thing to remember here, is the lower this value, the more frequent the checks are made, but at higher system requirements. However, do not make it too big, or else the player may be able to move their PC up to the object and away again before the check is made. Be sure to do more extensive testing for your own needs.

DELETED

Yes, the normal heartbeat event fires every 6 seconds. So, with regard to this script, the script will fire and execute the check on the first second. Then the delayed checks will fire during each of the remaining 5 seconds. After that the normal heartbeat will fire the entire script again starting the process over.

Yes, heartbeat events within the module fire all the time even if you’re not in the same area.
You can do something like this in the script which checks to see if the main PC is in the area with the script. If not, it won’t do any checks:

void main()
{
	if (GetArea(GetFirstPC()) == GetArea(OBJECT_SELF))
	{
		PseudoHeartbeat();	
		DelayCommand(1.0f, PseudoHeartbeat());
		DelayCommand(2.0f, PseudoHeartbeat());
		DelayCommand(3.0f, PseudoHeartbeat());
		DelayCommand(4.0f, PseudoHeartbeat());
		DelayCommand(5.0f, PseudoHeartbeat());
	}
}
1 Like

@travus - Ok, so if I understand this correctly: The heartbeat events continues to fire, even if I’ve put it on the heartbeat slot of the area and not the heartbeat slot of the module itself?

I tested your previous code travus, and had to increase the distance to 4.0 for it work properly. Not to say too much to spoil the story, but the NPCs in this area are pretty darn big, so I think that might have something to do with it not working with the distance of 0.2. Your new code makes sence and I understand it thankfully so I’ll try that before taking a look at Lance’s script.