Add dialog_check.py - a script to check dialog files for common errors.
Add ## DIALOGCHECK blocks to all existing action scripts Allow the pre 'quest' check to express ranges of stages or an exact stage to be at git-svn-id: svn://svn.code.sf.net/p/crossfire/code/maps/trunk@13124 282e977c-c81d-0410-88c4-b93c2d0d6712master
parent
1422f597ae
commit
9f159a519a
|
@ -0,0 +1,157 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# dialog_check.py
|
||||
# This script is *not* intended to be used by the crossfire plugin, it is
|
||||
# designed to verify the correctness of a dialog script -independantly of the crossfire server.
|
||||
|
||||
import cjson
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
def checkactionfile(filename, condition):
|
||||
args = condition[1:]
|
||||
checkstatus = 0
|
||||
if not os.path.isfile(filename):
|
||||
print "Error: No script to support action: ", condition[0], "Expected: ", filename
|
||||
return False
|
||||
else:
|
||||
actf = open(filename,"r")
|
||||
argnum = 0
|
||||
for actline in actf.readlines():
|
||||
if checkstatus == 0:
|
||||
if actline.find("## DIALOGCHECK") != -1:
|
||||
checkstatus = 1
|
||||
elif checkstatus == 1:
|
||||
if actline.find("## ENDDIALOGCHECK") != -1:
|
||||
checkstatus = 2
|
||||
elif actline.find("## MINARGS") != -1:
|
||||
num = actline.split()[2]
|
||||
if not num.isdigit():
|
||||
print "ERROR: Action definition for script ", filename, " MINARGS not defined"
|
||||
return False
|
||||
else:
|
||||
if len(args)<int(num):
|
||||
print "ERROR: Insufficiant options passed to script ", filename, "expected ", int(num), " recieved ", len(args)
|
||||
return False
|
||||
elif actline.find("## MAXARGS") != -1:
|
||||
num = actline.split()[2]
|
||||
if not num.isdigit():
|
||||
print "ERROR: Action definition for script ", filename, " MAXARGS not defined"
|
||||
return False
|
||||
elif int(num) == 0:
|
||||
# zero means there is no upper limit to the number of arguments
|
||||
pass
|
||||
else:
|
||||
if len(args)>int(num):
|
||||
print "ERROR: Too many options passed to script ", filename, "expected ", int(num), " recieved ", len(args)
|
||||
return False
|
||||
elif actline.find("##") != 1:
|
||||
# This is a regexp for one of the arguments
|
||||
if argnum < len(args):
|
||||
argmatch = re.compile(actline.split()[1])
|
||||
if not argmatch.match(args[argnum]):
|
||||
print "ERROR: Argument ", argnum+2, "of rule: ", condition, " doesn't match regexp ", actline.split()[1]
|
||||
return False
|
||||
argnum+=1
|
||||
if checkstatus != 2:
|
||||
print "Warning: No dialogcheck block for file ", filename, " Unable to check condition ", condition
|
||||
return True
|
||||
return True
|
||||
|
||||
def checkdialoguefile(msgfile, location):
|
||||
|
||||
rulenumber = 0
|
||||
errors = 0
|
||||
warnings = 0
|
||||
extrafiles =[]
|
||||
params = {}
|
||||
try:
|
||||
f = open(msgfile,"rb")
|
||||
except:
|
||||
print "ERROR: Can't open file, ", msgfile
|
||||
errors +=1
|
||||
else:
|
||||
try:
|
||||
params = cjson.decode(f.read())
|
||||
except:
|
||||
print "ERROR: Failed to parse file, ", msgfile, "not a valid json file"
|
||||
errors +=1
|
||||
f.close()
|
||||
if "location" in params:
|
||||
if not location == '':
|
||||
print "Warning: Location defined multiple times in included files"
|
||||
warnings+=1
|
||||
location = params["location"]
|
||||
if location =='':
|
||||
print "Warning: no location was specified"
|
||||
warnings +=1
|
||||
rulenumber =0
|
||||
for jsonRule in params["rules"]:
|
||||
rulenumber +=1
|
||||
include = 0
|
||||
msg=0
|
||||
post=0
|
||||
match=0
|
||||
pre =0
|
||||
for action in jsonRule:
|
||||
if action == "pre":
|
||||
pre+=1
|
||||
for condition in jsonRule["pre"]:
|
||||
action = condition[0]
|
||||
path = os.path.join("pre/", action + ".py")
|
||||
if not checkactionfile(path, condition):
|
||||
print "ERROR: verification of action file ", path, " failed for rule ", rulenumber, " condition ", condition
|
||||
errors+=1
|
||||
|
||||
elif action == "msg":
|
||||
msg+=1
|
||||
elif action == "post":
|
||||
post+=1
|
||||
for condition in jsonRule["post"]:
|
||||
action = condition[0]
|
||||
path = os.path.join("post/", action + ".py")
|
||||
if not checkactionfile(path, condition):
|
||||
print "ERROR: verification of action file ", path, " failed for rule ", rulenumber, " condition ", condition
|
||||
errors +=1
|
||||
|
||||
elif action == "match":
|
||||
match+=1
|
||||
elif action == "requires":
|
||||
pass
|
||||
elif action == "suggests":
|
||||
pass
|
||||
elif action == "comment":
|
||||
pass
|
||||
elif action == "include":
|
||||
include+=1
|
||||
for condition in jsonRule["include"]:
|
||||
if condition[0] == "/":
|
||||
inclname = os.path.join("../..", condition[1:])
|
||||
else:
|
||||
inclname = os.path.join(os.path.dirname(msgfile), condition)
|
||||
extrafiles.append(inclname)
|
||||
else:
|
||||
print "Warning: Ignoring unknown rule:", action
|
||||
warnings+=1
|
||||
if (include == 1 and msg+post+match == 0) or (msg == 1 and post == 1 and match ==1 and pre == 1):
|
||||
pass
|
||||
else:
|
||||
print "ERROR: Rule created with an invalid combination of actions, actions are: ", jsonRule.keys()
|
||||
errors +=1
|
||||
newfiles =0
|
||||
newrules =0
|
||||
newwarnings=0
|
||||
newerrors=0
|
||||
if len(extrafiles) > 0:
|
||||
for extrapath in extrafiles:
|
||||
newfiles, newrules, newwarnings, newerrors = checkdialoguefile(extrapath, location)
|
||||
print "checked ", newrules, "rules from file", extrapath, "Found ", newerrors, " errors and ", newwarnings,"warnings"
|
||||
extrafiles = []
|
||||
return (1+newfiles, rulenumber+newrules, warnings+newwarnings, errors+newerrors)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print "usage: python dialog_check.py path/to/dialogfile.msg"
|
||||
exit()
|
||||
for arg in sys.argv[1:]:
|
||||
newfiles, rulecount, newwarnings, newerrors = checkdialoguefile(arg, '')
|
||||
print "checked ", rulecount, "rules from file", arg, "Found ", newerrors, " errors and ", newwarnings,"warnings"
|
|
@ -5,5 +5,10 @@
|
|||
# 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.
|
||||
## DIALOGCHECK
|
||||
## MINARGS 1
|
||||
## MAXARGS 1
|
||||
## \d+
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
speaker.Map.TriggerConnected(int(condition[1]), 1, speaker)
|
|
@ -10,6 +10,12 @@
|
|||
# to the player
|
||||
# containers are matched by item name, not arch name.
|
||||
|
||||
## DIALOGCHECK
|
||||
## MINARGS 1
|
||||
## MAXARGS 1
|
||||
## .*
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
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())
|
|
@ -8,6 +8,13 @@
|
|||
# inventory or in their NPC_Gift_Box if they have one.
|
||||
# items are matched by item name, not arch name.
|
||||
|
||||
## DIALOGCHECK
|
||||
## MINARGS 1
|
||||
## MAXARGS 2
|
||||
## .*
|
||||
## \d
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
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())
|
|
@ -6,6 +6,12 @@
|
|||
# this can then be checked by an age condition that looks for the age
|
||||
# of "nameofmarker"
|
||||
|
||||
## DIALOGCHECK
|
||||
## MINARGS 1
|
||||
## MAXARGS 1
|
||||
## .*
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
markername = args[0]
|
||||
timestamp = map(str, (Crossfire.GetTime())[:5])
|
||||
self.setStatus(markername, "-".join(timestamp))
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
# 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
|
||||
## DIALOGCHECK
|
||||
## MINARGS 2
|
||||
## MAXARGS 2
|
||||
## .*
|
||||
## \d+
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
questname = args[0]
|
||||
stage = args[1]
|
||||
if character.QuestGetState(questname) == 0:
|
||||
|
|
|
@ -5,5 +5,11 @@
|
|||
# The syntax is ["settoken", "tokenname", "valuetosetto"]
|
||||
# this can then be checked by a token condition that looks for the
|
||||
# value of the token
|
||||
## DIALOGCHECK
|
||||
## MINARGS 2
|
||||
## MAXARGS 2
|
||||
## .*
|
||||
## .*
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
self.setStatus(args[0],args[1])
|
|
@ -9,6 +9,12 @@
|
|||
# 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.
|
||||
## DIALOGCHECK
|
||||
## MINARGS 1
|
||||
## MAXARGS 2
|
||||
## .*
|
||||
## \d+
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
itemname = args[0]
|
||||
if len(args) == 2:
|
||||
|
|
|
@ -6,6 +6,17 @@
|
|||
# ["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.
|
||||
## DIALOGCHECK
|
||||
## MINARGS 6
|
||||
## MAXARGS 6
|
||||
## .*
|
||||
## \d
|
||||
## \d
|
||||
## \d
|
||||
## \d
|
||||
## \d
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
|
||||
class checkfailed(Exception):
|
||||
pass
|
||||
|
|
|
@ -10,6 +10,14 @@
|
|||
# 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
|
||||
#
|
||||
## DIALOGCHECK
|
||||
## MINARGS 1
|
||||
## MAXARGS 2
|
||||
## .*
|
||||
## \d+
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
itemname = args[0]
|
||||
if len(args) == 2:
|
||||
quantity = args[1]
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
# 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
|
||||
## DIALOGCHECK
|
||||
## MINARGS 1
|
||||
## MAXARGS 1
|
||||
## \d
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
targetlevel = int(args[0])
|
||||
if len(args) == 1:
|
||||
|
|
|
@ -4,14 +4,38 @@
|
|||
# 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
|
||||
# defined by one of the .quests files.
|
||||
# Queststage can be in one of the following forms:
|
||||
# 1) A number - eg "20" - The player must be at or past step 20 on questname
|
||||
# 2) = A number - eg "=20" - The player must be at exactly step 20 on questname
|
||||
# 3) = A range of numbers - eg "10-30" - The player must be betwen steps 10 and 30 on questname
|
||||
# To deliver a True verdict, the player must be at the right stage in the quest.
|
||||
## DIALOGCHECK
|
||||
## MINARGS 2
|
||||
## MAXARGS 2
|
||||
## .*
|
||||
## (=|)\d+(|-\d+)
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
|
||||
questname = args[0]
|
||||
stage = args[1]
|
||||
if stage == "complete":
|
||||
# todo: implement this
|
||||
pass
|
||||
if character.QuestGetState(questname) < int(stage):
|
||||
if stage.find("-") == -1:
|
||||
if stage[1] == "=":
|
||||
startstep = int(stage[1:])
|
||||
endstep = startstep
|
||||
else:
|
||||
startstep = int(stage)
|
||||
endstep = -1
|
||||
else:
|
||||
startstep = int(condition.split("-")[0])
|
||||
endstep= int(condition.split("-")[1])
|
||||
|
||||
currentstep = character.QuestGetState(questname)
|
||||
if currentstep < startstep:
|
||||
verdict = False
|
||||
if endstep > -1 and currentstep > endstep:
|
||||
verdict = False
|
|
@ -7,6 +7,12 @@
|
|||
# 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
|
||||
## DIALOGCHECK
|
||||
## MINARGS 2
|
||||
## MAXARGS 0
|
||||
## .*
|
||||
## .*
|
||||
## ENDDIALOGCHECK
|
||||
|
||||
status = self.getStatus(args[0])
|
||||
for value in args[1:]:
|
||||
|
|
Loading…
Reference in New Issue