Jump to content
HateDread

Using Dialogue System - Between Player and AI?

Recommended Posts

I'm setting identity via the init.sqf, using;

JohnRaynerGroup = group JohnRayner;

JohnRaynerGroup setgroupId ["John Rayner"];

Should this be done with this setgroupId ["John Rayner"]; in his init field, instead?

EDIT: Okay, with a bit of editing:

Init field:

this setidentity "JohnRayner";

Description.ext:

class CfgIdentities

{

class JohnRayner

{

name = "John Rayner";

face = "Face58";

glasses = "None";

speaker = "Adam";

pitch = 0.9;

};

};

I realise now that you need to 'setidentity' the unit the 'class' defined in description.ext.

Also, re. dialogue trees - how would one handle the branching off of conversation, with multiple options? Would one take the end states used in a linear conversation, and change them to 'state's, then with conditions + end-states running off them, and so on? (Basically, each 'state' imitating the original 'startstate', with a knee, conditions, default handling, etc). Not sure how the sqf would reflect this, though.

Edited by HateDread

Share this post


Link to post
Share on other sites

Hatebreed, mind you posting a sample mission so I can look over your FSM? I really want to understand this...rather than just copy and edit out everyone elses conversations X.X

class Sentences {
 class J_0 {
    text = "Glad you could make it, Sir... I have your orders from H.Q.";
    speech[] = {};
    class Arguments {};
 };
 class P_0 {
    text = "Thanks Corporal. Make it good.";
    speech[] = {};
    class Arguments {};
 };
 class J_1 {
    text = "Sir, orders are to infiltrate the comm tower and sieze Colachinko's Commander.";
    speech[] = {};
    class Arguments {};
 };
 class P_1 {
    text = "Roger, I need a sitrep on all enemy forces within 100m of the comm tower.";
    speech[] = {};
    class Arguments {};
 };
 class J_2 {
   text = "@18:00; upon the arrival of Colachinko and his troops, the enemy put a Su-34 on standby. Recent reports show all the troops guarding Colachinko are battle-hardened Spetznas, the north side of the compound contains the barracks. Avoid contact untill you have crossed the runway.";
   speech[] = {};
   class Arguments {};
 };
};

Share this post


Link to post
Share on other sites

Yeah sure thing, buddy - just let me have some Christmas breakfast and what-not. If anyone can recommend a video-capture program I can use in windows explorer, etc, I'll put a video up with a how-to.

Just to avoid clutter, here's a re-hash of my past post, plus a new question:

Also, re. dialogue trees - how would one handle the branching off of conversation, with multiple options? Would one take the end states used in a linear conversation, and change them to 'state's, then with conditions + end-states running off them, and so on? (Basically, each 'state' imitating the original 'startstate', with a knee, conditions, default handling, etc). Not sure how the sqf would reflect this, though.

How does one do a monologue, i.e. a character delivering line after line of its own, with nothing from the other person? Does the FSM condition just point at the units own dialogue, of the previous sentence said, then provide the next one in order, and so on?

Oh, and how does one set addaction-ed elements to the conversation (I.e. starting it, and/or choices in the scroll menu). I think Dwarden said it has a special set of eventhandlers, or something?

Cheers.

EDIT:

http://www.megaupload.com/?d=GKXH6R07

No documentation as of yet.

Enjoy!

- HateDread.

New version uploaded 12:00 am AEST, December 26th. Contains slightly-fixed FSMs, and a readme note. Cheers.

Edited by HateDread

Share this post


Link to post
Share on other sites

Thank you & Merry Christmas. I will have a look now :)

after looking at it more and more its starting to click. the thing that is confusing me is all different FSM's and sqf's. i could easily just change the text and it works but thats not my goal here :P

for one I think im going to start by changing the _J_0 and _P_0 to _A_0 and _B_0. that might help my brain accept this a little more :P

---------- Post added at 03:47 AM ---------- Previous post was at 02:44 AM ----------

I think I understand it a lot more now. The purpose of the two files I guess is if the player is starting the conversation instead of the AI? Also say this is in a 2-12 player COOP. Will johnraynor be speaking to all the players in the server?

Edited by DankTank

Share this post


Link to post
Share on other sites

I only had J and P for 'John' and 'Player' :)

I'm not too sure of the last one - depends on how these are run.

Which two file are you talking about - the two 'chatter_' sqfs? Or the fsms?

Share this post


Link to post
Share on other sites

