Aller au contenu

Photo

A bullet proof IP conversation trigger


  • Veuillez vous connecter pour répondre
23 réponses à ce sujet

#1
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
Assuming IP_Festhall is an IPoint speaker creature, how can it be forced to begin the conversation?
Should I swap oPC and oIP in the last line? I would like an absolute failsafe conversation starter for such
plot conversations.




object oPC = GetFirstPC();
object oIP = GetObjectByTag("IP_festhall");
AssignCommand(oPC, ClearAllActions(TRUE));
AssignCommand(oPC, ActionStartConversation(oIP, "festhall", FALSE, FALSE, TRUE, TRUE) );

Modifié par Eguintir Eligard, 23 mai 2013 - 08:27 .


#2
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
Hm even a code that worked at all would be nice, even if we're not sure its bullet proof.
This one doesn't work either:


object oPC = GetFirstPC();
location locIP_WP = GetLocation( GetObjectByTag("WP_ip_festhall") );


AssignCommand(oPC, ClearAllActions(TRUE));
CreateIPSpeaker("IP_festhall", "festhall", locIP_WP, 0.5f);

#3
kevL

kevL
  • Members
  • 4 061 messages
what object are you running the script from?

ie, what is Object_Self, a trigger?

#4
Lugaid of the Red Stripes

Lugaid of the Red Stripes
  • Members
  • 955 messages
I've started doing a backup by putting a second call to start conversation on a delay command. This is especially useful if the convo is supposed to start right as combat end, sometimes you need another couple of seconds for the timers to cooldown and the extra attacks to play out.

#5
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
this script will fire on an on client enter in an area called festhall. OBJECT_SELF = area

As I recall the original games bullet proof method involved creating an ip speaker and
with its creation it automatically forces a conversation. But I cant find any samples and it appears my attempt
to create one is failing, no talk even starts.

Modifié par Eguintir Eligard, 24 mai 2013 - 12:01 .


#6
kevL

kevL
  • Members
  • 4 061 messages
I've just been looking at CreateIPSpeaker() and, although involved, it seems the basic premise is to attempt the dialog twice: once right away and again after a few second delay.

My own thought about starting a dialog from onClientEnter is... i wouldn't do it. At least not without a delay of at least 5 seconds. Personally if things had to go that route i'd use the area heartbeat, and try to keep connecting ( use the conversation to set a local_int that shorts out the HB later ).

Anyway, this is untested but pretty straightforward:


// 'ee_startdialog'


void ee_StartDialog(object oEnter)
{
  object oDebug = GetFirstPC(FALSE);
  DelayCommand(5.f, SendMessageToPC(oDebug, "run ( ee_startdialog ) " + GetName(OBJECT_SELF)));
  // <- delayed +5 sec.


  // note. Tags are case-sensitive; double check the tag of your IPSpeaker placeable:
  object oIP = GetObjectByTag("IP_festhall");

  // -> PC is controlling OwnedPC.
  oEnter = SetOwnersControlledCompanion(oEnter);

  AssignCommand(oIP, ActionStartConversation(oEnter, "festhall", FALSE, FALSE, TRUE, TRUE));
}



void main()
{
  object oEnter = GetEnteringObject();

  DelayCommand(5.f, ee_StartDialog(oEnter));
}


#7
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
So you think an area Hb that keeps running the dialog until it detects a boolean that the dialogue has begun is the best way (aka the infinite loop until sucess)? I did this for Islander in spots but I thought it was clunky and I wanted to run a little cleaner this time. But it certainly sounds superior to an IP speaker that only tries twice. So I go with the loop then?

I wonder if I could cleanly do this off an IP speaker HB instead. And then make a blueprint with variables to set the name of the conversation when I call the creation. That way it isn't restricted to the area, or shall I say connected. The speaker can be the speaker initiator. But still with the loop as said above. Is that a sound method in theory?

The one thing I would change though is depending on 2 scipts. 1 being the HB script, and the other having to remember to set a variable in the conversation. I like to eliminate dependency when possible. 

What about:
  • The Ip speaker tries to start a conversation.
  • It loops until it detects that itself is in conversation, then stops. This is critical because the IP speaker only has one possible person to speak to and one conversation to engage in. (If I detected the PC in conversation it could screw it up because it may be a different conversation the PC is engaged in [such as with a companion or other npc] ).
