Table of Contents

CFDialog Helper Classes

What is this about ?

This is a small set of Python utility classes, to help you creating complex dialogs. It is made for those who do not want to bother about complex programming, but just want to make a few dialogs that are better than the @match system used in the server.

What is CFDialog?

This is a small set of utility classes, to help you create complex dialogs. It is made for those who do not want to bother about complex programming, but just want to make a few dialogs that are better than the @match system used in the server.

How to use CFDialog

First, create a script that imports the DialogRule and Dialog classes. Add the following line at the beginning of your script:

from CFDialog import DialogRule, Dialog

Next, build the dialog by creating a sequence of several rules made up of keywords, answers, preconditions, and postconditions. Optionally, define prefunctions or postfunctions to enhance the capabilities of the rule.

Once the rules are all defined, assemble them into a dialog. Each dialog involves somebody who triggers it, somebody who answers, and also a unique name so it cannot be confused with other dialogs.

Typically, the “one who triggers” will be the player, and the “one who answers” is an NPC the player was taking to. You are free to choose whatever you want for the dialog name, as long as it contains no whitespace or special characters, and as long as it is not used by another dialog. You can then add the rules you created to the dialog. Rules are parsed in a given order, so you must add the most generic answer last.

A simple example

If I want to create a dialog for an old man, I might want him to respond to “hello” or “hi” differently the first time the player meets the NPC, and differently for subsequent encounters. In this example, grandpa greets the player cordially the first time, but grumbles subequent times (because he's like that, you know :)). This example grandpa also has a generic answer for what ever else is said to him. In the example, the player is stored in 'player', and the old man in 'grandpa', and the player said is in 'message'.

To illustrate the setup and make use of this plugin, a previously inanimate NPC in Goths Tavern will be modified. The order of the following steps is not important, but they will take you through every step required to get an NPC that uses the plugin.

Index: goths
===================================================================
--- goths       (revision 6971)
+++ goths       (working copy)
@@ -974,10 +974,15 @@
 y 23
 end
 arch man
+name grandpa
 x 3
 y 23
 friendly 1 
+arch event_say
+title Python
+slaying /python/maps/scorn-goths-grandpa_say.py
 end
+end
 arch woodfloor
 x 3
 y 24

* Create a new file in the maps directory python/maps/scorn-goths-grandpa_say.py and place the following code in the file.

import Crossfire
import string
from CFDialog import Dialog, DialogRule
#
player=Crossfire.WhoIsActivator()
grandpa=Crossfire.WhoAmI()
message=Crossfire.WhatIsMessage()

# Dialog creation:
speech = Dialog(player, grandpa, "test_grandpa_01")

# The first rule is the "hello" answer, so we place it at index 0 of the
# rules list. The precondition is that we never said hello before. The
# postcondition saves a value of "1" into a player file flag named "hello"
# so grandpa remembers he has already met this player before.

prer = [["hello","0"]]
postr = [["hello", "1"]]
rmsg = ["Hello, lad!","Hi, young fellow!","Howdy!"]
speech.addRule(DialogRule(["hello","hi"], prer, rmsg, postr),0)

# The second rule is the answer to a greeting if he as already met the player
# before.  Notice that "*" is used for the postcondition value, meaning that
# the flag will remain set as it was prior to the rule triggering.

prer = [["hello","1"]]
postr = [["hello", "*"]]
rmsg = ["I've heard, you know, I'm not deaf *grmbl*"]
speech.addRule(DialogRule(["hello","hi"], prer, rmsg, postr),1)

# Finally, the generic answer is written. This is the last rule of the list.
# We don't need to match any condition, and don't need to change any flags,
# so we use "*" in both cases this time.

prer = [["hello","*"]]
postr = [["hello", "*"]]
rmsg = ["What ?", "Huh ?", "What do you want ?"]
speech.addRule(DialogRule(["*"], prer, rmsg, postr),2)

# We only have to let the old man speak now:
speech.speak(message)
Player says: hello
grandpa says: Hello, lad!
Player says: hello
grandpa says: I've heard, you know, I'm not deaf *grmbl*
$ grep -ri test_grandpa var
var/crossfire/players/Player/Player.pl:dialog_test_grandpa_01 hello:1

A more complex example

A /python/misc/npc_dialog.py script has been written that uses CFDialog, but allows the dialog data to be written in a slightly different format. /scorn/kar/gork.msg is an example that uses multiple keywords and multiple precondition values. Whereas the above example has a linear and predicable conversation paths, note how a conversation with Gork can fork, merge, and loop back on itself. The example also illustrates how CFDialog can allow dialogs to affect how other NPCs react to a player. /scorn/kar/mork.msg is a completely different dialog, but it is part of a quest that requires the player to interact with both NPCs in a specific way before the quest prize can be obtained. With the @match system, once the player knew the key words, he could short-circuit the conversation the map designer intended to occur. CFDialog constrains the player to follow the proper conversation thread to qualify to receive the quest reward.

Derivative CFDialog Scripts

CFDialog Helper Classes provide basic functionality that can be used in scripts. Some CFDialog derivatives are:

NPC Dialog

This is a simple script that make use of CFDialog.py and receives parameters from a JSON inside the event message. Alternatively, the JSON parameters, if >= 4096 characters, can be stored in a separate file. Use the 'name' script parameter to specify relative location to the dialog file.

Map Example

An example of a map file entry is:

arch guildmaster
name Sigmund
msg

endmsg
x 11
y 7
resist_physical 100
resist_magic 100
weight 50000000
friendly 1
stand_still 1
arch event_say
name start/sigmund.msg
title Python
slaying /python/misc/npc_dialog.py
end
end

Dialog Example

An example of a JSON dialog similar to the one described above is:

{
  "location" : "test_grandpa_01",
  "rules": [
  {
    "match" : ["hello","hi"],
    "pre" : [["hello","0"]],
    "post" : [["hello","1"]],
    "msg" : ["Hello, lad!","Hi, young fellow!","Howdy!"]
  },
  {
    "match": ["hello","hi"],
    "pre" :[["hello","1"]],
    "post" :[["hello", "*"]],
    "msg" : ["I've heard, you know, I'm not deaf *grmbl*"]
  },
  {
    "match" : ["*"],
    "pre" : [["hello","*"]],
    "post" : [["hello", "*"]],
    "msg" : ["What ?", "Huh ?", "What do you want ?"]
  }
]}

For detailed descriptions of the match, pre, post, and msg formats, see the above CFDialog.py documentation.

match is a list of keyword strings, and corresponds to what the player says that the dialog will respond to.

In the above example, the first rule is applied if the player/character says “hello” or “hi” and if the “hello” flag is set to “0” (default). When the rule is applied, the “hello” flag is then set to “1”.

pre is a list of preconditions that identifies flags that must be set to a particular value in order to trigger a response if a match is detected.

post is a list of postconditions that specify flags that are to be set if a response is triggered.

All of the rule values are lists, and must be enclosed by square braces, but pre and post are lists of lists, so the double square braces ([[]]) are required.

msg defines one or more responses that will be given if the rule triggers. When more than one “msg” value is set up, the NPC randomly selects which one to say each time the rule is applied.

replies optionally defines suggestion to the player if this rule is executed. This is used first to let the player know what options are available, and secondly to alter the text that the player will actually say. This should be a list of items in the form [word, text, type], with word the actual word the player should say, text the text the player will actually say if she says the word, type an optional integer to specify if the text is a regular sentence (0), a reply (1) or a question to ask (2).

A relatively complex example of an npc_dialog.py dialog is given in the Gork treasure room quest. See /scorn/kar/gork.msg in particular as it demonstrates how multiple precondition flag values may be exploited to produce non-linear and variable-path conversations that are less likely to frustrate a player. Refer also to /scorn/kar/mork.msg to see how more than one dialog can reference the same dialog flags.