pretty much both...because of the different structure. the A-B and B-A situation is confusing sort of. Especially since johnraynor says more...if you get what im saying. every time my brain gets it I get confused again because of the player and johnraynor having different FSMs and sqfs more or less X.X lol

is the stringtable.xml part of the FSM?

---------- Post added at 04:31 AM ---------- Previous post was at 04:11 AM ----------

ok so the xml has nothing to do with it i see. I am wondering how do we slow this conversation down? I dont know anyone who can talk that fast :P

Edited by DankTank

Share this post


Link to post
Share on other sites

To follow these steps, you need to have the BI Tools installed, in particular the FSM editor, as they are near impossible to do via notepad. Notepad is preferable for editing the XML file you will also need. To do so. right click the xml file (after saving something in notepad as 'stringtable.xml', which will convert the respective .text into an .xml), and go 'open with -> notepad'. The BI Tools are located here, and the example mission I've designed, to supplement this guide, is located here. It is best you set up an editing directory where you can archive tutorials, programs, and code snippets you may need. Just a suggestion ^^

I hope you enjoy this tutorial and learn from it.

Regards,

- HateDread.

Alrighty, let's ignore the FSMs for a bit. For this example, 'J' references the 'JohnRayner' character, and 'P' references the 'player'.

'Chatter.bikb' basically lays out all the sentences that can be said, each with their own name. I name them like this, but it's only preference; 'STR_DIALOG_TOPIC_CharacterFirstNameLetter_SentenceNumber', so the first said by John Rayner ('Hey!'), is, as I class them under the 'chatter' topic (again, you can name whatever you want - this is why I called it 'chatter.bikb'. You can name it whatever you want ^^); STR_DIALOG_CHATTER_J_0 ('0' being the first sentence, then '1', '2', etc). I did the same for the Player (..._P_0, then ..._P_1, etc). This defining of sentences is repeated in the stringtable.xml you'll find in my mission's root folder:

<?xml version="1.0" encoding="UTF-8"?>

<Project name="Arma2">

<Package name="Mission">

<Container name="Chat Testing">

<Key ID="STR_DIALOG_Chatter_J_0">

<English>Hey!</English>

</Key>

<Key ID="STR_DIALOG_Chatter_J_1">

<English>So those bastards came after you too, eh?</English>

</Key>

<Key ID="STR_DIALOG_Chatter_J_2">

<English>You 'think so'?</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_0">

<English>Uhh, hi.</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_1">

<English>I... I think so.</English>

</Key>

</Container>

</Package>

</Project>

Again, this is simply defining sentence IDs, so they can be used later in the FSMs, and SQFs (basically, the 'key ID'). It is very similar to HTML, and the tags used in these very forums when writing posts (Just '<' and '>' instead of the '[' and ']' symbols, but with the '/' after the start of a closing tag's first symbol, like the closing 'project' tag, '</Project>' ). Each entry of text can be in either <English>,<Russian>, or <German>. In this case, obviously, we'd like English. If you do something like this, with multiple languages;

<?xml version="1.0" encoding="UTF-8"?>

<Project name="Arma2">

<Package name="Mission">

<Container name="Chat Testing">

<Key ID="STR_DIALOG_Chatter_J_0">

<English>Hey!</English>

<Russian>Эй</Russian>

<German>Hallo!</German>

</Key>

It will select the appropriate sentence version depending on the game's language setting, yet will still be of sentence ID 'STR_DIALOG_Chatter_J_0', regardles. This is only recommended in the event that you really need it, as many foreign characters require saving your files differently so that the characters can be handled ^^

In this chat, the conversation is started by 'John Rayner', so his first sentence needs no condition - it doesn't 'wait' for another sentence first. In fact, we manually call it whenever we want, with;

JohnRayner kbTell [player, "chatter", "STR_DIALOG_Chatter_J_0"];

At the end, see how my naming scheme means it's John's first sentence? He opens the conversation, basically, by shouting at the player.

In the SQFs, which are added to the player and John via the init.sqf at start-up, as I will explain later, basically control the reaction of the unit to the incoming sentences/IDs. For Chatter_JohnRayner.sqf:

BIS_convMenu = [];

switch (_sentenceId) do {

case "STR_DIALOG_Chatter_P_0": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_J_1"];

};

case "STR_DIALOG_Chatter_P_1": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_J_2"];

};

default {

};

};

// return the sentence list pool

BIS_convMenu

