[SOLVED] Trigger Script

Hi. I have the following problem. I have a party consisting of the main PC and his companion, and I want that companion to start a conversation with the main PC when stepping on the trigger. I set up the trigger on the ground, and attached the following script to it:

#include "nw_i0_generic"

object GetHenchmanByTag(string sTagHen);
int GetHenchValid(string sTagHen);

void main()
{
	object oEnterer = GetEnteringObject();
	int nTriggerDone = GetLocalInt(OBJECT_SELF, "Done");

	if (nTriggerDone < 1)
	{
		if (GetIsPC(oEnterer) && GetHenchValid("DRW_HEN_ELE01"))
		{
			object oIlir = GetHenchmanByTag("DRW_HEN_ELE01");
			object oPC = GetFactionLeader(oEnterer);
			SetLocalInt(OBJECT_SELF, "Done", 1);
			SetCutsceneMode(oPC, TRUE);
			AssignCommand(oPC, ClearAllActions());
			AssignCommand(oPC, SetFacingPoint(GetPosition(oIlir)));
//			ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectCutsceneParalyze(), oPC, 6.f);
			AssignCommand(oIlir, ClearAllActions(TRUE));
			AssignCommand(oIlir, ActionForceMoveToObject(oPC, TRUE, 2.f, 5.f));
			AssignCommand(oIlir, ActionStartConversation(oPC, "elereen_prologue", FALSE, FALSE, FALSE, FALSE));
			AssignCommand(oIlir, ActionDoCommand(AssignCommand(oPC, SetCutsceneMode(oPC, FALSE))));
		}
	}
}

object GetHenchmanByTag(string sTagHen)
{
	object oPC = GetFirstPC();	
	string sTag = GetFirstRosterMember();
	object oHench = GetObjectByTag(sTag);
	while(GetIsObjectValid(oHench))
	{
		if (sTag == sTagHen)
			return oHench;
		sTag = GetNextRosterMember();
		oHench = GetObjectByTag(sTag);
	}
	return OBJECT_INVALID;
}

int GetHenchValid(string sTagHen)
{
	object oPC = GetFirstPC();
	string sTag = GetFirstRosterMember();
	object oHench = GetObjectByTag(sTag);
	while(GetIsObjectValid(oHench))
	{
		if (sTag == sTagHen)
			return TRUE;
		sTag = GetNextRosterMember();
		oHench = GetObjectByTag(sTag);
	}	
	return FALSE;
}

Most of the time that works, but sometimes, when the trigger is placed near to the entrance of an area, the conversation doesn’t run and the game stays locked in the “cutscene mode”. I guess It has something to do with the action queue not being locked or the target being invalid. I tried to add SetCommandable, but it leads to another problem: the conversation doesn’t fire at all, no matter what. Besides, in its current state this script isn’t safe to use since the player can select that companion and interrupt the actions assigned to him via that script. I feel like this code needs to be rewritten entirely, but I don’t know how to do it best. Any help would be appreciated.

P.S. The code isn’t mine, It’s taken from one module.

// 'dialog_tr'
//
/*******************************************************************************
	Andy for Return of the Exile
	22.1.2016

	kevL rework
	28.1.2016

	To be used by a trigger's onEnter event.
	Starts a dialog. See TRIGGER VARIABLES below.

	Works with both SoZ-style.
	Works with an i-point placeable as owner as well.
	
	-------------
	Additions
    -------------
	30.1.2016 : Added a party face target. Note that this adresses the start of the dialogue
            	If the party moves or the NPC moves, we still need to adjust the facing using the ga_party_face
	30.1.2016 : Added a bPCTalk option. For cutscenes that NEED to happen, 
	            but where the NPC only needs to talk to the PC. Instead of the idiotic warp,
				the main PC stays where he is but control switches to him.
	4.2.2016  : Added a method for required creatures in order to start the dialogue. Good to have
	4.2.2016  : Commented out the required creatures because of too much complexity. Perhaps in the future another trigger could
	            be made for it, and all these function in a library.
				If this is ever added, remember to change the 
				if (!GetLocalInt(oTrig, "bCondition")) with
				if (!GetLocalInt(oTrig, "bCondition") && checkRequiredTalkers(GetLocalString(oTrig, "sTalkers")))
	            
	-------------
	TODO
	-------------
	Add a bForce option that makes party/oSpeaker conversable.
	Add an option to use fade-in/fade-outs.
	Add an option to delay the conversation start.
	Possibly implement a heartbeat like CreateIPSpeaker() uses.
	Add an option to disable cutscene bars.
	Add a variable to check for item

	-------------
	TRIGGER VARIABLES
	---------
	bCondition		- Default 0. Set it to 1 so that the trigger won't fire if
					  we go inside. If it needs to fire further in the game,
					  set a local int on the trigger called bCondition = 0.

	sQuestCond		- Default "". The tag of the quest if a QuestCondition is
					  needed.
	iQuestCond		- Default 0. The ID of the quest if a QuestCondition is
					  needed.

	sSpeaker		- Default "". The tag of the creature to be speaking to the
					  party.
	sConvo			- Default "". Name of the conversation file.
	bDestroySelf	- Default 0. Set to 1 if the trigger should get destroyed
					  after it's done its job.

	bParty			- Default 0. Set to 1 if the conversation is an internal
					  party dialog. Checks if the companions needed (speaker) is in party.
	bPCTalk         - Default 0. Set to 1 if Speaker is supposed to talk to the PC only. 
	                  Note: Leave bParty default FALSE.	
	sTalkers        - Default "". A list of tags for required participants excluding PC and Speaker (comma-separated, no spaces).
	                  This is not used at the moment.				  			  

*******************************************************************************/
//__________________
// ** PROTOTYPES ***

