User Tools

Site Tools


cfpython:cfdialog

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.

  • Keywords are what the rule answers to. For example, if you want a rule to trigger when the player says hi, then hi must appear in the keyword list. One or more keywords are specified in a string list in the form [“keyword1”, “keyword2” …]. A * character is a special keyword that means: “match everything”, and is useful to create rules that provide generic answers no matter what the player character says.
    • NOTE: Like the @match system, CFDialog converts both keywords and the things the player says to lowercase before checking for a match, so it is never necessary to include multiple keywords that only differ in case.
  • Answers are what the rule will respond, or say, to the player when it is triggered. This is what the NPC replies to the player. Answers are stored in a list of one or more strings in the form [“Answer1”, “Answer2” …]. When there is more than one answer in that list, each time the rule is triggered, a single random reply will be selected from the list.
    • NOTE: Answers may contain line breaks. To insert one, use “\n”.
  • Preconditions are flags that must match specific values in order for a rule to be triggered. These flags persist across gaming sessions and are useful for tracking the state of a conversation with an NPC. Because of this, it is possible for the same word to elicit different NPC responses depending on how flags have been set. If dialogs are set to use identical locations, the flags and preconditions can be used by other NPC dialogs so that other NPCs can detect that the player heard specific information from another NPC. The flags can also be used to help an individual NPC remember what he has said to the player in the past. Flag settings are stored in the player file, so they persist as long as the character exists in the game. Each rule contains a list of one or more preconditions, and each of the individual preconditions is itself a list of a flag name and one or more values in the following format: [["flag1", "value1", "value2" ...], ["flag2", "value3"] ...] where indicates that the pattern may be repeated. The flag name is always the first item in a precondition list. “:” and “;” characters are forbidden in the flag names and values. For a rule to be triggered, all its preconditions must be satisfied by settings in the player file. To satisfy a precondition, one of the its values must match the identified flag setting in the player file. The default value of any precondition that has not been specifically set in the player file is “0”. If one of the precondition values is set to “*”, a match is not required.
  • Postconditions are state changes to apply to the player file flags after the rule triggers. The postcondition is a nested list that has the same format as the precondition list except that each postcondition list only contains one value. This is because the other main difference is that whereas a precondition checks a player file to see if a flag has a certain value, the postcondition causes a value to be stored into the player file, and it does not make sense to store more than one value into a single flag. A value of “*” means that the player file flag will not be changed.
  • Prefunctions are an optional callback function that will be called when a rule's preconditions are all matched, but before the rule is validated. The callback can do additional tests, and should return 1 to allow the rule to be selected, or 0 to block the rule. The function arguments are the player and the actual rule being tested.
  • Postfunctions are an optional callback that is called when a rule has been applied, and after the message is said. It can do additional custom processing. The function arguments are the player and the actual rule having been used.

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.

  • Edit the map file in scorn/taverns/goths as follows.
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)
  • In this example, the player is stored in 'player', and the old man in 'grandpa'. What the player said is in 'message'.
  • Start the crossfire server, login, then enter Goths tavern and walk up to the man now named “grandpa” in the left-hand room. He is in the top left-hand corner of the room.
  • Say hello two times.
Player says: hello
grandpa says: Hello, lad!
Player says: hello
grandpa says: I've heard, you know, I'm not deaf *grmbl*
  • The conversation state is stored in your player file. For example:
$ 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.

  • npc_dialog.py in SVN.
  • Some actual in-game dialogs supported by npc_dialog.py are:
    1. Mork and Gork mini-quest in Scorn
    2. Sigmund in Newbies House
cfpython/cfdialog.txt · Last modified: 2018/03/30 08:00 by karl