It is basically switching to one of the cases listed above, depending on what the _sentenceId says. Above, the first 'case' means 'If the incoming sentence from the player is STR_DIALOG_Chatter_P_0, I will say STR_DIALOG_Chatter_J_2 in return.' And this is the same for the 2nd case, but with different IDs for the sentences obviously.

In the Chatter_player.sqf:

BIS_convMenu = [];

switch (_sentenceId) do {

case "STR_DIALOG_Chatter_J_0": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_0"];

};

case "STR_DIALOG_Chatter_J_1": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_1"];

};

default {

};

};

// return the sentence list pool

BIS_convMenu

We have a similar situation - the STR_DIALOG_Chatter_P_0 (The first sentence by the player) of the first case is waiting to be said, but can only be said if that case is 'chosen' by the switch. For this to occur, the incoming sentence ID (basically the sentence said by John Rayner) must be STR_DIALOG_Chatter_J_0, otherwise it will either go to the second case (only if the incoming ID is infact STR_DIALOG_Chatter_J_1 instead), otherwise nothing will happen, and it will go to the 'default' result, which is to say nothing, as marked above by the 'default' section.

As I said earlier, these are added to the units via init.sqf, so they each have the corresponding SQFs, FSMs, and the bikb file loaded correctly, under the same 'topic' (remember, I said I chose 'Chatter' as the topic. Again, name it whatever you want). This is done via these commands in init.sqf:

JohnRayner kbAddtopic["chatter", "kb\chatter.bikb", "kb\chatter_JohnRayner.fsm", {call compile preprocessFileLineNumbers "kb\chatter_JohnRayner.sqf"}];

player kbAddtopic["chatter", "kb\chatter.bikb", "kb\chatter_player.fsm", {call compile preprocessFileLineNumbers "kb\chatter_player.sqf"}];

Red = Subject,

Blue = Bikb,

Lime = SQF,

Magenta = FSM.

Remember to include the path to the file within the quotes, exactly, with exact names - that includes silly mistakes like mis-naming 'JohnRayner', as 'JohnRaynor', etc. Above, the topic, as I said, has been listed as 'chatter', and the corresponding SQFs and FSMs have been correctly assigned to each individual involved.

Now, onto the FSMs!

For starters, open the 'Chatter_player.fsm', as the player is the first character that needs an FSM (remember, I stated earlier that the first sentence spoken is by John Rayner, and his first sentence is executed in a trigger, script, etc, manually, and therefore does not need a condition in an FSM. Therefore, the first sentence said in reaction to any other is the players first sentence, as its the first 'reply' sentence in the entire conversation.

In the FSM, we have the startstate, which is obviously where the FSM starts. Directly after it is what's called a 'knee'; this is the black box that the startstate is connected to. It basically allows us to 'split' the choices of the engine, which then decides which course to take, based on their priority/importance, and any conditions we impose.

To the left of the knee is a condition with name '-'. This is done because it has no real meaning in the conversation, but feel free to name it what you want. It's condition is set to 'true', so it is always active. It then leads to an endstate, which will quit the FSM until it is needed again.

To the right of the knee are 'condition-blocks/objects' (whatever you want to classify them as) that wait for incoming sentence IDs, just like the case switch we used in the SQFs.

(_sentenceId in ["STR_DIALOG_Chatter_J_0"]);

Means 'Wait until the incoming sentence is of ID STR_DIALOG_Chatter_J_0, then continue down that conditions path/branch. This would lead to an endstate, which you can see connected to the end of each condition 'block'. The endstate that follows on from the above example has a code in its initcode that will be executed before the FSM is terminated.

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_0"];

This code basically means 'Say sentence of ID STR_DIALOG_Chatter_P_0 back'. So, in this part of the FSM (I mean, from the knee, up to this particular endstate), it says 'If the incoming sentence is of ID 'STR_DIALOG_Chatter_J_0', then reply with sentence of ID 'STR_DIALOG_Chatter_P_0'. This is the same way the second condition box operates - it waits for the sentence dictated in its condition field, before replying with whatever is specified in its followed endstate.

You may be thinking, if the path to the left of the knee is always active, won't the FSM just default to that, and close without touching the sentences? You see, if you look at the three conditions (the '-' one on the left, and the two on the right), you will notice a 'priority' field. Those on the right are set to priority 1, which is higher than the left condition block's 'Priority 0', so the right-side conditions are checked first, and if nothing fits their conditions, the next priority-level (the '0' of the left box) is then selected, which leads to an exit of the FSM without any action. This is similar to the 'default' section of the SQFs I quoted earlier.

You need to name each box individually. In my example, I have named each condition box on the right as the sentence it is expecting. In the player's FSM, the top-most condition box is waiting for the sentence of ID 'STR_DIALOG_Chatter_J_0', as I said earlier. This sentence, as we defined earlier in both 'Chatter.bikb', and 'stringtable.xml', reads as follows:

Hey!

I have therefore named this condition box as this, so that when I look, I think "Ah, so the condition is waiting for this sentence to be said."

Similarly, the endstate for this condition box, wherein the player responds with a sentence of ID 'STR_DIALOG_Chatter_P_0', has the player saying, as we defined earlier in the same two files (the bikb and the xml);

Uhh, hi.

This is why I have named the endstate this - when I look at it, I think "Ahh, okay, so the player responds with this, as dictated by this endstate's initcode (_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_0"]; )." This is basically just a faster way to tell what is being said where, and is how BIS does their FSM naming.