// Checks if target can talk.
int getConversable(object oTarget, int bFaction = FALSE);
// Checks if an effect prevents dialog.
int hasEffectSpeechless(object oTarget);
// Makes party face the speaker
void PartyFace(object oFacer, object oFaced);
// Checks if required talkers are present and conversable.
//int checkRequiredTalkers(string sTalkers);

//____________
// ** MAIN ***
void main()
{
	object oTrig = OBJECT_SELF;

	if (!GetLocalInt(oTrig, "bCondition"))
	{
		object oPlayer = GetEnteringObject();
		if (GetIsPC(oPlayer) && getConversable(oPlayer, TRUE))
		{
			string sQuestCond = GetLocalString(oTrig, "sQuestCond");
			if (sQuestCond == ""
				|| GetJournalEntry(sQuestCond, GetOwnedCharacter(oPlayer)) == GetLocalInt(oTrig, "iQuestCond"))
			{
				object oSpeaker = GetNearestObjectByTag(GetLocalString(oTrig, "sSpeaker"));
				if (GetIsObjectValid(oSpeaker))
				{
					int bParty = GetLocalInt(oTrig, "bParty");
					if (bParty || getConversable(oSpeaker))
					{
						if (bParty || GetLocalInt(oTrig, "bPCTalk"))
							oPlayer = SetOwnersControlledCompanion(oPlayer);

						if (!bParty || GetFactionEqual(oPlayer, oSpeaker))
						{
							AssignCommand(oSpeaker, ClearAllActions());
							AssignCommand(oPlayer, ClearAllActions());
							
                            PartyFace(oPlayer, oSpeaker);
							DelayCommand(0.25f, AssignCommand(oSpeaker,
									ActionStartConversation(
											oPlayer,
											GetLocalString(oTrig, "sConvo"),
											FALSE, FALSE, TRUE, FALSE)));

							if (GetLocalInt(oTrig, "bDestroySelf"))
								DelayCommand(3.0f, DestroyObject(oTrig));
						}
					}
				}
			}
		}
	}
}



//_________________
// ** FUNCTIONS ***

// Set the facing of the party to the speaker.
// oFacer - the PC or companion
// oFaced - the speaker
// It happens by default. No need for any trigger variable here.
void PartyFace(object oFacer, object oFaced)
{
	object oFM = oFacer;
	vector vTarget = GetPosition(oFaced);
	
	oFacer = GetFirstFactionMember(oFM, FALSE);
	while(GetIsObjectValid(oFacer))
	{
		AssignCommand(oFacer, ActionDoCommand(SetFacingPoint(vTarget, FALSE)));
		AssignCommand(oFacer, ActionWait(0.5f));
		oFacer = GetNextFactionMember(oFM, FALSE);
	}
}
// Checks if target can talk.
// oTarget	- creature to check
// bFaction	- true to check target's faction (default FALSE)
int getConversable(object oTarget, int bFaction = FALSE)
{
	if (bFaction)
	{
		object oFaction = GetFirstFactionMember(oTarget, FALSE);
		while (GetIsObjectValid(oFaction))
		{
			if (GetIsDead(oFaction)
				|| GetIsInCombat(oFaction)
				|| IsInConversation(oFaction)
				|| hasEffectSpeechless(oFaction))
			{
				return FALSE;
			}
			oFaction = GetNextFactionMember(oTarget, FALSE);
		}
	}
	else if (GetIsDead(oTarget)
		|| GetIsInCombat(oTarget)
		|| IsInConversation(oTarget)
		|| hasEffectSpeechless(oTarget))
	{
		return FALSE;
	}

	return TRUE;
}