What do you think, any holes in that approach?

Modifié par Eguintir Eligard, 24 mai 2013 - 01:35 .


#8
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
Oh and embarrassing admission I guess. I never learned how to turn on debugging messages? How do I do that?

#9
Tchos

Tchos
  • Members
  • 5 054 messages
EE, look in the placeables for Ipoint Speaker. The script is in the heartbeat. It tries to fire twice each heartbeat, and when it succeeds, it destroys itself.

#10
kevL

kevL
  • Members
  • 4 061 messages

Eguintir Eligard wrote...

Oh and embarrassing admission I guess. I never learned how to turn on debugging messages? How do I do that?

you don't have to if you go with SendMessageToPC(GetFirstPC(FALSE), "this is happening now!") <- chat window

The 'other' debug ( eg. PrettyDebug() ) sends messages to a logfile in the NwN2 temp directory...


as to the rest, well... If you want to work through a generic dialog-starter, personally I'd start in 'ginc_ipspeaker', grab CreateIPSpeaker() and its affiliates, rename them rewrite them, tweak them into my own brand new #include file

and go from there. But it too is likely to have limitations, eg the delay I suggested above when first entering areas ( especially when loading from other modules ). but it might work fine; i haven't teased that stuff enough to say for sure,

- except it's not bulletproof. / shrug /


Eguintir Eligard wrote...

So you think an area Hb that keeps running the dialog until it detects a boolean that the dialogue has begun is the best way (aka the infinite loop until sucess)? I did this for Islander in spots but I thought it was clunky and I wanted to run a little cleaner this time. But it certainly sounds superior to an IP speaker that only tries twice. So I go with the loop then?


I think the area_HB is a more stable way to start a dialog than the ClientEnter script, yes. Any clunkiness can be minimized drastically with that simple boolean at the top of a HB script, it seems to me.

About loops: notice that little function "void ee_StartDialog(object oEnter)"? that can easily be looped ... ie, parsed, expanded, and looped.


I wonder if I could cleanly do this off an IP speaker HB instead. And then make a blueprint with variables to set the name of the conversation when I call the creation. That way it isn't restricted to the area, or shall I say connected. The speaker can be the speaker initiator. But still with the loop as said above. Is that a sound method in theory?


tl;dr : what Tchos said.

There are plenty of places in the OC where the Ipoint is down and ready to go. Offhand I'm thinking of the Battle at the Gates. OEI put a lot of effort into Ipoint dialogs, so you probably should hunt through the OC to see what they did -- even better if you can find Ipoint usage in MotB, which feels more solid at starting convo's. ( not sure what the difference if any is tho )

Just remember that the IPSpeaker has to be already placed down for its HB to fire up in the first place. It strikes me as a good idea; make a template with the right scripts already assigned, so that when building you just plonk one down and give it the string_var of whichever dialog file it starts up. But again you're dealing with a heartbeat script (which is basically a loop), and there should be lots of checks (see 'ginc_ipspeaker'), and the more I think about it you might end up with a bunch of IPSpeakers running HB scripts *all the time*

- or instead of placing them when building, create them from triggers/onEnters/etc, but then we're back at CreateIPSpeaker().....


The one thing I would change though is depending on 2 scipts. 1 being the HB script, and the other having to remember to set a variable in the conversation. I like to eliminate dependency when possible.

What about:

  • The Ip speaker tries to start a conversation.
  • It loops until it detects that itself is in conversation, then stops. This is critical because the IP speaker only has one possible person to speak to and one conversation to engage in. (If I detected the PC in conversation it could screw it up because it may be a different conversation the PC is engaged in [such as with a companion or other npc] ).
What do you think, any holes in that approach?

Not if, as you imply, the conversation writes the Stop flag to the IPoint itself that started the conversation, and the heartbeat then checks that IPoint for its flag: limit of one dialog per Ipoint ofc -- without screwing up the simplicity that is.


even with this back&forth about loops, I still think there are reliable ways to start conversations on the first try. I mean, obviously there are. I'm sure it becomes an issue only under special circumstances, like entering an area, or like LotRS said, at the end of combat ( i don't trust that groupondeath startconversation function, and will executeCustomScript onGroupDeath instead, btw )