Lastly, in order to compile your FSM, you need to select a config file. Go up to the 'FsmAttributes' tab, then to 'Compile config'. Navigate to the FSM Editor's install location, where you will find 'ScriptedFSM.cfg'. Choose this for your compile config. Then, go to 'file', then 'compile'. This should pass without any errors, unless you've made a mistake (such as having two conditions of the same name - this also happens if you leave them blank - there may be more than one with blank name, therefore they are duplicates. This is why you name them all something). Then, go to save, and preferably put it in 'MISSIONNAME\kb', with a similar naming scheme to what I have used. Make sure its name, and its location, is IDENTICAL to what you have said it is when adding your topics to the unit via;

JohnRayner kbAddtopic["chatter", "kb\chatter.bikb", "kb\chatter_JohnRayner.fsm", {call compile preprocessFileLineNumbers "kb\chatter_JohnRayner.sqf"}];

player kbAddtopic["chatter", "kb\chatter.bikb", "kb\chatter_player.fsm", {call compile preprocessFileLineNumbers "kb\chatter_player.sqf"}];

Otherwise, enjoy your errors and silent dialogue ^^

If you want your characters to speak with a custom set name when using this method, you need to use the 'setidentity' command, in the unit's init field (this setidentity "JohnRayner"; in John Rayner's, or can be done via another script, or the init.sqf, such as 'JohnRayner setidentity "JohnRayner";'. Remember, the names of identities and the names of the units are classed separately, even if they look the same, as shown here). This must be supplemented with a 'CfgIdentity' with the same name as the identity set above (JohnRayner), as I have done in the description.ext file in the mission's root directory:

class CfgIdentities

{

class JohnRayner

{

name = "John Rayner";

face = "Face58";

glasses = "None";

speaker = "Adam";

pitch = 0.9;

};

};

Here you can set custom face, speaker type, pitch, etc, but most importantly, the name. This will only take effect when the speaker is using direct chat. When the character switches to radio (done automatically when the two units are far apart), the unit's group's callsign will be used. The only way around this is to set the group's callsign ID, as I've done in the init.sqf. Unfortunately, the messages will come from "John Rayner 1", as he is the 'leader' of the group 'John Rayner'. But, it's a small price to pay for such a dynamic conversation system ^^

JohnRaynerGroup = group JohnRayner;

JohnRaynerGroup setgroupId ["John Rayner"];

Hopefully, this includes everything!

I hope that this tutorial serves you well, and that you are now able to create simple, linear conversation between characters, and have made sense of the awesome power of FSMs.

If you have any questions, the BIS Forums are the way to go!

Good luck, best wishes, and Merry Christmas!

- HateDread.

Edited by HateDread

Share this post


Link to post
Share on other sites

very good writeup. I now understand how it starts and why the FSMs are setup differently. Now I have my simple dialog "working"...but it is entirely too fast. I guess now its time to learn more about the FSM. I am also interested in selectable options now (or there would have been no purpose for using the FSM)...I had a quick look at the sample missions and I couldn't get it to work. There is entirely too much stuff tied together in those missions...looks like they have some "SUPER DUPER" automated EDITOR program they hide from everyone...

Share this post


Link to post
Share on other sites

Alright, I think I got some ideas for the choices, as pulled from http://forums.bistudio.com/showthread.php?t=91875.

// here we'll be storing all the sentences from which the player will choose (the menu on the left side of the screen)

// if there's only one option in the array, you will have the sentence as the "Talk to" action

BIS_convMenu = [];

// we want the player to be able to approach his buddy and talk to him via the action menu

// we need to check:

// if I'm pointing at my buddy

// if I'm not answering any of his sentences