// Checks if required talkers are present and conversable.
// sTalkers - a comma-separated list of creature-tags (spaces *not* allowed)
/*int checkRequiredTalkers(string sTalkers)
{
    string sTalker;
    object oTalker;

    int iSeparator = 0;
    while (GetStringLength(sTalkers) > 0 && iSeparator != -1)
    {
        iSeparator = FindSubString(sTalkers, ",");
        if (iSeparator == -1)
            sTalker = sTalkers;
        else
            sTalker = GetStringLeft(sTalkers, iSeparator);

        oTalker = GetNearestObjectByTag(sTalker);
        if (!GetIsObjectValid(oTalker) || !getConversable(oTalker))
            return FALSE;

        sTalkers = GetStringRight(sTalkers, GetStringLength(sTalkers) - iSeparator - 1);
    }

    return TRUE;
}*/
// Checks if an effect prevents dialog.
// oTarget - creature to check
int hasEffectSpeechless(object oTarget)
{
	int iEffect;

	effect eEffect = GetFirstEffect(oTarget);
	while (GetIsEffectValid(eEffect))
	{
		iEffect = GetEffectType(eEffect);
		if (   iEffect == EFFECT_TYPE_CONFUSED
			|| iEffect == EFFECT_TYPE_CUTSCENE_PARALYZE
			|| iEffect == EFFECT_TYPE_CUTSCENEGHOST
//			|| iEffect == EFFECT_TYPE_CUTSCENEIMMOBILIZE
			|| iEffect == EFFECT_TYPE_DAZED
			|| iEffect == EFFECT_TYPE_DEAF
			|| iEffect == EFFECT_TYPE_DOMINATED
//			|| iEffect == EFFECT_TYPE_ENTANGLE
//			|| iEffect == EFFECT_TYPE_FRIGHTENED
//			|| iEffect == EFFECT_TYPE_GREATERINVISIBILITY
			|| iEffect == EFFECT_TYPE_INSANE
//			|| iEffect == EFFECT_TYPE_INVISIBILITY
//			|| iEffect == EFFECT_TYPE_JARRING
			|| iEffect == EFFECT_TYPE_MESMERIZE
			|| iEffect == EFFECT_TYPE_PARALYZE
			|| iEffect == EFFECT_TYPE_PETRIFY
//			|| iEffect == EFFECT_TYPE_POLYMORPH
			|| iEffect == EFFECT_TYPE_SILENCE
			|| iEffect == EFFECT_TYPE_SLEEP
			|| iEffect == EFFECT_TYPE_STUNNED)
//			|| iEffect == EFFECT_TYPE_TURNED
		{
			return TRUE;
		}
		eEffect = GetNextEffect(oTarget);
	}

	return FALSE;
}

Try this. Works for me. I wrote it together with KevL, and I’m pretty certain he doesn’t mind sharing, as I don’t either.

edit, sorry about the format. Don’t know how to post code on the new forums.
edit, fixed the format :slight_smile:

1 Like

maybe that’s more complicated than what Aqvilinus needs …

here’s a little thing (untested) that disregards some safeties

// 'tr_convo'

const string sROSTER_ILIR = "DRW_HEN_ELE01"; // note: this needs to be the *roster* string-id

//
void main()
{
    if (!GetLocalInt(OBJECT_SELF, "Done"))
    {
        object oEnter = GetEnteringObject();
        if (oEnter == GetFactionLeader(oEnter)) // the party-leader is the entering object (and is controlled) - if player is controlling a companion that's okay
        {
            object oIlir = GetObjectFromRosterName(sROSTER_ILIR);
            if (GetIsObjectValid(oIlir) && GetFactionEqual(oIlir, oEnter)) // Ilir is valid and in pc-party.
            {
                SetLocalInt(OBJECT_SELF, "Done", TRUE);

                oEnter = SetOwnersControlledCompanion(oEnter); // shunt oEnter into his/her OwnedPC.

                SetCutsceneMode(oEnter); // this needs to be cleared by the dialog's End/Abort dialog handlers.

                AssignCommand(oEnter, ClearAllActions());

                AssignCommand(oIlir, ClearAllActions());
                AssignCommand(oIlir, ActionForceMoveToObject(oEnter, TRUE, 2.f, 5.f));
                AssignCommand(oIlir, ActionStartConversation(oEnter, "elereen_prologue", FALSE, FALSE, FALSE, FALSE));
            }
        }
    }
}

perhaps Aqvi can riff it between the two.

2 Likes

I know that I don’t have as much knowledge as all you guys, but just for the record, whenever a trigger doesn’t work for me, when it comes to starting conversations (and it happens sometimes and is often quite illogical) I always use the CreateIPSpeaker() script I got from andysks on the old forum. It always works, at least for me, and I have even been able to stop fights at three times in my module and got a conversation running without any problems:

#include "ginc_ipspeaker"
 
void main()
{
  object oPC = GetFirstPC(FALSE);
 
  CreateIPSpeaker("tag_of_speaker", "name_of_convo", GetLocation(GetFirstPC()), 1.0f);
}