ps. I'm scratching my head trying to remember where I saw a group of variables, that include all the usual bCutsceneBars etc, but has more options like run to speaker etc ...... right: DoSpeakTrigger() in 'ginc_trigger', which has a few more ideas that may help in bazooka-proofing.

#11
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
 heres script one as I definitely want to be able to create the IP via script (I just dont want to have to remember to set any flags in a following conversation. The IP should know to destroy itself):
[b]void Create_EE_IP(string sConv, location locWhere, int iHB_delays=0)[/b][b]

//** * creates a custom infinite-attempt IP speaker, a failsafe way to execute conversations
[/b][b]// HB_delays = number of heartbeats to wait before trying
[/b][b]{
[/b][b]object objIP = CreateObject(OBJECT_TYPE_PLACEABLE, "ee_ipspeaker", locWhere, FALSE, "ip_" + sConv);
[/b][b]SetLocalString(objIP, "sConv", sConv);
[/b][b]SetLocalInt(objIP, "iHB_delays", iHB_delays);
[/b][b]SetLocalInt(objIP, "bKill", 0);[/b][b]// can refer to autotag named ip_ + sConv  later if you want to destory this IP speaker[/b][b]
[/b][b]}[/b]

And here's the HB script of the object I created:
[b]#include "ginc_debug"
[/b]
[b]void main()[/b][b]

[/b][b]{
[/b][b]object oEnter = GetFirstPC(FALSE); // return currently controlled character, not leader[/b][b]int bIP_conv_fired = FALSE;
[/b][b]int bKill = GetLocalInt(OBJECT_SELF, "bKill");
[/b][b]string sConv = GetLocalString(OBJECT_SELF, "sConv");
[/b][b]//int iHB_delays = GetLocalInt(OBJECT_SELF, "iHB_delays");
[/b][b]int iCount = 0;[/b][b]
[/b]
[b]// fired sucessfully once and ready to be killed/handled or ignored 
[/b][b]if (bKill==TRUE) return; [/b][b]

[/b][b]	[/b][b]bIP_conv_fired = IsInConversation(OBJECT_SELF);[/b][b]

[/b][b]if (bIP_conv_fired) 
[/b][b]	{[/b][b]	
SetLocalInt(OBJECT_SELF, "bKill", 1);[/b][b]	
return; // exit script early if conversation already fired, the job is done, flag for kill[/b][b]	
}[/b][b]
[/b][b]

[/b][b]/*if (iCount < iHB_delays)[/b][b]	
   
{[/b][b]	
SetLocalInt(OBJECT_SELF, "iHB_delays", iHB_delays-1);	
[b]return;[/b][b]	} // reduce delay until ready, if not ready, exit entire script[/b][b]*/[/b][b]

[/b][b]AssignCommand(OBJECT_SELF, ActionStartConversation(oEnter, sConv, FALSE, FALSE, TRUE, TRUE));[/b][b]
[/b][b]}[/b]

Then I called it in my HB area script when the flag is set to fire a big conversation:

location locWP = GetLocation(GetObjectByTag("WP_ip_festhall") );
Create_EE_IP("festhall", locWP, 1);

}

This works... but I am not sure if the IP is considered "In Conversation" so to speak since it's a placeable.... I am trying to see why it wont destroy itself or at least appears not to, have to put in a lot of debugging text now.

Modifié par Eguintir Eligard, 24 mai 2013 - 12:43 .


#12
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
Nope it works. It keeps returning bKill True, so I can destroy it but I see no need. It may accidentally be referred to during the conversation so I dont want the conv to break if it happens to be a node speaker for a bit. Plus the HB script does nothing in terms of resources.


freebe for the readers:

void DBT(string sText, int iVal=-99, float fVal=-99.0)
/* * * really efficient way to enter debug message feedback during code time * * */
/* * * int or float value can be entered or ommitted without using IntoString etc, just type numbers * * */
/* * * to use float simply type -99 as the int value, or juse use 0 and ignore the int value * * */
{
object oPC = GetFirstPC(FALSE);
string sSay = sText;

if (iVal != -99)
	{ 
	sSay = sSay + ": ";
	sSay = sSay + IntToString(iVal); 
	}
	
if (fVal != -99.0)
	{ 
	sSay = sSay + "  : ";
	sSay = sSay + FloatToString(fVal); 
	}

	
SendMessageToPC(oPC, sSay);

}