// if I haven't told him hello already

// then we add that array to BIS_convMenu - the parameters are mostly self-explanatory

if (_from == buddy1 && _sentenceId == "" && !(_this kbWasSaid [_from, _topic, "hello1", 999999])) then {

BIS_convMenu = BIS_convMenu + [["Say hello.", _topic, "hello1", []]]

};

// here we make the unit say the proper sentence based on the one he just received

// I use switch-case-do, it's completely up to you how to evaluate it (if-then etc.)

switch (_sentenceId) do

{

case "hello1": {

_this kbTell [_from, _topic, "hi_how_are_you"]

};

case "good_you": {

_this kbTell [_from, _topic, "fine_thanks"]

};

case "what_do_we_do_today": {

// here the player will have 3 answers to choose from

BIS_convMenu = BIS_convMenu + [["Football.", _topic, "choose_footbal", []]];

BIS_convMenu = BIS_convMenu + [["Bike.", _topic, "choose_bike", []]];

BIS_convMenu = BIS_convMenu + [["Arma II.", _topic, "choose_arma2", []]]

};

};

// return the sentence list pool

BIS_convMenu

The bold is what interests me - it allows the player to 'start' the conversation with the scroll menu. Similar commands later on provide choices.

Also, to slow it down, you could have a speech file (even silence), that lasts the time you want? It'll never play the next line of dialogue until the speech file is finished.

And, do you think I should make a separate thread with a revised version of that guide?

- HateDread.

Share this post


Link to post
Share on other sites

Good to see you two have made so much progress. Nice work.

Blank phrases are one way to do a monologue.

A says something, B replies with a blank phrase ( kind of like nodding head or something so indicate "I hear ya" ), A replies to B's blank phrase with more chatter, etc.

Also if you attached a sound file with x seconds of utter silence that should alter the pace, since the BIKB does wait for each sound to finish before moving on to next part.

Edited by Evil_Echo

Share this post


Link to post
Share on other sites

Alright, thanks.

In one of the BIS examples of the player choosing a response of four, in the endstate wherein the kbtell is normally done, there is this code, even though the player gets full choice?

switch (floor (random 4)) do

{

case 0: {_this kbTell [_from, _topic,"t04_instructions_P_0"];};

case 1: {_this kbTell [_from, _topic,"t04_instructions_P_4"];};

case 2: {_this kbTell [_from, _topic,"t04_instructions_P_5"];};

case 3: {_this kbTell [_from, _topic,"t04_instructions_P_6"];};

default {};

};

Is this a fall-back incase somehow it's actually the AI responding in this situation - the AI randomly chooses one of the 4, so everything continues?

And actually, the way in which I added multiple choice, without having anything in the endstate after a condition made me think; 'What is the actual connection between the sqf and the FSM? Why can I skip adding things to an FSM, but still have my choices work?'

Cheers.

Share this post


Link to post
Share on other sites

The FSM is just for when the character is pure AI, sqf for when a player runs that slot. So it may not be appropriate to add multiple choice info ( in the menu ) for an AI that will never see that menu. It would be more logical for the FSM to just pick an action in that situation ( perhaps via random number ).

Share this post


Link to post
Share on other sites

Okay, thanks.

I'm wondering the most efficient way to handle and organise the choices, both in the SQF, and the FSMs. It starts to get confusing. Even help with naming conventions for choices would be wonderful :)

Share this post


Link to post
Share on other sites

There is no most efficient way. That is religion.

I use tags based on name of speaker ( D1 == Delta 1 ) and a sequence number, but that gives you very little context to what is being said, only the order.

My only advice is come up with a scheme that works for you and be consistant about it.

Share this post


Link to post
Share on other sites

Well, I think I've got a system in place now. It makes sense to me :)

How does one return to a list of 'subjects' like Oblivion/Fallout - I'm not sure how to, either with SQF or FSM, so that when a particular conversation 'line/subject' has been completed in the conversation, it returns to a root/base, where the original subjects are, which would appear after a 'hello' (which I've got covered).

Share this post


Link to post
Share on other sites

To follow these steps, you need to have the BI Tools installed, in particular the FSM editor, as they are near impossible to do via notepad. Notepad is preferable for editing the XML file you will also need. To do so. right click the xml file (after saving something in notepad as 'stringtable.xml', which will convert the respective .text into an .xml), and go 'open with -> notepad'. The BI Tools are located here, and the example mission I've designed, to supplement this guide, is located here. Required reading is my previous tutorial on basic FSMs and KbTell usage for linear conversations, located here. Please, you must read it first, or this will make no sense.