Of course you need to add the ActionForceMoveToObject thing…

I also use this to keep my companions from fighting if they were in a fight (which doesn’t seem to be an issue in your script but anyway):

#include "x0_i0_assoc" // SetAssociateState()

SetAssociateState(NW_ASC_MODE_STAND_GROUND,TRUE,GetObjectByTag("tag_of_companion"));
2 Likes

Thank you all for your replies! I found them very useful.

I’ll test first KevL’s script and then probably add to it some safety checks from andysks’ variant.

Glad to hear it works :). The problem is, the ip speaker will switch to the PC by default and I couldn’t work around it. I think we managed to solve this with KevL, but only after we had the above script so I don’t use it. But if Aqvilinus wants, then yes, the small script andgalf posted should work fine and without any problems.

@Aqvilinus

Am curious what you found works. Could you post script pls?

Triggers and spawn/entering locations can be tricky. If a PC (or other moving object) enters/appears inside a trigger, I have found they need to move (even just a tiny movement) to be considered inside the trigger by the engine. At the end of the cutscene, see if you can add a slight amount of actionable movement to the PC, even if it is 0.1. This should clear up this issue.

Ah, sorry for the late reply, was quite busy these days.

@kevL_s
Your script seems to work OK, but not as good as was expected. I thought ‘oEnter == GetFactionLeader(oEnter)’ would return TRUE only when the main character stepped on the trigger. At the moment it looks a bit weird when the companion triggers that script and the game suddenly switch control to the main character that can be quite far away from the trigger.

So I was going to use GetDistanceBetween function to see who is closer to the trigger and then switch control based on this info (if that possible).

P.S. I’ll also try to tinker with your c-info script. Maybe the problem is that companion is a Campaign NPC roster member…

returns true iff the character that the player who owns the current faction-leader is controlling steps into the trigger … something like that

uhh try

if (GetIsOwnedByPlayer(oEnter))

or

if (GetIsPC(oEnter))

 
But ultimately it depends on your ‘vision’ of what should happen,

Andy and i sussed this stuff over for days …

and, like Mannast suggests, try not to plonk a character into the trigger – make/let them walk into it

Its not a matter of plonking inside the trigger or not - they can be inside. They just need to move a little once inside it.

Give this a shot:

#include "ginc_cutscene"

void main()
{
	object oPC = GetEnteringObject();
	object oMain = GetFirstPC();
	
	if (GetFactionEqual(oMain, oPC)	// must be party member
	&& !GetAssociateType(oPC) 		// must not be henchman, familiar, summon, animal, or dominated
	&& !GetLocalInt(OBJECT_SELF, "DoOnce"))
	{
		SetLocalInt(OBJECT_SELF, "DoOnce", 1);
		ClearPartyActions(oPC);
		
		// if the companion triggers event, the main PC will run to the companion and fire dialog
		if (oMain != oPC) AssignCommand(oMain, ActionStartConversation(oPC, "dlg_file"));
		
		// if the main PC triggers event, the companion will run to the main PC and fire dialog						
		else AssignCommand(GetObjectByTag("companion_tag"), ActionStartConversation(oMain, "dlg_file"));
	}
}
2 Likes

Thanks, @Travus, It works the way I wanted it.

So now my scripts look this way:

// 'tr_ilir_startconv'
//
/*******************************************************************************/

const string sILIR_TAG = "DRW_HEN_ELE01"; //the same as the roster name

/*******************************************************************************/

#include "ginc_cutscene"

//__________________
// ** PROTOTYPES ***

// Checks if target can talk.
int getConversable(object oTarget, int bFaction = FALSE);
// Checks if an effect prevents dialog.
int hasEffectSpeechless(object oTarget);
// Makes party face the speaker
void PartyFace(object oFacer, object oFaced);
//The wrapper for SetOwnersControlledCompanion() function.
void SetOwnersControlledCompanionWrapper(object oPC);

