Jump to content

Photo

IWD engine: dialogue coding


2 replies to this topic

#1 Kulyok

Kulyok
  • Modders
  • 5771 posts
  • Gender:Female
  • Location:Moscow, Russia

Posted 21 March 2008 - 05:12 AM

(I'm mostly accumulating the_bigg's(Avenger's, CamDawg's, devSin's, igi's(IESDP), jcompton's) code here, hoping it may be useful in the future. Note that it may not be as useful for IWD2, because IWD2 has some nifty functions of its own and you'll be better off perusing Domi's code - IWD2 NPC forum is just a few scrolldowns below.)

0) Dialogue with non-joinable NPCs.

Same as BG2, but note that some triggers and actions that work in BG2 do NOT work for IWD. Check IESDP for more details: http://iesdp.gibberlings3.net

1) Banters.

a) There are no "real" timers, only "game" ones. This means you cannot set a RealSetGlobalTimer() for one real hour, but you can set a timer for one game day. Note that from my experience, even the existing timers seem to be quite capricious, especially if you are setting a timer for one(or two) game hours.

b) There is no banter engine, which randomly triggers banters, aka BG2. It means that all banters between party members have to be scripted.

c) This is very important: in IWD, party NPC scripts do NOT work during the time the player issues commands. It means that as long as the player keeps clicking on his party members, ordering them around, they will NOT talk. They will only talk if the party stops.

Why am I telling you this? Well, inconvenience aside, it means that every banter has to be scripted this way(an example between Teri and Nella):

IF
InParty(Myself)
Global("A#NPCNellaTeri1","GLOBAL",1)
InParty("A#Nella")
Detect("A#Nella")
!Detect([ENEMY])
!StateCheck(Myself,CD_STATE_NOTVALID)
!StateCheck("A#Nella",CD_STATE_NOTVALID)
THEN
RESPONSE #100
StartDialogueNoSet("A#Nella")
END

- for as long as the variable A#NellaTeri1 is 1, Teri will keep trying to talk to Nella. Otherwise, she'd try once, and the dialogue may never trigger(or start stacking in her player-initiated dialogue slot, which is just as unpleasant, see "How to make sure your banters run when you want them to")

In the dialogue, use

CHAIN IF ~Global("A#NPCNellaTeri1","GLOBAL",1)~ THEN A#TERI nt1
~(text)~ [audio]
DO ~SetGlobal("A#NPCNellaTeri1","GLOBAL",2)~

... and so on.

But an important point: where do we set this variable in the first place? I have two answers: either, again, via Teri's script(see the link to the tutorial above), or via the area script. The latter will mean that AR2100 will have the following block appended on top:

IF
Global("A#NPCNellaTeri1","GLOBAL",0)
InParty("A#Nella")
InParty("A#Teri")
THEN
RESPONSE #100
SetGlobal("A#NPCNellaTeri1","GLOBAL",1)
Continue() // continue, as not to interfere with OnCreation() block
END

(Why on top? Why EXTEND_TOP, not EXTEND_BOTTOM? Because with EXTEND_BOTTOM, your blocks will never be executed correctly, if another mod adds a block that is always true on top of it, which happened.)

Note carefully: if Player1(PC) is participating in the dialogue, you have to 1) check that Player1 is able to talk; 2) even if it's an NPC-NPC banter, dialogue should be initiated via StartDialogueNoSet(Player1).

If you use StartDialogueNoSet("A#Nella") instead, do not be surprised if your replies show as "Nella- (reply)" on the dialogue screen.

2) Interjections.

the_bigg takes full credit for this.

a) Interjections into a state with no replies: same as in BG2. Read Weidu readme at http://weidu.org

b) Interjections into a state WITH PC replies.

You'll need three files. First file, A#Dump.d, will look like this:

BEGIN A#DUMP
IF ~~ 0   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 1   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 2   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 3   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 4   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 5   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 6   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 7   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 8   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 9   SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 10  SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 11  SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 12  SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 13  SAY ~T~ IF ~~ THEN EXIT END
IF ~~ 14  SAY ~T~ IF ~~ THEN EXIT END
...
(~T~ is not necessary - it's my indication for "Temp". Choose anything you like. :thumbsup: )

Yes, you guessed correctly: each line for each interjection. Now, watch my hands...

For each interjection, we are doing the following: first, we are "saving" the existing replies into a dump state(that's what we need A#dump for), then we disable them. How? It's simple: we add an "if this NPC is not available right now" condition, so they show ONLY if NPC is not available for the interjection), and if the said NPC IS present, we let him speak: we add an EXTEND_BOTTOM where he speaks his mind, and then we COPY_TRANS back.

Too much information too fast? Let's take an example:

Korin is talking to Sister Accalia. He wants to say his piece before PC does. Now, we add a line to Replace.d:

REPLACE A#DUMP IF ~~ THEN 0   SAY ~T~ COPY_TRANS DACCALIA 2  END END // this means we save Accalia's original replies

And now we add another line to Replace.d:

ADD_TRANS_TRIGGER DACCALIA 2  ~OR(3) !InParty("A#Korin") !Detect("A#Korin") StateCheck("A#Korin",CD_STATE_NOTVALID)~ // if Korin is not available, Accalia's replies will show. But if he IS available and is able to speak, Accalia's replies will NOT show. This is essential for his interjection to work correctly.

And now, the actual interjection in Interjections.d:

EXTEND_BOTTOM DACCALIA 2
IF ~InParty("a#Korin") Detect("a#Korin") !StateCheck("a#Korin",CD_STATE_NOTVALID)~ EXTERN A#KORIN A#KorinDACCALIA2
END

CHAIN A#KORIN A#KorinDACCALIA2
~I have seen one of these rituals, once.  It is an imposing sight, almost to the point of being frightening.  All the fallen heroes...  I could almost see them calling from beyond the sun and the stars.~
== DACCALIA ~Your friend speaks truly.~
END
COPY_TRANS A#DUMP 0

And in .tp2, we compile these files separately:

COMPILE ~Mod/Dialogue/A#Dump.d~
COMPILE ~Mod/Dialogue/Replace.d~
COMPILE ~Mod/Dialogue/Interjections.d~

NOTE: Be very, very careful in attributing correct numbers to each interjection, or your game will quickly become a mess.

3) Party members: cre files.

Very easy: create a .chr file, open it in NI. You have three fields to change: death variable(script name), override script, and dialogue file. With "script name", just edit your NPC name with your prefix in. With NPC's override script and NPC's dialogue file, right-click on these fields, choose "edit as string", double-click, edit the necessary value and save. Note that your NPC will have only one dialogue file.



I'll try to add more to it, if I remember anything else useful. But that's it for the time being.

#2 jastey

jastey
  • Gibberlings
  • 7520 posts
  • Gender:Female

Posted 21 March 2008 - 08:12 AM

I just did the following mistake: Adding a check variable for interjections is a good idea, especially if the interjection could fire more than once (But also in all other cases).

#3 Kulyok

Kulyok
  • Modders
  • 5771 posts
  • Gender:Female
  • Location:Moscow, Russia

Posted 21 March 2008 - 09:06 AM

I actually like it when the interjection fires each time the particular state comes up(example: Keldorin with his "<CHARNAME>, must you... ?" when PC is examining "questionable" wares), so my preference would be removing all variables from I_C_T's entirely, but as it's tecnhically impossible... :thumbsup: No, it's good - variables, I mean; just not my preference.

Anyway, yes, I'd just make sure that ADD_TRANS_TRIGGER changes to

ADD_TRANS_TRIGGER DACCALIA 2 ~OR(4) !InParty("A#Korin") !Detect("A#Korin") StateCheck("A#Korin",CD_STATE_NOTVALID) GlobalGT("A#KorinDACCALIA2","GLOBAL",0)~

- or similar, because I made such a mistake in HoW, myself.



Reply to this topic



  


0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users