NPC bumping into companion

I need a script that checks if an NPC has bumped into one of the party’s companions. I asked kind of a similar question back in 2020. I checked that thread, but this is a bit of a different situation.

I have an NPC that runs past the PC and his companions. If he runs into one of the companions, I would like to check which of the companions that is. I think this might be very complicated to achieve. I would have liked if there was a function like GetLastBumpedInto or something like that.

It seems that I need to instead use the heartbeat script of the creature to check for distances between the NPC and the companions. However, this is not quite what I’m after. I would have liked to be able to set an integer if the NPC happens to bump into one of the companions. If he doesn’t bump into one of them while running past the party, maybe I can do a GetDistance thing to check which of the companions were nearest to him or something.

It’s frustrating that there seems to be no easy way to do this… or is there?

So…I did my own clunky script based on some old script I got from Lance Botelle. It seems to kind of work, but…well, not entirely. When testing I saw some companion ingame getting bumped clearly by the running NPC, but it wasn’t that companion the script thought were the closest to the NPC. I guess it depends on if the NPC happens to bump the companion that millisecond when the script is running for it to be correct. Anyway, please be kind, here’s my very clunky script:

const string sROSTER_KELLIE = "kellie"; // note: this needs to be the *roster* string-id
const string sROSTER_ASTRID = "astrid"; // note: this needs to be the *roster* string-id
const string sROSTER_BACHAR = "bachar"; // note: this needs to be the *roster* string-id
const string sROSTER_GENEVIEVE = "genevieve"; // note: this needs to be the *roster* string-id
const string sROSTER_NAERTITA = "naertita"; // note: this needs to be the *roster* string-id
const string sROSTER_UWIZHE = "uwizhe"; // note: this needs to be the *roster* string-id
const string sROSTER_GYMETHON = "gymethon"; // note: this needs to be the *roster* string-id

// Pseudo heartbeat check (based on proximity)

void CheckProximity(object oNPC, object oPC, float fCheckDistance)
{	

	object oKellie = GetObjectFromRosterName(sROSTER_KELLIE);
	object oAstrid = GetObjectFromRosterName(sROSTER_ASTRID);
	object oBachar = GetObjectFromRosterName(sROSTER_BACHAR);
	object oGenevieve = GetObjectFromRosterName(sROSTER_GENEVIEVE);
	object oNaertita = GetObjectFromRosterName(sROSTER_NAERTITA);
	object oUwizhe = GetObjectFromRosterName(sROSTER_UWIZHE);
	object oGymethon = GetObjectFromRosterName(sROSTER_GYMETHON);

	object oFM = GetFirstFactionMember(oPC, FALSE);
	
	while (oFM != OBJECT_INVALID)
	{
		float fDisA = GetDistanceBetween(oNPC, oFM);
	
		if(fDisA < fCheckDistance)
		{			
			//DeleteLocalInt(oMod, "Proximitycheck");
			fCheckDistance = fDisA;
				 if (oFM == oKellie)
				 { 
				 	SetLocalInt(oKellie,"noterunner",TRUE);
					
					DeleteLocalInt(oAstrid,"noterunner");
					DeleteLocalInt(oBachar,"noterunner");
					DeleteLocalInt(oGenevieve,"noterunner");
					DeleteLocalInt(oNaertita,"noterunner"); 
					DeleteLocalInt(oUwizhe,"noterunner");
					DeleteLocalInt(oGymethon,"noterunner");
				 } 
				 
				else if (oFM == oAstrid) 
				{ 
				 	SetLocalInt(oAstrid,"noterunner",TRUE);
					
					DeleteLocalInt(oKellie,"noterunner");
					DeleteLocalInt(oBachar,"noterunner");
					DeleteLocalInt(oGenevieve,"noterunner");
					DeleteLocalInt(oNaertita,"noterunner"); 
					DeleteLocalInt(oUwizhe,"noterunner");
					DeleteLocalInt(oGymethon,"noterunner");
				 } 
				else if (oFM == oBachar)
				{ 
				 	SetLocalInt(oBachar,"noterunner",TRUE);
					
					DeleteLocalInt(oKellie,"noterunner");
					DeleteLocalInt(oAstrid,"noterunner");
					DeleteLocalInt(oGenevieve,"noterunner");
					DeleteLocalInt(oNaertita,"noterunner"); 
					DeleteLocalInt(oUwizhe,"noterunner");
					DeleteLocalInt(oGymethon,"noterunner");
				 } 
			
				else if (oFM == oGenevieve)
				{
					SetLocalInt(oGenevieve,"noterunner",TRUE);
					
					DeleteLocalInt(oKellie,"noterunner");
					DeleteLocalInt(oAstrid,"noterunner");
					DeleteLocalInt(oBachar,"noterunner");
					DeleteLocalInt(oNaertita,"noterunner"); 
					DeleteLocalInt(oUwizhe,"noterunner");
					DeleteLocalInt(oGymethon,"noterunner");
				
				
				}
			
				else if (oFM == oNaertita)
				{
					SetLocalInt(oNaertita,"noterunner",TRUE);
					
					DeleteLocalInt(oKellie,"noterunner");
					DeleteLocalInt(oAstrid,"noterunner");
					DeleteLocalInt(oBachar,"noterunner");
					DeleteLocalInt(oGenevieve,"noterunner"); 
					DeleteLocalInt(oUwizhe,"noterunner");
					DeleteLocalInt(oGymethon,"noterunner");
				
				
				}
			
				else if (oFM == oUwizhe)
				{
				
					SetLocalInt(oUwizhe,"noterunner",TRUE);
					
					DeleteLocalInt(oKellie,"noterunner");
					DeleteLocalInt(oAstrid,"noterunner");
					DeleteLocalInt(oBachar,"noterunner");
					DeleteLocalInt(oGenevieve,"noterunner"); 
					DeleteLocalInt(oNaertita,"noterunner");
					DeleteLocalInt(oGymethon,"noterunner");
				
				
				} 
			
				else if (oFM == oGymethon)
				{
				
					SetLocalInt(oGymethon,"noterunner",TRUE);
					
					DeleteLocalInt(oKellie,"noterunner");
					DeleteLocalInt(oAstrid,"noterunner");
					DeleteLocalInt(oBachar,"noterunner");
					DeleteLocalInt(oGenevieve,"noterunner"); 
					DeleteLocalInt(oNaertita,"noterunner");
					DeleteLocalInt(oUwizhe,"noterunner");
				
				
				} 
				
		}
		
		oFM = GetNextFactionMember(oPC, FALSE);
	}	
	
	if(GetIsDead(oPC) || GetArea(oNPC) != GetArea(oPC))
	{
		DeleteLocalInt(oPC, "Proximitycheck"); return;
	}
	
	else
	{	
		// Check proximity every 0.2 seconds once detected (adjust according to performance & need)
		DelayCommand(0.1, CheckProximity(oNPC,oPC, fCheckDistance));
	}
}