//____________
// ** MAIN ***
void main()
{
	object oTrigger = OBJECT_SELF;

	if (!GetLocalInt(oTrigger, "Done"))
	{
		object oPC = GetEnteringObject();
		object oMain = GetFirstPC();
		object oIlir = GetObjectFromRosterName(sILIR_TAG);

		if (GetIsObjectValid(oIlir)
		&&  GetFactionEqual(oMain, oIlir)
		&&  GetIsPC(oPC)
		&&  getConversable(oPC, TRUE))
		{
			SetLocalInt(oTrigger, "Done", TRUE);
			SetLocalInt(oIlir, "NodeIndex", GetLocalInt(oTrigger, "NodeIndex"));

			ClearPartyActions(oPC);
			SetCutsceneMode(oPC, TRUE);
			effect eParalyze = EffectCutsceneParalyze();
			ApplyEffectToObject(DURATION_TYPE_PERMANENT, eParalyze, oPC);

			if (oPC == oIlir)
			{
				PartyFace(oMain, oIlir);
				AssignCommand(oIlir, ActionDoCommand(SetFacingPoint(GetPosition(oMain), FALSE)));
				AssignCommand(oIlir, ActionWait(0.5f));
				AssignCommand(oMain, ActionForceMoveToObject(oIlir, TRUE, 2.0f, 6.0f));
				AssignCommand(oMain, ActionDoCommand(SetOwnersControlledCompanionWrapper(oIlir)));
				AssignCommand(oMain, ActionStartConversation(oIlir, "elereen_prologue", FALSE, FALSE));
			}
			else if(oPC == oMain)
			{
				PartyFace(oIlir, oMain);
				AssignCommand(oMain, ActionDoCommand(SetFacingPoint(GetPosition(oIlir), FALSE)));
				AssignCommand(oMain, ActionWait(0.5f));
				AssignCommand(oIlir, ActionStartConversation(oMain, "elereen_prologue", FALSE, FALSE));
			}
			else
			{
				AssignCommand(oPC, ActionDoCommand(SetFacingPoint(GetPosition(oMain), FALSE)));
				AssignCommand(oMain, ActionForceMoveToObject(oPC, TRUE, 2.0f, 6.0f));
				AssignCommand(oIlir, ActionForceMoveToObject(oPC, TRUE, 2.0f, 6.0f));
				AssignCommand(oMain, ActionDoCommand(AssignCommand(oIlir, ActionDoCommand(SetOwnersControlledCompanionWrapper(oPC)))));
				AssignCommand(oMain, ActionDoCommand(AssignCommand(oIlir, ActionStartConversation(oMain, "elereen_prologue", FALSE, FALSE))));
			}
		}
	}
}

//_________________
// ** FUNCTIONS ***

//The wrapper for SetOwnersControlledCompanion() function.
void SetOwnersControlledCompanionWrapper(object oPC)
{
	oPC = SetOwnersControlledCompanion(oPC);
}


// Set the facing of the party to the speaker.
// oFacer - the PC or companion
// oFaced - the speaker
// It happens by default. No need for any trigger variable here.
void PartyFace(object oFacer, object oFaced)
{
	object oFM = oFacer;
	vector vTarget = GetPosition(oFaced);

	oFacer = GetFirstFactionMember(oFM, FALSE);
	while(GetIsObjectValid(oFacer))
	{
		AssignCommand(oFacer, ActionDoCommand(SetFacingPoint(vTarget, FALSE)));
		AssignCommand(oFacer, ActionWait(0.5f));
		oFacer = GetNextFactionMember(oFM, FALSE);
	}
}

// Checks if target can talk.
// oTarget	- creature to check
// bFaction	- true to check target's faction (default FALSE)
int getConversable(object oTarget, int bFaction = FALSE)
{
	if (bFaction)
	{
		object oFaction = GetFirstFactionMember(oTarget, FALSE);
		while (GetIsObjectValid(oFaction))
		{
			if (GetIsDead(oFaction)
				|| GetIsInCombat(oFaction)
				|| IsInConversation(oFaction)
				|| hasEffectSpeechless(oFaction))
			{
				return FALSE;
			}
			oFaction = GetNextFactionMember(oTarget, FALSE);
		}
	}
	else if (GetIsDead(oTarget)
		|| GetIsInCombat(oTarget)
		|| IsInConversation(oTarget)
		|| hasEffectSpeechless(oTarget))
	{
		return FALSE;
	}

	return TRUE;
}

// Checks if an effect prevents dialog.
// oTarget - creature to check
int hasEffectSpeechless(object oTarget)
{
	int iEffect;

	effect eEffect = GetFirstEffect(oTarget);
	while (GetIsEffectValid(eEffect))
	{
		iEffect = GetEffectType(eEffect);
		if (   iEffect == EFFECT_TYPE_CONFUSED
			|| iEffect == EFFECT_TYPE_CUTSCENE_PARALYZE
			|| iEffect == EFFECT_TYPE_CUTSCENEGHOST
//			|| iEffect == EFFECT_TYPE_CUTSCENEIMMOBILIZE
			|| iEffect == EFFECT_TYPE_DAZED
			|| iEffect == EFFECT_TYPE_DEAF
			|| iEffect == EFFECT_TYPE_DOMINATED
//			|| iEffect == EFFECT_TYPE_ENTANGLE
//			|| iEffect == EFFECT_TYPE_FRIGHTENED
//			|| iEffect == EFFECT_TYPE_GREATERINVISIBILITY
			|| iEffect == EFFECT_TYPE_INSANE
//			|| iEffect == EFFECT_TYPE_INVISIBILITY
//			|| iEffect == EFFECT_TYPE_JARRING
			|| iEffect == EFFECT_TYPE_MESMERIZE
			|| iEffect == EFFECT_TYPE_PARALYZE
			|| iEffect == EFFECT_TYPE_PETRIFY
//			|| iEffect == EFFECT_TYPE_POLYMORPH
			|| iEffect == EFFECT_TYPE_SILENCE
			|| iEffect == EFFECT_TYPE_SLEEP
			|| iEffect == EFFECT_TYPE_STUNNED)
//			|| iEffect == EFFECT_TYPE_TURNED
		{
			return TRUE;
		}
		eEffect = GetNextEffect(oTarget);
	}

	return FALSE;
}