Enjoy this tutorial, and continue your learning!

Regards,

- HateDread.

Let's dive write in to what we're doing here; creating a simple conversation with some multiple-choice, wherein the player receives multiple options in a conversation, at a particular point (or points), and can then respond, altering the dialogue flow/tree.

To make this easier, I'll try to describe the conversation for you, in typing. ('J' represents 'John Rayner', and 'P' represents the player).

Main Conversation:

J: Hey!

P: Uhh, hi.

J: So those bastards came after you too, eh?

P: I... I think so.

J: You 'think so'?

Now the player is faced with a choice, and it branches:

Choice 1:

P: Shut up!

J: Get lost.

P: Fine!

Choice 2:

P: Well... yeah.

J: You're not much of a talker, are you?

P: ...

It's the same layout as before, with all the same files. In fact, it's the same file, just with a few additions. Here is the (slightly) changed XML file, below:

<?xml version="1.0" encoding="UTF-8"?>

<Project name="Arma2">

<Package name="Mission">

<Container name="Chat Testing">

<Key ID="STR_DIALOG_Chatter_J_0">

<English>Hey!</English>

</Key>

<Key ID="STR_DIALOG_Chatter_J_1">

<English>So those bastards came after you too, eh?</English>

</Key>

<Key ID="STR_DIALOG_Chatter_J_2">

<English>You 'think so'?</English>

</Key>

<Key ID="STR_DIALOG_Chatter_J_P_2_1">

<English>Get lost.</English>

</Key>

<Key ID="STR_DIALOG_Chatter_J_P_3_1">

<English>You're not much of a talker, are you?</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_0">

<English>Uhh, hi.</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_1">

<English>I... I think so.</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_2">

<English>Shut up!</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_3">

<English>Well... yeah.</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_2_1">

<English>Fine!</English>

</Key>

<Key ID="STR_DIALOG_Chatter_P_3_1">

<English>...</English>

</Key>

</Container>

</Package>

</Project>

You'll notice I've slightly changed the naming scheme. Previously, it was 'STR_DIALOG_TOPIC_CharacterFirstNameLetter_SentenceNumber', but this doesn't work when we're doing multiple conversation trees. For this, I use the 'number' part of the above to name a particular branch. As you can see in the XML file above, the tree that goes from 'shut up' (STR_DIALOG_Chatter_P_2), to 'Fine!' (STR_DIALOG_Chatter_P_2_1) has a '2' as it's number, with the '2' representing this particular tree, as the '3' does, in the other.

This is the same as with John Rayner's speech, but I have chosen to name his sentence IDs after the names of the player's. This, like all of the naming scheme above, is simply preference. Feel free to change it as you will. For John, in the sentence tree starting with 'Shut up!' (STR_DIALOG_Chatter_P_2), his first response is 'Get lost.' (STR_DIALOG_Chatter_J_P_2_1). For his sentence ID, I have used the following: 'STR_DIALOG_SUBJECT_SPEAKER_MainCharacterInTree_TreeID_SentenceNumber'. Again, the 'treeID' is just a naming classification by me (in this case, as I said, it is '2', as that's what I've called this conversational 'tree').

The player's SQF is where one of the most important changes has taken place. We need to add the choices between tree '2', and tree '3' (You're probably thinking the first tree should have a '1', but this gets more confusing as it collides with the player's first sentence [At least, in my mind]). Chatter_player.sqf:

BIS_convMenu = [];

switch (_sentenceId) do {

case "STR_DIALOG_Chatter_J_0": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_0"];

};

case "STR_DIALOG_Chatter_J_1": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_1"];

};

case "STR_DIALOG_Chatter_J_2": {

BIS_convMenu = BIS_convMenu + [["Shut up!", _topic, "STR_DIALOG_Chatter_P_2", []]];

BIS_convMenu = BIS_convMenu + [["Well... yeah.", _topic, "STR_DIALOG_Chatter_P_3", []]];

};

case "STR_DIALOG_Chatter_J_P_2_1": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_2_1"];

};

case "STR_DIALOG_Chatter_J_P_3_1": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_P_3_1"];

};

default {

};

};

// return the sentence list pool

BIS_convMenu

Everything is the same, bar the new Sentence IDs (which you know how to handle by now, of course), and the new commands being used. In the new command;

BIS_convMenu = BIS_convMenu + [["Shut up!", _topic, "STR_DIALOG_Chatter_P_2", []]];