#include "ginc_ipspeaker"

void main()
{        

	// Assumes single player game only (no dm)	
	// Change according to hook type. eg: object oPC = GetFirstEnteringPC(); for on client area enter
	object oPC = GetFirstPC();
	
	// Track on module
	//object oMod = GetModule();
	
	//int iCheck = GetLocalInt(oMod, "Proximitycheck");
	int iCheck = GetLocalInt(oPC, "Proximitycheck");
	
	// These objects cannot die or it will break the code
	object oNPC = GetObjectByTag("runner");

	if((GetArea(oNPC) == GetArea(oPC)) && iCheck == 0)
	{	
		// Track pseudo started
		SetLocalInt(oPC, "Proximitycheck", 1);			
			
		// USing 2.0, but you could provide any value here by a local float
		// Experiment with this value to get the "bump" distance you require
		float fCheckDistance = 100000.0f;						
		//CheckProximity(oNPC, oPC, fCheckDistance, oMod);
		CheckProximity(oNPC, oPC, fCheckDistance);
		
	}
}

Nevermind. I found another solution. I just made a script where the runner runs towards a certain companion and then continues to a waypoint. I think it actually will look a bit better since the NPC really bumps the character, otherwise it can be that he doesn’t bump anyone depending on where they stand:

const string sROSTER_KELLIE = "kellie"; // note: this needs to be the *roster* string-id
const string sROSTER_ASTRID = "astrid"; // note: this needs to be the *roster* string-id
const string sROSTER_BACHAR = "bachar"; // note: this needs to be the *roster* string-id
const string sROSTER_GENEVIEVE = "genevieve"; // note: this needs to be the *roster* string-id
const string sROSTER_NAERTITA = "naertita"; // note: this needs to be the *roster* string-id
const string sROSTER_UWIZHE = "uwizhe"; // note: this needs to be the *roster* string-id
const string sROSTER_GYMETHON = "gymethon"; // note: this needs to be the *roster* string-id

#include "ginc_param_const"