The dialog’s End/Abort event script:

#include "ginc_cutscene"

void main()
{
	SetLocalInt(OBJECT_SELF, "NodeIndex", 0);

	object oPC = GetFirstPC();
	object oFactionMember = GetFirstFactionMember(oPC, FALSE);
	effect eEffect;

	while(GetIsObjectValid(oFactionMember))
	{
		RemoveEffectCutsceneParalyze(oFactionMember);
		SetCutsceneMode(oFactionMember, FALSE);
		oFactionMember = GetNextFactionMember(oPC, FALSE);
	}
}

I’ll test this out more carefully over time, and see if there are any problems left.

(SOLVED) The one problem that I noticed is that if the companion triggers event and the main PC run to him, the main PC’s and companion’s positions are swaped during the conversation (some engine related problems). But it’s not critical and I can live with that.

To prevent swapping, one needs to switch control to the main PC before starting the conversation.

I did this that way:

    // if the companion triggers event, the main PC will run to the companion and fire dialog
	if (oMain != oPC)
	{
		AssignCommand(oMain, ActionForceMoveToObject(oIlir, TRUE, 2.0f, 6.0f));
		AssignCommand(oMain, ActionDoCommand(SetOwnersControlledCompanionWrapper(oPC)));
		AssignCommand(oMain, ActionDoCommand(AssignCommand(oIlir, ActionStartConversation(oMain, "elereen_prologue"))));
	}
	// if the main PC triggers event, the companion will run to the main PC and fire dialog
	else
		AssignCommand(oIlir, ActionStartConversation(oMain, "elereen_prologue"));

where

void SetOwnersControlledCompanionWrapper(object oPC)
{
    oPC = SetOwnersControlledCompanion(oPC);
}

Maybe there’s a more simple solution, I don’t know.

Now, since SetCommandable doesn’t work in that case and since it’s pretty dangerous to use this script in its current state (if the player interrupts assigned actions, the conversation will never start, and the paralyze effect and cutscene mode will never be removed), I’d like to create some ‘safety mechanism’. The first my thought was to use the trigger heartbeat script to check if the conversation started or even a separate user defined event, but maybe there’s a more ‘clever’ and simple way to work out this?

Well, I managed to do something like that. Maybe not very beautiful, but at least it works :slight_smile:

// 'tr_party_convo'
//
/*******************************************************************************/
#include "ginc_cutscene"
#include "x0_i0_assoc"

//__________________
// ** PROTOTYPES ***

// Checks if target can talk.
int getConversable(object oTarget, int bFaction = FALSE);
// Checks if an effect prevents dialog.
int hasEffectSpeechless(object oTarget);
// Makes party face the speaker and the speaker face oFacer
void PartyFace(object oListener, object oSpeaker);
// The wrapper for SetOwnersControlledCompanion() function
void SetOwnersControlledCompanionWrapper(object oCurrentCreature, object oTargetCreature=OBJECT_INVALID);
// Makes the listener move to the speaker and start a coversation
void StartConversation(object oListener, object oSpeaker, float fTime);

object oTrigger = OBJECT_SELF;

/*******************************************************************************/