Red = What appears in the action menu.

Blue = Sentence ID spoken.

It is best to have these two match, as I have (The sentence spoken as a result of the ID in the above example is 'Shut up!', so it matches the action menu perfectly. You may want this to be different, for whatever reason. It's your mission after all!). In the example, instead of listing your topic (Remember, our topic name is 'chatter'? I specified as such in the last guide. Again, name as you wish), you just have '_topic', which pulls the correct topic from the sentence ID, regardless, so the system looks after itself in that regard.

In case you had forgotten, in the SQF files, we use a 'switch' method, as described previously;

It is basically switching to one of the cases listed above, depending on what the incoming _sentenceId says.

Instead of simply replying with a KbTell, as we've done previously, we're obviously listing choices that the player can make, but these are only ever added in the event that that 'case' is matched, so, in the above, the case where we add the actions to the player's action menu is only ever done when the incoming sentence from John Rayner is 'STR_DIALOG_Chatter_J_2' ('You 'think so'?'). Otherwise, the options are not present, so you need not 'remove' them when they're not needed.

For John Rayner, the SQF is less complex - it's the same old straight KbTell responses to incoming IDs, just with each incoming ID from the player, regardless of its 'branch', being a separate 'case'. Chatter_JohnRayner.sqf;

BIS_convMenu = [];

switch (_sentenceId) do {

case "STR_DIALOG_Chatter_P_0": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_J_1"];

};

case "STR_DIALOG_Chatter_P_1": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_J_2"];

};

case "STR_DIALOG_Chatter_P_2": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_J_P_2_1"];

};

case "STR_DIALOG_Chatter_P_3": {

_this kbTell [_from, _topic, "STR_DIALOG_Chatter_J_P_3_1"];

};

default {

};

};

// return the sentence list pool

BIS_convMenu

In the above, he simply responds on a case-by-case basis. As the player chooses between the two 'trees' ('2', and '3' respectively), John Rayner's SQF can only match one of them, as the one the player chooses is then the 'incoming sentence ID' the 'switch' uses - If the player chooses to reply with

'STR_DIALOG_Chatter_P_2', the SQF can only respond with 'STR_DIALOG_Chatter_J_P_2_1', and the same for if the player chooses '..._P_3', instead - the answer, then, is '..._J_P_2_1', from John Rayner.

Now, onto the FSMs! (You'll definitely need to open up the example mission's 'kb' folder, so this makes sense).

PlayerFSM.jpg

In 'Chatter_Player.fsm' (above), everything you see is the same as before - standard condition-based KbTell-ing the correct sentence based on the incoming one.

The only difference is the third endstate down, on the right. Under my naming scheme, I've given it a name including two separate sentences. If you can recall to the start of this guide, those two sentences are where the conversation splits. Because the action of actually choosing which conversation path to take is entirely player-driven, there is zero need for any FSM usage, here.

You'll notice the 'initcode' field of this endstate lacks a straight-up 'kbtell' command. In this case, you will see a 'switch' similar to those that I used in the SQFs, with a small difference. In this example, the (floor (random 2)) results in a random choice between 0 and 1. (Random, Floor). The reason why I want this, is normally as a safeguard in the event that the character to whom this FSM belongs is infact an AI.

Now, this is impossible as it requires a 'player', so no AI would fill this space. But, in the event of a mission where there are 'playable' slots, and an AI is filling them, the above example will choose one of the two KbTell commands, and so the unit will therefore, through random chance, decide which sentence to say in response (great for re-playability). In this FSM, it will choose between 'STR_DIALOG_Chatter_P_2' and 'STR_DIALOG_Chatter_P_3'.

If you open up 'chatter_JohnRayner.fsm', again, everything is standard - there are simple condition-based KbTells, with no choices being made by the FSM.

Double-KneeConditions.jpg