void main()
{

	object oKellie = GetObjectFromRosterName(sROSTER_KELLIE);
	object oAstrid = GetObjectFromRosterName(sROSTER_ASTRID);
	object oBachar = GetObjectFromRosterName(sROSTER_BACHAR);
	object oGenevieve = GetObjectFromRosterName(sROSTER_GENEVIEVE);
	object oNaertita = GetObjectFromRosterName(sROSTER_NAERTITA);
	object oUwizhe = GetObjectFromRosterName(sROSTER_UWIZHE);
	object oGymethon = GetObjectFromRosterName(sROSTER_GYMETHON);

    object oTarg = GetObjectByTag("runner");
	object oPC = GetFirstPC();
	object oWP = GetObjectByTag("runnerwp2");
 
	if(GetFactionEqual(oKellie,oPC))
	{
    	AssignCommand(oTarg, ActionForceMoveToObject(oKellie, 1));
		DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
		SetLocalInt(oKellie,"noterunner",TRUE);
	
	}
	else if(GetFactionEqual(oAstrid,oPC))
	{
    	AssignCommand(oTarg, ActionForceMoveToObject(oAstrid, 1));
		DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
		SetLocalInt(oAstrid,"noterunner",TRUE);
	
	}
	else if(GetFactionEqual(oBachar,oPC))
	{
    	AssignCommand(oTarg, ActionForceMoveToObject(oBachar, 1));
		DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
		SetLocalInt(oBachar,"noterunner",TRUE);
	
	}
	else if(GetFactionEqual(oGenevieve,oPC))
	{
    	AssignCommand(oTarg, ActionForceMoveToObject(oGenevieve, 1));
		DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
		SetLocalInt(oGenevieve,"noterunner",TRUE);
	
	}
	else if(GetFactionEqual(oNaertita,oPC))
	{
    	AssignCommand(oTarg, ActionForceMoveToObject(oNaertita, 1));
		DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
		SetLocalInt(oNaertita,"noterunner",TRUE);
	
	}
	else if(GetFactionEqual(oUwizhe,oPC))
	{
    	AssignCommand(oTarg, ActionForceMoveToObject(oUwizhe, 1));
		DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
		SetLocalInt(oUwizhe,"noterunner",TRUE);
	
	}
	else if(GetFactionEqual(oGymethon,oPC))
	{
    	AssignCommand(oTarg, ActionForceMoveToObject(oGymethon, 1));
		DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
		SetLocalInt(oGymethon,"noterunner",TRUE);
	
	}
}

@andgalf

I know you have your solution now, but was curious as to your “ordering” in your solution. Did you do the check the way you did because you wanted each companion listed higher to take the “priority” of being bumped into?

If, however, you only have the possibility of these PCs in your party, and priority was not an issue, then you may be able to simplify your script:

void main()
{
	object oTarg = GetObjectByTag("runner");
	object oPC = GetFirstPC();
	object oWP = GetObjectByTag("runnerwp2");
	
	object oFM = GetFirstFactionMember(oPC, FALSE);
	
	while(oFM != OBJECT_INVALID)
	{
		if(!GetAssociateType(oFM) && oFM != oPC)
		{
			AssignCommand(oTarg, ActionForceMoveToObject(oFM, 1));
			DelayCommand(1.5,AssignCommand(oTarg, ActionForceMoveToObject(oWP, 1)));
			SetLocalInt(oFM,"noterunner",TRUE);
            break;
		}
		
		oFM = GetNextFactionMember(oPC, FALSE);
	}
	
}
2 Likes

Well, I don’t know which companions are in the party, so I made a list so at least one of them is bumped into. My first attempt (the first script in the second post) didn’t guarantee that anyone was actually bumped into, so that’s why I did it the way I did. And no, I didn’t have a priority list over who was to be bumped into, it was just my clunky way of trying to make it work.

Your script at first looked a bit peculiar to me, but I didn’t see the break; so now I see that it works similar to what mine did. It’s quite a bit simpler perhaps, but…well, mine works so… Next time I would have probably used something like what you did here.

What I’ve noticed when testing about 10-20 times is that sometimes the runner stops in front of the companion 'cause he cannot find a way through the whole party towards the waypoint. That may happen since I have no way to know exactly where the party members will be (it’s coinsidental). It will have to do though. Otherwise I will have to make a script where I really place each and every party member at an exact spot and that looks pretty clunky too, so…
It’s the limitations of NWN2, I guess.

1 Like

@andgalf

In theory, you can also check for the closest companion to the runner at time of the call, which would then help alleviate the potential early collision situation. Some sort of GetNearestCreature, etc sort of thing, checking for a valid faction member on find.

Alternatively, you could cheat (but it may look odd if noticed), by setting the collision on the runner to false, and then they may simply run through any obstructions including any other PCs who happen to step in the way.

But, if it works as it stands, take this as just some info that may help in other situations in a future script. :slightly_smiling_face:

1 Like