As many of you have noticed, Arma II introduced a new conversation system (I'll be referring to it as "conversations" further on) which replaced the old say/...Radio description.ext-based approach. There have been many questions regarding this subject, so I decided to write a short overview of the possibilities and usage of this system.
Main advantages
The system itself waits for the sound sample to finish. No more sample time measuring and countless sleeps!
Conversation always flows through the appropriate channel (direct for face to face, team radio for distant team members etc.)
Possibility to create fully dynamic conversations.
Transparent syntax (FSM branches allow fast and efficient edits).
Theory
In order to run a conversation, all of the participants need to have the proper topic added to them. The conversation then starts with the first sentence which one of the participants says to another. After that, the conversation flow is usually controlled by scripts - FSM's and event handlers assigned to the participants. After a participant receives a sentence, his script reacts to its ID and reacts with the proper answer. It may sound a bit complicated, but it's really not.
Let's start with adding the topic. The scripting command to do this is kbAddTopic:
person kbAddTopic [topicName, fileName.bikb, (fileName.fsm, (eventHandler))]
topicName: (string) Can be anything.
fileName.bikb: (string) Name of the .bikb file - will be described later.
fileName.fsm: (string) Name of the .fsm file - will be described later. Optional.
eventHandler: (string or code) Will be described later. Optional.
Here is an example of what the code usually looks like:
unit2 kbAddTopic ["baseDefended", "baseDefended.bikb", "baseDefended_unit2.fsm", {call compile preprocessFileLineNumbers "baseDefended_unit2.sqf"}]
.bikb files
Stand for "Bohemia Interactive Knowledge Base" as it was originally used only for storing an AI unit's memory of what it has seen. Not important to you. You will use these files to store all the speech samples (referred to as "Sentences" further on) used for the topic. This is in fact quite similar to the good old cfgSounds and cfgRadio classes in description.ext. Here is a short .bikb file:
class Sentences
{
class example_01
{
text = "Hello Bret.";
speech[] = {"\Sound\jemaine01.ogg"};
class Arguments {};
};
class example_02
{
text = "Oh, hello Jemaine.";
speech[] = {"\Sound\bret01.ogg"};
class Arguments {};
}
};
class Arguments{};
class Special{};
startWithVocal[] = {hour};
startWithConsonant[] = {europe, university}
text: Subtitles used to caption the sample. If you're using stringtable, use $stringName (has to start with "STR_"!)
speech: Sound sample.
arguments, special, startWithVocal, startWithConsonant: Don't worry about these.
The scripts
In both FSM's and event handlers, there are some default variables which you'll be using quite often:
_this: Receiver of the sentence. The unit that had this particular script assigned via kbAddTopic.
_from: Self-explanatory. Basically the unit that told me the sentence.
_sentenceId: The sentence this unit is reacting to. Defined in .bikb in class Sentences.
_topic: Self-explanatory. Topic name used in kbAddTopic.
FSM's
FSM stands for Finite-state machine. We have released the FSM Editor some time ago. I will not describe its functionality here, I'm sure there are plenty tutorials out there. You can open one of the conversation FSMs used in the example mission (you need to have the FSM Editor installed to open it).
As you can see, the FSM parameter is optional in kbAddTopic. That is because a) you don't want to use scripts at all and rather manage the conversation manually (about that later) or b) the unit is always controlled by a player. The engine recognizes who's controlling the unit by the time it receives a sentence. If it's controlled by AI, the assigned FSM is executed. If it's player-controlled, it fires the event handler. If you're making an MP mission and the unit is playable, you will want to use both the FSM and the event handler.
Event Handlers
Nothing to do with our standard event handlers. This is a code used only if the unit is controlled by a player and is executed not only if it receives a sentence, but also if the player points at someone and is close enough to start a conversation ("Talk to" action appears). This is very important to understand. FSM's are executed only once after each received sentence, event handlers are fired constantly (usually every frame) as long as you're pointing at somebody.
Here is an example showing all you might want to use (you should get familiar with kbTell and kbWasSaid first):
// 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
There. Everything should be explained in the comments inside the code. As you can see, it's nothing more than a compiled sqf function.
"Interrupted" event
The left-side menu on the HUD with the list of possible sentences can be closed via backspace at all times. If you want to handle this event as well, you just have to add new sentence class called Interrupted into you .bikb file. It can be then used as a standard _sentenceId in the script.
Example mission
And finally, here it is. The example mission contains everything that has been explained here and shows you mainly how to manage the files. It contains voice samples from the Harvest Red campaign, please don't be surprised that the conversation is a bit out of context :)
Get it here.
"Manual" conversation flow
For some reasons, you may not want to use FSMs and event handlers to control a conversation. For this, you will want to use kbWasSaid. An example will suffice I think.
miles kbAddTopic ["briefing", "kb\briefing.bikb", ""];
shaftoe kbAddTopic ["briefing", "kb\briefing.bikb", ""];
shaftoe kbTell [miles, "briefing", "shaftoe_briefing_H_1"];
waitUntil {shaftoe kbWasSaid [miles, "briefing", "shaftoe_briefing_H_1", 3]};
miles kbTell [shaftoe, "briefing", "shaftoe_briefing_M_1"];
waitUntil {miles kbWasSaid [shaftoe, "briefing", "shaftoe_briefing_M_1", 3]};
hint "Conversation ended."
Mission accomplished
Well, I think that's everything for now. It's quite possible I forgot to mention something or made a typo in the scripts, please let me know if you're confused about something and I'll edit it out.