The only difference is how it's set out, and named. You will notice the extra knee (for those of you who have forgotten, that's the black square), which I added simply to show that there is an incoming dialogue branch there (that is, the dialogue branches due to the player - I only represent it here, in John Rayner's FSM, so that the conditions, and their names, make more sense. This is optional). It has no effect, as it is a graphical difference, only - the info is passed straight from the first knee to the second, and the conditions still checked as normal. I repeat, the knees have no effect. Add as many as you wish.

The names of the two separate incoming choices may seem confusing - I changed the original name of them to make it more obvious. Here, for example the first top-most conditional after the second knee is named 'Choice One (TreeID 2) - Shut up!' I am simply identifying it as the first time any split happens (it's the first choice that must be made), and as the tree of ID '2' (as opposed to the other, '3'). This naming scheme, as I have explained earlier, was to allow my current classification system to not clash with the names of the early dialogues that preceded these choices. Remember, conditions' names are only for classification's-sake - they have no effect on the FSM, as long as they are all unique.

Hopefully, this includes everything needed for multiple-choice conversations.

I hope that this tutorial serves you well, and that you are now able to create interesting dialogue, with randomised re-playability as a bonus, and have made even more sense of the awesome power of FSMs.

If you have any questions, the BIS Forums are the way to go! (This very thread, in particular, would be the most suitable).

Good luck, and best wishes.

- HateDread.

Edited by HateDread
  • Like 1

Share this post


Link to post
Share on other sites

Well done, HateDread. You really set your mind to this project! :pc:

Share this post


Link to post
Share on other sites

Wow, this has been immensely useful, I've now got a working conversation!

One question though, is there some additional condition required to make it MP compatible?

I used HateDread's 2nd example, a player + AI conversation, only substituting my dialogue/naming conventions and used the bis convmenu for each player response (to force the player to read them).

When I host and my friend is the player in the conversation, he does not see the AI responses, only the player ones.

Any suggestions?

Share this post


Link to post
Share on other sites

Glad this was of help to you :)

I've personally not used it in multiplayer, so good question!

I would guess that the place/way you actually execute it would affect it - could you post more details on your implementation?

It might be containing the responses of the AI within the locality of the AI's host.

However, it would be best if, say, Evil-Echo were to post his thoughts.

Sorry I couldn't help you out more, but good luck, mate. I'll continue to try and help out.

Regards,

- HateDread.

Share this post


Link to post
Share on other sites

When writing a MP mission I use MP commands.

The CBA addon has support for remotely executing scripts via events.

However, I prefer to stick to stock functions for broader support. Hence use the MP Framework module and it's RE ( remote execute ) command. This is the method BIS uses in their A2 campaign.

Nice part about that is you have both a rKBTELL option for simple messages, and can use rEXECVM to run a more generic script for messages that take parameters.

An example from a mission, HQ unit alerting base command ( DTeam ) of a change in DEFCON level. The call does not care if the units are AI or player controlled, it will run the actions on whatever machine is appropriate.

  _ok = [objNull, HQWEST, rKBTELL, DTeam, "launch", "HQW_DEFCON3"] call RE;

Edited by Evil_Echo

Share this post


Link to post
Share on other sites

Users of the RE command should read the BI wiki article on the Multiplayer framework. While somewhat confusing, it does cover some important elements needed to understand the MP system. One thing it omits is that you need to have the functions module included in your mission and do the standard waitUntil for that to ensure the module is fully initialized.

waitUntil{BIS_MPF_InitDone};

In regards to using this system, it is basically a one-for-one substitution of the calls to kbtell and kbaddtopic. The overall structure of the BI conversation system remains, you still have the .bikb files and other parts. In fact, you can develop your mission using the regular commands and then substitute the MP versions one at a time as you debug your mission.

All RE commands have similar leading arguments, then the name of the remote command to run and any parameters to pass to that command. The leading arguments are

  1. caller - a source unit or nil. Used to chose machine to run command on. In my usage of kbtell this is always nil.
  2. target - a target unit or nil. Used to chose machine to run command on. In my usage of kbtell, I want the target to be where my speaking unit is located on. While this may sound backwards, it is the correct way to invoke rKBTELL.
  3. options - a string to control how the remote command is run.
    • "loc" - run where the combination of caller and target is local
    • "per" - request persistance. Run this command for any new player that joins the mission after this call is made.
    • "loc + per" - do both

The default option is "loc", so if you wish you may skip the options altogether if using that setting. Which is how I run my remote kbtells.

Following the rKBTELL parameter is the person my speaking unit is addressing, the topic and identifying tag. So, aside from some minor re-ordering of parameters it's quite similar to an ordinary kbtell command.

Share this post


Link to post
Share on other sites

Ok, thanks to your continued help, I am getting closer and closer to making a working conversation in an MP mission!

I've incorporated the functions module, the waitUntil command, rKBTELL + call RE in the AI .fsm, as you recommended.

So here's where I am today: my friend cannot see the AI dialogue on the very first load of the mission, however, when I simply restart the mission he can and then everything works as intended.

Perhaps something isn't finished initializing and there is an additional waitUntil command I am missing?

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×