#13
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
Oh and I did add a range checker for the creation and HB portion of the IP. It defaults to 30.0 and if a value of that or higher is entered, it just auto fires no matter the distance. Otherwise it waits for the distance. Makes a lot more sense when approaching a creature or object rather than a large room full of people as this will initially be used.

#14
kamal_

kamal_
  • Members
  • 5 250 messages
Does this mean another Islander is in the works?

#15
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
eguintir.blogspot.ca/2011/05/1-month-challenge.html

We'll start with this and go from there.

If I can't give Islander 2 The High Seas a proper go, I will at least give players
a small epilogue module to explain the cliffhanger. Far too many people think they know who everyone turned out to be and were wrong.

Modifié par Eguintir Eligard, 24 mai 2013 - 02:50 .


#16
diophant

diophant
  • Members
  • 116 messages
Although the topic is already resolved, I'm adding my two cents. If the conversation start really must be reliable, e.g. because missing the conversation breaks the game, the way to go is using the hb script of some object, like EE did. Using a loop with a DelayCommand (like kevL proposed is not reliable. Delayed commands are not saved in a savegame. If the user saves before the convo (when the delay is running), then reloads after the convo (because he was not happy with his decicions), the convo will not start again.

#17
ColorsFade

ColorsFade
  • Members
  • 1 267 messages

diophant wrote...

Although the topic is already resolved, I'm adding my two cents. If the conversation start really must be reliable, e.g. because missing the conversation breaks the game, the way to go is using the hb script of some object, like EE did. Using a loop with a DelayCommand (like kevL proposed is not reliable. Delayed commands are not saved in a savegame. If the user saves before the convo (when the delay is running), then reloads after the convo (because he was not happy with his decicions), the convo will not start again.


This is incredibly valuable information. Thanks man. 

I am going to copy this to my notes and then take a look at a few of the covnersations I've written that rely on iPointSpeakers and triggers. 

#18
kevL

kevL
  • Members
  • 4 061 messages
dio,

excellent point about the DelayCommand. I think that cinches it in favor of heartbeats.


( note to those who still want a pseudo-heartbeat for whatever: they can be restarted from onEnter or such )

#19
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
As you may have noted, I chose the option of letting the player use heartbeat count if they want a delay, vs a set time. Lucky me.

void Create_EE_IP(string sConv, location locWhere, int iHB_delays=0)

//** * creates a custom infinite-attempt IP speaker, a failsafe way to execute conversations
// HB_delays = number of heartbeats to wait before trying


#20
kevL

kevL
  • Members
  • 4 061 messages
y, i saw that.

with the basics down, Are you going to add things like ClearActions/MakeConversable ?


maybe a check for anyone in the party inCombat, then delay the dialog etc etc.

#21
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
hm... Is it necessary? I used to clearallactions, but I figured as long as I dont set these conversations to once only they will complete despite any interruptions.

Anything else to check for besides combat?

#22
kevL

kevL
  • Members
  • 4 061 messages
I'm just looking, the default IPSpeaker function does this subroutine:


// from 'ginc_ipspeaker'

// Check if oObject is safe to conversate (valid, in area, not in conversation)
// IPS conversation can start despite death or combat state
int GetIsIPSConversible(object oObject, object oIPSpeaker = OBJECT_SELF)
{
  // Verify oObject is valid
  if (!GetIsObjectValid(oObject))
  {
    PrettyError("GetIsIPSConversible: oObject is not valid");
    return FALSE;
  }

  // Verify oObject and oIPSpeaker are in the same area
  if (GetArea(oIPSpeaker) != GetArea(oObject))
  {
    PrettyError("GetIsIPSConversible: " + GetName(oObject) + " is not in IPS' area");
    return FALSE;
  }

  // Verify oObject (or PC party) is not already in a conversation
  if (GetIsPC(oObject) && GetIsPartyInConversation(oObject))
  {
    PrettyError("GetIsIPSConversible: oObject or a party member is already in a conversation");
    return FALSE;
  }
  else if (IsInConversation(oObject))
  {
    PrettyError("GetIsIPSConversible: oObject is in a conversation");
    return FALSE;
  }

  // Solid gold!
  return TRUE;
}

and it checks using paramaters (oPC, oIP) *and* (oIP, oPC) both ways. And what this means -- "IPS conversation can start despite death or combat state" -- is probably a note about function MakeConversable() in 'ginc_cutscene', which is long so i'll paraphrase:

- resurrect PC & companions ...
- make PC ( and optionally all PC faction ) Commandable: run on both PC *and* IP
- ClearAllActions on IP, and ClearPartyActions on PC faction
- check for and remove bad effects (optionally on all PC faction), like EFFECT_TYPE_PETRIFY etc.
- set Associate_State of PC & Companions/Associates to StandGround.
- set all hostile creatues in the area to ScriptHidden until the dialog is over.

and there's this bit in 'ginc_trigger'

if (!bDoNotChangePlayer)
{
  // oEnter is a "created" character not being possessed by anyone.
  if (GetIsOwnedByPlayer(oEnter) && !GetIsPC(oEnter))
  {
    // Warp the PC into his created character and return
    oEnter = SetOwnersControlledCompanion(GetControlledCharacter(oEnter), oEnter);
}
  // oEnter is a companion who is not being controlled, and is blocked from possession
  else if (GetIsCompanionPossessionBlocked(oEnter)
      && !GetIsPC(oEnter)
      && !GetIsOwnedByPlayer(oEnter))
  {
    // Target can talk to anyone, so whoever the faction leader is A-O-K to talk to this guy
    if (GetCanTalkToNonPlayerOwnedCreatures(oTarg))
    {
      object oLeader = GetFactionLeader(oEnter);
      AssignCommand(oLeader, ClearAllActions(TRUE));
      AssignCommand(oLeader, JumpToObject(oEnter));
      oEnter = oLeader;
    }
    // Picky NPC, force possession of an owned character and use them (since it is companion only in this case, any PC will do
    else
    {
      object oLeader = GetFactionLeader(oEnter);
      // Warp the PC into his created character and return
      object oOwned = SetOwnersControlledCompanion(oLeader, GetOwnedCharacter(oLeader));
      AssignCommand(oOwned, ClearAllActions(TRUE));
      AssignCommand(oOwned, JumpToObject(oEnter));
      oEnter = oOwned;
    }
  }
  // oEnter is a companion who is not being controlled, but can be
  else if (!GetIsCompanionPossessionBlocked(oEnter)
      && !GetIsPC(oEnter)
      && !GetIsOwnedByPlayer(oEnter))
  {
    // Force possession of Faction Leader into Companion
    oEnter = SetOwnersControlledCompanion(GetFactionLeader(oEnter), oEnter);
  }
  // All other cases involve oEnter being a PC or companion who is CURRENTLY being possessed
  // by a player. They are fine to pass in, and engine will handle a swap if need be.
  // If oEnter is still not controlled by a player
  if (!GetIsPC(oEnter))
  {
    object oLeader = GetFactionLeader(oEnter);
    AssignCommand(oLeader, ClearAllActions(TRUE));
    AssignCommand(oLeader, JumpToObject(oEnter));
    oEnter = oLeader;
  }
}

- which basically seems to check whether things are setup to talk to Companions and if not then switch to the mainPC

plus a check for startDistance ... crazy huh. Anyways I'm just spraying bullets; they'll probably miss

#23
Eguintir Eligard

Eguintir Eligard
  • Members
  • 1 832 messages
Not sure about all of that.

First of I don't want the conversation ressurecting people. (as an aside I can't believe thats become the new direction of RPGs). And taking away cool effects like petrify? Never. Not in my campaigns anyhow.


However... if for some weird reason the player has a dead party member selected, I would want to switch to the next available one so I'll think about that. Of course that might not even be necessary because the repition takes care of having to deal with any of the above does it not? Try until you succeed, the reasons for failing being immaterial.

#24
kevL

kevL
  • Members
  • 4 061 messages
well perhaps, but it wouldn't surprise me if an NPC can have a conversation with a dead PC.

- instead of removing conditions ( fixing them for the player ), check for them and if True bypass starting the dialog.

eg. If (distance > MaxDialogDistance) return;


etc.

edit: in truth i remember making a short dialog that won't start if there are hostiles within 25m. It's not foolproof (the hostiles are patrolling), but it's not that important a dialog either ..but it's there & I'm glad it is.

- what i find when writing scripts like yours is that it takes time for them to congeal into something truly solid,

Modifié par kevL, 26 mai 2013 - 05:08 .