//____________
// ** MAIN ***
void main()
{
	if (!GetLocalInt(oTrigger, "bCondition") && !GetLocalInt(oTrigger, "bDone"))
	{
		object oPC = GetEnteringObject();
		if (GetIsPC(oPC) && getConversable(oPC, TRUE))
		{
			object oSpeaker = GetNearestObjectByTag(GetLocalString(oTrigger, "sSpeaker"));
			if (GetIsObjectValid(oSpeaker) && GetFactionEqual(oPC, oSpeaker))
			{
				SetLocalInt(oTrigger, "bDone", TRUE);
				SetLocalInt(oSpeaker, "NodeIndex", GetLocalInt(oTrigger, "NodeIndex"));
				SetLocalInt(oSpeaker, "bConvoPending", 1);

				ClearPartyActions(oPC);
				SetCutsceneMode(oPC, TRUE);
				effect eParalyze = EffectCutsceneParalyze();
				ApplyEffectToObject(DURATION_TYPE_PERMANENT, eParalyze, oPC);

				SetLocalString(oTrigger, "sActivatedBy", GetTag(oPC));
			
				object oMain = GetFirstPC();

				if(GetAssociateType(oPC) != ASSOCIATE_TYPE_NONE && !GetAssociateState(NW_ASC_MODE_STAND_GROUND, oPC))
				{
					SetLocalInt(oPC, "NeedStandGround", 1); //Otherwise it will run to the master, even if paralyzed
					SetAssociateState(NW_ASC_MODE_STAND_GROUND, TRUE, oPC);
				}
			
				if(GetAssociateState(NW_ASC_MODE_PUPPET, oMain))
				{
					SetLocalInt(oMain, "NeedHeartbeat", 1); 
					SetEventHandler(oMain, CREATURE_SCRIPT_ON_HEARTBEAT, ""); // To prevent problems with PuppetMode and AI
				}
				
				if(GetAssociateState(NW_ASC_MODE_PUPPET, oSpeaker))
				{
					SetLocalInt(oSpeaker, "NeedHeartbeat", 1);
					SetEventHandler(oSpeaker, CREATURE_SCRIPT_ON_HEARTBEAT, "");
				}				
																		
				PartyFace(oMain, oSpeaker);
				DelayCommand(0.25f, StartConversation(oMain, oSpeaker, 0.0f));
			}
		}
	}
}

//_________________
// ** FUNCTIONS ***

// The wrapper for SetOwnersControlledCompanion() function.
//
// This will find the player controlling oCurrentCreature, and set
// their currently controlled companion to oTargetCreature. If it
// can’t find oTargetCreature, then the player is reassigned to
// whoever their original, owned character is.
void SetOwnersControlledCompanionWrapper(object oCurrentCreature, object oTargetCreature=OBJECT_INVALID)
{
	oCurrentCreature = SetOwnersControlledCompanion(oCurrentCreature, oTargetCreature);
}

// Makes the listener (oListener) and the speaker (oSpeaker) move to
// each other and start the coversation.
void StartConversation(object oListener, object oSpeaker, float fTime)
{
	string sTag     = GetLocalString(oTrigger, "sActivatedBy");
	string sConvo   = GetLocalString(oTrigger, "sConvo");
	object oPC = GetObjectByTag(sTag);

	if (!GetLocalInt(oSpeaker, "bConvoPending"))
		return;
	
	ClearPartyActions(oListener);

	if (fTime >= 10.0f)
		AssignCommand(oListener, ActionStartConversation(oSpeaker, sConvo, FALSE, FALSE, TRUE));
	
	
	if(sTag == "")
	{
		AssignCommand(oSpeaker, ActionStartConversation(oListener, sConvo, FALSE, FALSE));
	}
	else if (sTag == GetLocalString(oTrigger, "sSpeaker"))
	{
		AssignCommand(oListener, ActionForceMoveToObject(oSpeaker, TRUE, 0.5f));
		AssignCommand(oListener, ActionDoCommand(SetOwnersControlledCompanionWrapper(oSpeaker)));
		AssignCommand(oListener, ActionStartConversation(oSpeaker, sConvo, FALSE, FALSE));
	}
	else
	{
		if((GetDistanceBetween(oListener, oPC) < 3.0f)
		&& (GetDistanceBetween(oSpeaker, oPC) < 3.0f))
		{
			oListener = SetOwnersControlledCompanion(oPC);
			AssignCommand(oSpeaker, ActionStartConversation(oListener, sConvo, FALSE, FALSE));
		}
		else
		{
			AssignCommand(oListener, ActionForceMoveToObject(oPC, TRUE));
			AssignCommand(oSpeaker,  ActionForceMoveToObject(oPC, TRUE));
		}
	}

	if (GetLocalInt(oSpeaker, "bConvoPending"))
		DelayCommand(2.0f, StartConversation(oListener, oSpeaker, fTime + 2.0f));
}

// Set the facing of the party to the speaker.
void PartyFace(object oListener, object oSpeaker)
{
	string sTag = GetLocalString(oTrigger, "sActivatedBy");
	object oFacer, oFaced;
	object oPC = GetObjectByTag(sTag);
		
	if(sTag == "" || sTag == GetLocalString(oTrigger, "sSpeaker"))
	{
		if (sTag == "")
		{
			oFacer = oSpeaker;
			oFaced = oListener;
		}
		else
		{
			oFacer = oListener;
			oFaced = oSpeaker;
		}
		
		object oFM = oFacer;
		vector vTarget = GetPosition(oFaced);

		AssignCommand(oFaced, ActionDoCommand(SetFacingPoint(GetPosition(oFacer), FALSE)));
		AssignCommand(oFaced, ActionWait(0.5f));

		oFacer = GetFirstFactionMember(oFM, FALSE);
		while(GetIsObjectValid(oFacer))
		{
			AssignCommand(oFacer, ActionDoCommand(SetFacingPoint(vTarget, FALSE)));
			AssignCommand(oFacer, ActionWait(0.5f));
			oFacer = GetNextFactionMember(oFM, FALSE);
		}
	}
	else
	{
		AssignCommand(oPC, ActionDoCommand(SetFacingPoint(GetPosition(oSpeaker), FALSE)));
	}
}

