Break out the CFDialog python script into a dispatcher for a number of smaller, action oriented scripts.
git-svn-id: svn://svn.code.sf.net/p/crossfire/code/maps/trunk@13014 282e977c-c81d-0410-88c4-b93c2d0d6712master
parent
c5899c93a0
commit
bb1f5d66cf
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
# CFDialog.py - Dialog helper class
|
# CFDialog.py - Dialog helper class
|
||||||
#
|
#
|
||||||
# Copyright (C) 2007 Yann Chachkoff
|
# Copyright (C) 2007 Yann Chachkoff
|
||||||
|
|
@ -25,6 +26,8 @@
|
||||||
# It is made for those who do not want to bother about complex programming,
|
# 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
|
# but just want to make a few dialogs that are better than the @match system
|
||||||
# used in the server.
|
# used in the server.
|
||||||
|
# You will not normally use this directly, but will instead want to call
|
||||||
|
# dialog/npc_dialog.py which will handle most common uses for dialogs.
|
||||||
#
|
#
|
||||||
# How to use CFDialog
|
# How to use CFDialog
|
||||||
# ===================
|
# ===================
|
||||||
|
|
@ -35,8 +38,7 @@
|
||||||
# from CFDialog import DialogRule, Dialog
|
# from CFDialog import DialogRule, Dialog
|
||||||
#
|
#
|
||||||
# Next, build the dialog by creating a sequence of several rules made up of
|
# Next, build the dialog by creating a sequence of several rules made up of
|
||||||
# keywords, answers, preconditions, and postconditions. Optionally, define
|
# keywords, answers, preconditions, and postconditions.
|
||||||
# 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
|
# - 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
|
# trigger when the player says "hi", then "hi" must appear in the keyword
|
||||||
|
|
@ -58,51 +60,14 @@
|
||||||
#
|
#
|
||||||
# NOTE: Answers may contain line breaks. To insert one, use "\n".
|
# NOTE: Answers may contain line breaks. To insert one, use "\n".
|
||||||
#
|
#
|
||||||
# - Preconditions are flags that must match specific values in order for a
|
# - Preconditions are checks that must pass in order for a rule to be
|
||||||
# rule to be triggered. These flags persist across gaming sessions and are
|
# triggered. The checks that can be used are to be found in dialog/pre/*.py
|
||||||
# useful for tracking the state of a conversation with an NPC. Because of
|
# Each file describes how to use the check in question.
|
||||||
# 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, if
|
|
||||||
# any. Supply an empty list [] if no preconditions exist, but otherwise,
|
|
||||||
# each of the preconditions is required to be a list that contains at least
|
|
||||||
# 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 precondition 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
|
# - Postconditions are changes that should be made to the player and/or the
|
||||||
# the rule triggers. If a rule is not intended to set a flag, supply an
|
# game world after the rule triggers. The effects that are available are to
|
||||||
# empty list [] when specifying postconditions, otherwise, postconditions
|
# be found in dialog/post/*.py Each file describes how to use the effect in
|
||||||
# are supplied in a nested list that has the same format as the precondition
|
# question.
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# - A prefunction is 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.
|
|
||||||
#
|
|
||||||
# - A postfunction is 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
|
# 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
|
# involves somebody who triggers it, somebody who answers, and also a unique
|
||||||
|
|
@ -114,58 +79,19 @@
|
||||||
# the dialog. Rules are parsed in a given order, so you must add the most
|
# the dialog. Rules are parsed in a given order, so you must add the most
|
||||||
# generic answer last.
|
# 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'.
|
|
||||||
#
|
|
||||||
## 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)
|
|
||||||
#
|
|
||||||
# The following link points to a page on the Crossfire Wiki shows all the
|
|
||||||
# details needed to actually place this example in an actual game map:
|
|
||||||
#
|
|
||||||
# http://wiki.metalforge.net/doku.php/cfdialog?s=cfdialog#a_simple_example
|
# http://wiki.metalforge.net/doku.php/cfdialog?s=cfdialog#a_simple_example
|
||||||
#
|
#
|
||||||
# A more complex example
|
# A more complex example
|
||||||
# ======================
|
# ======================
|
||||||
#
|
#
|
||||||
# A ./misc/npc_dialog.py script has been written that uses CFDialog, but
|
# A ./misc/npc_dialog.py script has been written that uses CFDialog, but
|
||||||
# allows the dialog data to be written in a slightly different format.
|
# allows the dialog data to be written in JSON format.
|
||||||
|
# This also permits the inclusion of additional files to take in more rules
|
||||||
|
# (this is mostly useful when you have a character who has some specific lines
|
||||||
|
# of dialog but also some other lines that are shared with other characters
|
||||||
|
# - the character can reference their specific lines of dialog directly and
|
||||||
|
# include the general ones.
|
||||||
|
#
|
||||||
# ../scorn/kar/gork.msg is an example that uses multiple keywords and multiple
|
# ../scorn/kar/gork.msg is an example that uses multiple keywords and multiple
|
||||||
# precondition values. Whereas the above example has a linear and predicable
|
# precondition values. Whereas the above example has a linear and predicable
|
||||||
# conversation paths, note how a conversation with Gork can fork, merge, and
|
# conversation paths, note how a conversation with Gork can fork, merge, and
|
||||||
|
|
@ -188,6 +114,7 @@ import Crossfire
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
import CFItemBroker
|
import CFItemBroker
|
||||||
|
|
||||||
class DialogRule:
|
class DialogRule:
|
||||||
|
|
@ -264,7 +191,6 @@ class Dialog:
|
||||||
def speak(self, msg):
|
def speak(self, msg):
|
||||||
for rule in self.__rules:
|
for rule in self.__rules:
|
||||||
if self.isAnswer(msg, rule.getKeyword()) == 1:
|
if self.isAnswer(msg, rule.getKeyword()) == 1:
|
||||||
print "Checking whether to say: ", rule.getMessage()
|
|
||||||
if self.matchConditions(rule) == 1:
|
if self.matchConditions(rule) == 1:
|
||||||
message = rule.getMessage()
|
message = rule.getMessage()
|
||||||
message = message.replace('$me', self.__speaker.QueryName())
|
message = message.replace('$me', self.__speaker.QueryName())
|
||||||
|
|
@ -293,278 +219,44 @@ class Dialog:
|
||||||
# Check the preconditions specified in rule have been met. Preconditions
|
# Check the preconditions specified in rule have been met. Preconditions
|
||||||
# are lists of one or more conditions to check. Each condition specifies
|
# are lists of one or more conditions to check. Each condition specifies
|
||||||
# a check to perform and the options it should act on.
|
# a check to perform and the options it should act on.
|
||||||
# separate functions are called for each type of check.
|
# separate files are used for each type of check.
|
||||||
def matchConditions(self, rule):
|
def matchConditions(self, rule):
|
||||||
|
character = self.__character
|
||||||
|
location = self.__location
|
||||||
|
speaker = self.__speaker
|
||||||
|
verdict = True
|
||||||
for condition in rule.getPreconditions():
|
for condition in rule.getPreconditions():
|
||||||
#try:
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: Trying to test %s." % condition)
|
||||||
print 'attempting to match rule', condition
|
action = condition[0]
|
||||||
if condition[0] == 'quest':
|
args = condition[1:]
|
||||||
print self.matchquest(condition[1:])
|
path = os.path.join(Crossfire.DataDirectory(), Crossfire.MapDirectory(), 'python/dialog/pre/', action + '.py')
|
||||||
if self.matchquest(condition[1:]) == 0:
|
if os.path.isfile(path):
|
||||||
return 0
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: performing test %s." % action)
|
||||||
elif condition[0] == 'item':
|
exec(open(path).read())
|
||||||
print self.matchitem(condition[1:])
|
if verdict == False:
|
||||||
if self.matchitem(condition[1:]) == 0:
|
return 0
|
||||||
return 0
|
else:
|
||||||
elif condition[0] == 'level':
|
Crossfire.Log(Crossfire.LogError, "CFDialog: Pre Block called with unknown action %s." % action)
|
||||||
print self.matchlevel(condition[1:])
|
return 0
|
||||||
if self.matchlevel(condition[1:]) == 0:
|
|
||||||
return 0
|
|
||||||
elif condition[0] == 'age':
|
|
||||||
print self.checkage(condition[1:])
|
|
||||||
if self.checkage(condition[1:]) == 0:
|
|
||||||
return 0
|
|
||||||
elif condition[0] == 'token':
|
|
||||||
print self.checktoken(condition[1:])
|
|
||||||
if self.checktoken(condition[1:]) == 0:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
Crossfire.Log(Crossfire.LogError, "CFDialog: Preconditon called with unknown action.")
|
|
||||||
#except:
|
|
||||||
# Crossfire.Log(Crossfire.LogDebug, "CFDialog: Bad Precondition")
|
|
||||||
# return 0
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Checks whether the token arg[0] has been set to any of the values in args[1] onwards
|
|
||||||
def checktoken(self, args):
|
|
||||||
status = self.getStatus(args[0])
|
|
||||||
for value in args[1:]:
|
|
||||||
if (status == value) or (value == "*"):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# This returns 1 if the token in args[0] was set at least
|
# If a rule triggers, this function goes through each condition and runs the file that handles it.
|
||||||
# args[1] years
|
|
||||||
# args[2] months
|
|
||||||
# args[3] days
|
|
||||||
# args[4] hours
|
|
||||||
# args[5] minutes
|
|
||||||
# ago (in game time).
|
|
||||||
def checkage(self, args):
|
|
||||||
# maximum months, days, hours, as defined by the server.
|
|
||||||
# minutes per hour is hardcoded to approximately 60
|
|
||||||
MAXTIMES = [Crossfire.Time.MONTHS_PER_YEAR, Crossfire.Time.WEEKS_PER_MONTH*Crossfire.Time.DAYS_PER_WEEK,
|
|
||||||
Crossfire.Time.HOURS_PER_DAY, 60]
|
|
||||||
# we have three times to consider, the old time, the current time, and the desired time difference.
|
|
||||||
if len(args) != 6:
|
|
||||||
return 0
|
|
||||||
markername = args[0]
|
|
||||||
oldtime = self.getStatus(markername).split("-")
|
|
||||||
oldtime = map(int, oldtime)
|
|
||||||
if len(oldtime) !=5:
|
|
||||||
|
|
||||||
# The marker hasn't been set yet
|
|
||||||
return 0
|
|
||||||
|
|
||||||
desireddiff = map(int, args[1:])
|
|
||||||
currenttime = (Crossfire.GetTime())[:5]
|
|
||||||
actualdiff = []
|
|
||||||
|
|
||||||
for i in range(5):
|
|
||||||
actualdiff.append(currenttime[i]-oldtime[i])
|
|
||||||
|
|
||||||
for i in range(4,0,-1):
|
|
||||||
# let's tidy up desireddiff first
|
|
||||||
if desireddiff[i] > MAXTIMES[i-1]:
|
|
||||||
desireddiff[i-1] += desireddiff[i] // MAXTIMES[i-1]
|
|
||||||
desireddiff[i] %= MAXTIMES[i-1]
|
|
||||||
# Then actualdiff
|
|
||||||
if actualdiff[i] < 0:
|
|
||||||
actualdiff[i] += MAXTIMES[i-1]
|
|
||||||
actualdiff[i-1] -=1
|
|
||||||
|
|
||||||
print 'tidied up desired difference', desireddiff
|
|
||||||
print 'actual difference', actualdiff
|
|
||||||
for i in range(5):
|
|
||||||
if actualdiff[i] < desireddiff[i]:
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
# is passed a list, returns 1 if the character has at least args[1] of an item called args[0]
|
|
||||||
# if args[1] isn't given, then looks for 1 of the item.
|
|
||||||
def matchitem(self, args):
|
|
||||||
itemname = args[0]
|
|
||||||
if len(args) == 2:
|
|
||||||
quantity = args[1]
|
|
||||||
else:
|
|
||||||
quantity = 1
|
|
||||||
if itemname == "money":
|
|
||||||
if self.__character.Money >= int(quantity):
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
inv = self.__character.CheckInventory(itemname)
|
|
||||||
if inv:
|
|
||||||
if inv.Quantity >= int(quantity):
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# is passed a list, returns 1 if the player is at least at stage args[1] in quest args[0]
|
|
||||||
def matchquest(self, args):
|
|
||||||
questname = args[0]
|
|
||||||
stage = args[1]
|
|
||||||
print 'I am looking for stage ', stage, ' current stage is ', self.__character.QuestGetState(questname)
|
|
||||||
if stage == "complete":
|
|
||||||
# todo: implement this
|
|
||||||
pass
|
|
||||||
if self.__character.QuestGetState(questname) < int(stage):
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# is passed a list, returns 1 if the player is at least at level args[0] either overall or in the skill corresponding to any of the following arguments.
|
|
||||||
def matchlevel(self, args):
|
|
||||||
targetlevel = int(args[0])
|
|
||||||
if len(args) == 1:
|
|
||||||
if self.__character.Level >= targetlevel:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
#TODO
|
|
||||||
|
|
||||||
# If a rule triggers, this function is called to make identified player
|
|
||||||
# file changes, and to call any declared postfunctions to implement more
|
|
||||||
# dramatic effects than the setting of a flag in the player file.
|
|
||||||
def setConditions(self, rule):
|
def setConditions(self, rule):
|
||||||
|
character = self.__character
|
||||||
|
location = self.__location
|
||||||
|
speaker = self.__speaker
|
||||||
|
|
||||||
for condition in rule.getPostconditions():
|
for condition in rule.getPostconditions():
|
||||||
# try:
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: Trying to apply %s." % condition)
|
||||||
if 1:
|
action = condition[0]
|
||||||
print 'trying to apply', condition
|
args = condition[1:]
|
||||||
action = condition[0]
|
path = os.path.join(Crossfire.DataDirectory(), Crossfire.MapDirectory(), 'python/dialog/post/', action + '.py')
|
||||||
if action == 'quest':
|
if os.path.isfile(path):
|
||||||
self.setquest(condition[1:])
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: implementing action %s." % action)
|
||||||
elif action == 'connection':
|
exec(open(path).read())
|
||||||
self.__speaker.Map.TriggerConnected(int(condition[1]), 1, self.__speaker)
|
|
||||||
elif action == 'takeitem':
|
|
||||||
self.takeitem(condition[1:])
|
|
||||||
elif action == 'giveitem':
|
|
||||||
self.giveitem(condition[1:], False)
|
|
||||||
elif action == 'givecontents':
|
|
||||||
self.giveitem(condition[1:], True)
|
|
||||||
elif action == 'marktime':
|
|
||||||
self.marktime(condition[1:])
|
|
||||||
elif action == 'settoken':
|
|
||||||
self.setStatus(condition[1],condition[2])
|
|
||||||
else:
|
|
||||||
Crossfire.Log(Crossfire.LogError, "CFDialog: Post Block called with unknown action.")
|
|
||||||
else:
|
else:
|
||||||
#except:
|
Crossfire.Log(Crossfire.LogError, "CFDialog: Post Block called with unknown action %s." % action)
|
||||||
Crossfire.Log(Crossfire.LogError, "CFDialog: Bad Postcondition")
|
|
||||||
Crossfire.Log(Crossfire.LogError, sys.exc_info()[0])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def marktime(self, args):
|
|
||||||
markername = args[0]
|
|
||||||
timestamp = map(str, (Crossfire.GetTime())[:5])
|
|
||||||
self.setStatus(markername, "-".join(timestamp))
|
|
||||||
|
|
||||||
# moves player to stage args[1] of quest args[0]
|
|
||||||
def setquest(self, args):
|
|
||||||
questname = args[0]
|
|
||||||
stage = args[1]
|
|
||||||
if self.__character.QuestGetState(questname) == 0:
|
|
||||||
print 'starting quest', questname, ' at stage ', stage
|
|
||||||
self.__character.QuestStart(questname, int(stage))
|
|
||||||
elif int(stage) > self.__character.QuestGetState(questname):
|
|
||||||
print 'advancing quest', questname, 'to stage ', stage
|
|
||||||
self.__character.QuestSetState(questname, int(stage))
|
|
||||||
else:
|
|
||||||
Crossfire.Log(Crossfire.LogError, "CFDialog: Tried to advance a quest backwards.")
|
|
||||||
|
|
||||||
# places args[1] copies of item called args[0], into the inventory of the player.
|
|
||||||
# the item must be in the inventory of the NPC first for this to work.
|
|
||||||
# if args[1] is not specified, assume this means 1 copy of the item.
|
|
||||||
# if contents is 'true' then we don't give the player the item, but treat this item as a container,
|
|
||||||
# and give the player the exact contents of the container.
|
|
||||||
def giveitem(self, args, contents):
|
|
||||||
itemname = args[0]
|
|
||||||
if len(args) == 2:
|
|
||||||
quantity = int(args[1])
|
|
||||||
else:
|
|
||||||
quantity = 1
|
|
||||||
if itemname == "money":
|
|
||||||
# we can't guarentee that the player has any particular type of coin already
|
|
||||||
# so create the object first, then add 1 less than the total.
|
|
||||||
if quantity >= 50:
|
|
||||||
id = self.__character.CreateObject('platinum coin')
|
|
||||||
CFItemBroker.Item(id).add(int(quantity/50))
|
|
||||||
if quantity % 50 > 0:
|
|
||||||
id = self.__character.CreateObject('gold coin')
|
|
||||||
CFItemBroker.Item(id).add(int((quantity % 50)/10))
|
|
||||||
if quantity % 50 > 0:
|
|
||||||
id = self.__character.CreateObject('silver coin')
|
|
||||||
CFItemBroker.Item(id).add(int(quantity % 10))
|
|
||||||
else:
|
|
||||||
# what we will do, is increase the number of items the NPC is holding, then
|
|
||||||
# split the stack into the players inventory.
|
|
||||||
# first we will check if there is an NPC_Gift_Box, and look in there.
|
|
||||||
lookin = self.__speaker.CheckInventory("NPC_Gift_Box")
|
|
||||||
if lookin:
|
|
||||||
inv = lookin.CheckInventory(itemname)
|
|
||||||
if not inv:
|
|
||||||
# ok, the NPC has no 'Gift Box', we'll check the other items.
|
|
||||||
inv = self.__speaker.CheckInventory(itemname)
|
|
||||||
else:
|
|
||||||
inv = self.__speaker.CheckInventory(itemname)
|
|
||||||
|
|
||||||
if inv:
|
|
||||||
if contents:
|
|
||||||
nextob=inv.Inventory
|
|
||||||
while nextob:
|
|
||||||
# when giving the contents of a container, always give the
|
|
||||||
# number of items in the container, not the quantity number.
|
|
||||||
quantity = nextob.Quantity
|
|
||||||
if quantity == 0:
|
|
||||||
# if quantity is 0, then we need to set it to one, otherwise bad things happen.
|
|
||||||
nextob.Quantity = 1
|
|
||||||
quantity = 1
|
|
||||||
newob = nextob.Clone(0)
|
|
||||||
newob.Quantity = quantity
|
|
||||||
newob.InsertInto(self.__character)
|
|
||||||
nextob=nextob.Below
|
|
||||||
else:
|
|
||||||
if quantity == 0:
|
|
||||||
nextob.Quantity = 2
|
|
||||||
quantity = 1
|
|
||||||
else:
|
|
||||||
CFItemBroker.Item(inv).add(quantity+1)
|
|
||||||
newob = inv.Split(quantity)
|
|
||||||
|
|
||||||
newob.InsertInto(self.__character)
|
|
||||||
else:
|
|
||||||
# ok, we didn't find any
|
|
||||||
Crossfire.Log(Crossfire.LogError, "Dialog script tried to give a non-existant item to a player")
|
|
||||||
|
|
||||||
# removes args[1] copies of item called args[0], this should only be used if
|
|
||||||
# you have checked the player has those items beforehand.
|
|
||||||
# if args[1] is zero, will take all copies of the item from the first matching stack.
|
|
||||||
# if it is not specified, take 1 copy of the item.
|
|
||||||
def takeitem(self, args):
|
|
||||||
itemname = args[0]
|
|
||||||
if len(args) == 2:
|
|
||||||
quantity = args[1]
|
|
||||||
else:
|
|
||||||
quantity = 1
|
|
||||||
print 'trying to take ', quantity, ' of item ', itemname
|
|
||||||
if itemname == "money":
|
|
||||||
paid = self.__character.PayAmount(int(quantity))
|
|
||||||
if paid == 0:
|
|
||||||
Crossfire.Log(Crossfire.LogError, "Tried to make player pay more than they had")
|
|
||||||
else:
|
|
||||||
inv = self.__character.CheckInventory(itemname)
|
|
||||||
if inv:
|
|
||||||
if quantity == 0:
|
|
||||||
CFItemBroker.Item(inv).subtract(inv.Quantity)
|
|
||||||
else:
|
|
||||||
CFItemBroker.Item(inv).subtract(int(quantity))
|
|
||||||
# we might have been wearing an item that was taken.
|
|
||||||
self.__character.Fix()
|
|
||||||
else:
|
|
||||||
Crossfire.Log(Crossfire.LogError, "Dialog script tried to remove non-existant item from player")
|
|
||||||
|
|
||||||
|
|
||||||
# Search the player file for a particular flag, and if it exists, return
|
# Search the player file for a particular flag, and if it exists, return
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# commongive.py
|
||||||
|
# This is a common block of 'give' code to handle give item and give contents.
|
||||||
|
|
||||||
|
itemname = args[0]
|
||||||
|
if len(args) == 2:
|
||||||
|
quantity = int(args[1])
|
||||||
|
else:
|
||||||
|
quantity = 1
|
||||||
|
if itemname == "money":
|
||||||
|
# we can't guarentee that the player has any particular type of coin already
|
||||||
|
# so create the object first, then add 1 less than the total.
|
||||||
|
if quantity >= 50:
|
||||||
|
id = character.CreateObject('platinum coin')
|
||||||
|
CFItemBroker.Item(id).add(int(quantity/50))
|
||||||
|
if quantity % 50 > 0:
|
||||||
|
id = character.CreateObject('gold coin')
|
||||||
|
CFItemBroker.Item(id).add(int((quantity % 50)/10))
|
||||||
|
if quantity % 50 > 0:
|
||||||
|
id = character.CreateObject('silver coin')
|
||||||
|
CFItemBroker.Item(id).add(int(quantity % 10))
|
||||||
|
else:
|
||||||
|
# what we will do, is increase the number of items the NPC is holding, then
|
||||||
|
# split the stack into the players inventory.
|
||||||
|
# first we will check if there is an NPC_Gift_Box, and look in there.
|
||||||
|
lookin = speaker.CheckInventory("NPC_Gift_Box")
|
||||||
|
if lookin:
|
||||||
|
inv = lookin.CheckInventory(itemname)
|
||||||
|
if not inv:
|
||||||
|
# ok, the NPC has no 'Gift Box', we'll check the other items.
|
||||||
|
inv = speaker.CheckInventory(itemname)
|
||||||
|
else:
|
||||||
|
inv = speaker.CheckInventory(itemname)
|
||||||
|
|
||||||
|
if inv:
|
||||||
|
if contents:
|
||||||
|
nextob=inv.Inventory
|
||||||
|
while nextob:
|
||||||
|
# when giving the contents of a container, always give the
|
||||||
|
# number of items in the container, not the quantity number.
|
||||||
|
quantity = nextob.Quantity
|
||||||
|
if quantity == 0:
|
||||||
|
# if quantity is 0, then we need to set it to one, otherwise bad things happen.
|
||||||
|
nextob.Quantity = 1
|
||||||
|
quantity = 1
|
||||||
|
newob = nextob.Clone(0)
|
||||||
|
newob.Quantity = quantity
|
||||||
|
newob.InsertInto(character)
|
||||||
|
nextob=nextob.Below
|
||||||
|
else:
|
||||||
|
if quantity == 0:
|
||||||
|
nextob.Quantity = 2
|
||||||
|
quantity = 1
|
||||||
|
else:
|
||||||
|
CFItemBroker.Item(inv).add(quantity+1)
|
||||||
|
newob = inv.Split(quantity)
|
||||||
|
|
||||||
|
newob.InsertInto(character)
|
||||||
|
else:
|
||||||
|
# ok, we didn't find any
|
||||||
|
Crossfire.Log(Crossfire.LogError, "Dialog script tried to give a non-existant item to a player")
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# connection.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a post rule of 'connection'
|
||||||
|
# The syntax is ["connection", "numberofconnection"]
|
||||||
|
# Triggers the numbered connection on the local map in the same way as
|
||||||
|
# if button with that connection value had been pressed.
|
||||||
|
|
||||||
|
speaker.Map.TriggerConnected(int(condition[1]), 1, speaker)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# givecontents.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a post rule of 'givecontents'
|
||||||
|
# The syntax is ["givecontents", "nameofcontainer"]
|
||||||
|
# The NPC must have a container with the name "nameofcontainer", either in their
|
||||||
|
# inventory or in their NPC_Gift_Box if they have one.
|
||||||
|
# If a suitable container exists, then a complete copy of the entire contents
|
||||||
|
# of that container is given to the player. The container itself is *not* given
|
||||||
|
# to the player
|
||||||
|
# containers are matched by item name, not arch name.
|
||||||
|
|
||||||
|
contents=True
|
||||||
|
# commongive.py does all of the heavy lifting here.
|
||||||
|
exec(open(os.path.join(Crossfire.DataDirectory(), Crossfire.MapDirectory(), 'python/dialog/commongive.py')).read())
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# giveitem.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a post rule of 'giveitem'
|
||||||
|
# The syntax is ["giveitem", "itemtogive", "quantitytogive"]
|
||||||
|
# "quantitytogive" is optional, if it is missing, then 1 is assumed.
|
||||||
|
# The NPC must have at least one of the item being given, either in their
|
||||||
|
# inventory or in their NPC_Gift_Box if they have one.
|
||||||
|
# items are matched by item name, not arch name.
|
||||||
|
|
||||||
|
contents=False
|
||||||
|
# commongive.py does all of the heavy lifting here.
|
||||||
|
exec(open(os.path.join(Crossfire.DataDirectory(), Crossfire.MapDirectory(), 'python/dialog/commongive.py')).read())
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# marktime.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a post rule of 'marktime'
|
||||||
|
# The syntax is ["marktime", "nameofmarker"]
|
||||||
|
# this can then be checked by an age condition that looks for the age
|
||||||
|
# of "nameofmarker"
|
||||||
|
|
||||||
|
markername = args[0]
|
||||||
|
timestamp = map(str, (Crossfire.GetTime())[:5])
|
||||||
|
self.setStatus(markername, "-".join(timestamp))
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#quest.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a post rule of 'quest'
|
||||||
|
# The syntax is ["quest", "questname", "queststage"]
|
||||||
|
# All arguments are required, questname must be a quest that is
|
||||||
|
# defined by one of the .quests files queststage must be a step
|
||||||
|
# number in that quest
|
||||||
|
questname = args[0]
|
||||||
|
stage = args[1]
|
||||||
|
if character.QuestGetState(questname) == 0:
|
||||||
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: starting quest: %s at stage %s for character %s" %(questname, stage, character.Name))
|
||||||
|
character.QuestStart(questname, int(stage))
|
||||||
|
elif int(stage) > character.QuestGetState(questname):
|
||||||
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: advancing quest: %s to stage %s for character %s" %(questname, stage, character.Name ))
|
||||||
|
character.QuestSetState(questname, int(stage))
|
||||||
|
else:
|
||||||
|
Crossfire.Log(Crossfire.LogError, "CFDialog: Tried to advance a quest backwards.")
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# settoken.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a post rule of 'settoken'
|
||||||
|
# The syntax is ["settoken", "tokenname", "valuetosetto"]
|
||||||
|
# this can then be checked by a token condition that looks for the
|
||||||
|
# value of the token
|
||||||
|
|
||||||
|
self.setStatus(args[0],args[1])
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# takeitem.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a post rule of 'takeitem'
|
||||||
|
# The syntax is ["takeitem", "itemtotake", "quantitytotake"]
|
||||||
|
# "quantitytotake" is optional, if it is missing, then 1 is assumed.
|
||||||
|
# if it is 0, then *all* instances of the item are taken.
|
||||||
|
# The player must have a sufficiant quantity of the item being taken
|
||||||
|
# This should normally be determined by doing an "item" check in the
|
||||||
|
# pre-block of the rule
|
||||||
|
# items are matched by item name, not arch name.
|
||||||
|
|
||||||
|
itemname = args[0]
|
||||||
|
if len(args) == 2:
|
||||||
|
quantity = args[1]
|
||||||
|
else:
|
||||||
|
quantity = 1
|
||||||
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: trying to take: %s of item %s from character %s" %(quantity, itemname, character.Name ))
|
||||||
|
if itemname == "money":
|
||||||
|
paid = character.PayAmount(int(quantity))
|
||||||
|
if paid == 0:
|
||||||
|
Crossfire.Log(Crossfire.LogError, "Tried to make player %s pay more than they had" %(character.Name))
|
||||||
|
else:
|
||||||
|
inv = character.CheckInventory(itemname)
|
||||||
|
if inv:
|
||||||
|
if quantity == 0:
|
||||||
|
CFItemBroker.Item(inv).subtract(inv.Quantity)
|
||||||
|
else:
|
||||||
|
CFItemBroker.Item(inv).subtract(int(quantity))
|
||||||
|
# we might have been wearing an item that was taken.
|
||||||
|
character.Fix()
|
||||||
|
else:
|
||||||
|
Crossfire.Log(Crossfire.LogError, "Dialog script tried to remove non-existant item from player %s" %(character.Name))
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
Dialog 'Pre' block check handlers:
|
||||||
|
|
||||||
|
Every .py file in this directory corresponds to a handler for a class of checks in a dialog file.
|
||||||
|
Each file describes in its comments the check that it performs.
|
||||||
|
|
||||||
|
When writing a new check, you have the variable 'verdict' which you are able to write to.
|
||||||
|
It is passed in with the value 'True' In order to have a check be treated as 'passed' then
|
||||||
|
ensure that this variable is still true when you are done, if you want to have your check fail
|
||||||
|
then set the verdict to 'False' at the point where control goes back to your caller.
|
||||||
|
|
||||||
|
The following variables are also avaiable to you:
|
||||||
|
|
||||||
|
character, speaker, location
|
||||||
|
which hold the CFObject of the player and the NPC and the location where tokens are being
|
||||||
|
written to and read from.
|
||||||
|
|
||||||
|
These should probably be treated as read-only by your check, otherwise you might cause strange
|
||||||
|
side-effects on other checks.
|
||||||
|
|
||||||
|
Other than that, there are no technical limits on what you can do with a check, although it is
|
||||||
|
probably good form not to make drastic changes to the player's character.
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#age.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a pre rule of 'age'
|
||||||
|
# The syntax is
|
||||||
|
# ["age", "agetoken", "years", "months", "days", "hours", "minutes"]
|
||||||
|
# To deliver a True verdict, the agetoken must correspond to a date
|
||||||
|
# that is was at least as long ago as the duration specified in the options.
|
||||||
|
|
||||||
|
class checkfailed(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# maximum months, days, hours, as defined by the server.
|
||||||
|
# minutes per hour is hardcoded to approximately 60
|
||||||
|
|
||||||
|
try:
|
||||||
|
MAXTIMES = [Crossfire.Time.MONTHS_PER_YEAR, Crossfire.Time.WEEKS_PER_MONTH*Crossfire.Time.DAYS_PER_WEEK,
|
||||||
|
Crossfire.Time.HOURS_PER_DAY, 60]
|
||||||
|
# we have three times to consider, the old time, the current time, and the desired time difference.
|
||||||
|
if len(args) != 6:
|
||||||
|
raise checkfailed()
|
||||||
|
markername = args[0]
|
||||||
|
oldtime = self.getStatus(markername).split("-")
|
||||||
|
oldtime = map(int, oldtime)
|
||||||
|
if len(oldtime) !=5:
|
||||||
|
# The marker hasn't been set yet
|
||||||
|
raise checkfailed()
|
||||||
|
|
||||||
|
desireddiff = map(int, args[1:])
|
||||||
|
currenttime = (Crossfire.GetTime())[:5]
|
||||||
|
actualdiff = []
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
actualdiff.append(currenttime[i]-oldtime[i])
|
||||||
|
|
||||||
|
for i in range(4,0,-1):
|
||||||
|
# let's tidy up desireddiff first
|
||||||
|
if desireddiff[i] > MAXTIMES[i-1]:
|
||||||
|
desireddiff[i-1] += desireddiff[i] // MAXTIMES[i-1]
|
||||||
|
desireddiff[i] %= MAXTIMES[i-1]
|
||||||
|
# Then actualdiff
|
||||||
|
if actualdiff[i] < 0:
|
||||||
|
actualdiff[i] += MAXTIMES[i-1]
|
||||||
|
actualdiff[i-1] -=1
|
||||||
|
Crossfire.Log(Crossfire.LogDebug, "CFDialog: tidied up desired difference: %s actual difference %s" %(desireddiff, actualdiff))
|
||||||
|
for i in range(5):
|
||||||
|
if actualdiff[i] < desireddiff[i]:
|
||||||
|
raise checkfailed()
|
||||||
|
except checkfailed:
|
||||||
|
verdict = False
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#item.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a pre rule of 'item'
|
||||||
|
# The syntax is ["item", "itemname", "numberrequired"]
|
||||||
|
# numberrequired is optional, if it is missing then 1 is assumed.
|
||||||
|
# To deliver a True verdict, the player must have at least numberrequired
|
||||||
|
# copies of an item with name 'itemname'
|
||||||
|
# if the itemname is 'money' then as a special case, the check
|
||||||
|
# is against the total value of coin held, in silver. In this
|
||||||
|
# case the value of the coin must exceed numberrequired silver,
|
||||||
|
# in any denominations
|
||||||
|
itemname = args[0]
|
||||||
|
if len(args) == 2:
|
||||||
|
quantity = args[1]
|
||||||
|
else:
|
||||||
|
quantity = 1
|
||||||
|
if itemname == "money":
|
||||||
|
if character.Money < int(quantity):
|
||||||
|
verdict = False
|
||||||
|
else:
|
||||||
|
inv = character.CheckInventory(itemname)
|
||||||
|
if inv:
|
||||||
|
if inv.Quantity < int(quantity):
|
||||||
|
verdict = False
|
||||||
|
else:
|
||||||
|
verdict = False
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#level.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a pre rule of 'level'
|
||||||
|
# The syntax is ["level", "levelrequired"]
|
||||||
|
# To deliver a True verdict, the player must be at or above level levelrequired
|
||||||
|
|
||||||
|
targetlevel = int(args[0])
|
||||||
|
if len(args) == 1:
|
||||||
|
if character.Level < targetlevel:
|
||||||
|
verdict = False
|
||||||
|
else:
|
||||||
|
verdict = False
|
||||||
|
#TODO - add support for checking the level of individual skills
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#quest.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a pre rule of 'quest'
|
||||||
|
# The syntax is ["quest", "questname", "queststage"]
|
||||||
|
# All arguments are required, questname must be a quest that is
|
||||||
|
# defined by one of the .quests files queststage must be a step
|
||||||
|
# number in that quest
|
||||||
|
# To deliver a True verdict, the player must be at or past queststage on questname
|
||||||
|
|
||||||
|
questname = args[0]
|
||||||
|
stage = args[1]
|
||||||
|
if stage == "complete":
|
||||||
|
# todo: implement this
|
||||||
|
pass
|
||||||
|
if character.QuestGetState(questname) < int(stage):
|
||||||
|
verdict = False
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#token.py
|
||||||
|
# This is one of the files that can be called by an npc_dialog,
|
||||||
|
# The following code runs when a dialog has a pre rule of 'token'
|
||||||
|
# The syntax is
|
||||||
|
# ["token", "tokenname", "possiblevalue1", "possiblevalue2", etc]
|
||||||
|
# To deliver a True verdict, the token tokenname must be set to one of the
|
||||||
|
# 'possiblevalue' arguments. This will normally have been done
|
||||||
|
# with a previous use of settoken
|
||||||
|
|
||||||
|
status = self.getStatus(args[0])
|
||||||
|
for value in args[1:]:
|
||||||
|
if (status == value) or (value == "*"):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
verdict = False
|
||||||
|
break
|
||||||
Loading…
Reference in New Issue