// Checks if target can talk.
// oTarget	- creature to check
// bFaction	- true to check target's faction (default FALSE)
int getConversable(object oTarget, int bFaction = FALSE)
{
	if (bFaction)
	{
		object oFaction = GetFirstFactionMember(oTarget, FALSE);
		while (GetIsObjectValid(oFaction))
		{
			if (GetIsDead(oFaction)
				|| GetIsInCombat(oFaction)
				|| IsInConversation(oFaction)
				|| hasEffectSpeechless(oFaction))
			{
				return FALSE;
			}
			oFaction = GetNextFactionMember(oTarget, FALSE);
		}
	}
	else if (GetIsDead(oTarget)
		|| GetIsInCombat(oTarget)
		|| IsInConversation(oTarget)
		|| hasEffectSpeechless(oTarget))
	{
		return FALSE;
	}

	return TRUE;
}

// Checks if an effect prevents dialog.
// oTarget - creature to check
int hasEffectSpeechless(object oTarget)
{
	int iEffect;

	effect eEffect = GetFirstEffect(oTarget);
	while (GetIsEffectValid(eEffect))
	{
		iEffect = GetEffectType(eEffect);
		if (   iEffect == EFFECT_TYPE_CONFUSED
			|| iEffect == EFFECT_TYPE_CUTSCENE_PARALYZE
			|| iEffect == EFFECT_TYPE_CUTSCENEGHOST
//			|| iEffect == EFFECT_TYPE_CUTSCENEIMMOBILIZE
			|| iEffect == EFFECT_TYPE_DAZED
			|| iEffect == EFFECT_TYPE_DEAF
			|| iEffect == EFFECT_TYPE_DOMINATED
//			|| iEffect == EFFECT_TYPE_ENTANGLE
//			|| iEffect == EFFECT_TYPE_FRIGHTENED
//			|| iEffect == EFFECT_TYPE_GREATERINVISIBILITY
			|| iEffect == EFFECT_TYPE_INSANE
//			|| iEffect == EFFECT_TYPE_INVISIBILITY
//			|| iEffect == EFFECT_TYPE_JARRING
			|| iEffect == EFFECT_TYPE_MESMERIZE
			|| iEffect == EFFECT_TYPE_PARALYZE
			|| iEffect == EFFECT_TYPE_PETRIFY
//			|| iEffect == EFFECT_TYPE_POLYMORPH
			|| iEffect == EFFECT_TYPE_SILENCE
			|| iEffect == EFFECT_TYPE_SLEEP
			|| iEffect == EFFECT_TYPE_STUNNED)
//			|| iEffect == EFFECT_TYPE_TURNED
		{
			return TRUE;
		}
		eEffect = GetNextEffect(oTarget);
	}

	return FALSE;
}

Dialog’s Abort/End event script:

#include "ginc_cutscene"

void main()
{
	SetLocalInt(OBJECT_SELF, "NodeIndex", 0);

	object oPC = GetFirstPC();
	object oFactionMember = GetFirstFactionMember(oPC, FALSE);
	effect eEffect;

	while(GetIsObjectValid(oFactionMember))
	{
		RemoveEffectCutsceneParalyze(oFactionMember);
		SetCutsceneMode(oFactionMember, FALSE);
		
		if(GetLocalInt(oFactionMember, "NeedHeartbeat"))
			SetEventHandler(oFactionMember, CREATURE_SCRIPT_ON_HEARTBEAT, "gb_comp_heart");
			
		if(GetLocalInt(oFactionMember, "NeedStandGround"))
			SetAssociateState(NW_ASC_MODE_STAND_GROUND, FALSE, oFactionMember);
		
		oFactionMember = GetNextFactionMember(oPC, FALSE);
	}
}

happy?

Yep =) Thanks once again to all of you for the assistance.

////////////
upd: The code has been improved a bit, added more safety checks.
///////////

I have one last question: is there a way to center the camera on the selected PC using scripts?

the Exploration mode camera can be scripted … but i don’t believe there’s any g/setter for camera mode so you can’t be sure that player is using that mode. (or reset it after mucking about w/ it)

/shrug

dialog-nodes have camera settings ofc.

iirc you can turn on the Overland Map mode, which all that actually does it lock the camera as defined for OM mode in the area properties. If you can set your camera settings for that to what you want, you’ve functionally locked the player camera into that view until you turn it off.