222 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
| # CFDialog.py - Dialog helper class
 | |
| #
 | |
| # Copyright (C) 2007 Yann Chachkoff
 | |
| #
 | |
| # This program is free software; you can redistribute it and/or modify
 | |
| # it under the terms of the GNU General Public License as published by
 | |
| # the Free Software Foundation; either version 2 of the License, or
 | |
| # (at your option) any later version.
 | |
| #
 | |
| # This program is distributed in the hope that it will be useful,
 | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| # GNU General Public License for more details.
 | |
| #
 | |
| # You should have received a copy of the GNU General Public License
 | |
| # along with this program; if not, write to the Free Software
 | |
| # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | |
| #
 | |
| # The author can be reached via e-mail at lauwenmark@gmail.com
 | |
| #
 | |
| 
 | |
| # What is this about ?
 | |
| #=======================
 | |
| # This is a small set of 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.
 | |
| #
 | |
| # How to use this.
 | |
| #=======================
 | |
| # First, you need to import DialogRule and Dialog classes. Add the
 | |
| # following line at the beginning of your script:
 | |
| # from CFDialog import DialogRule, Dialog
 | |
| # Then, you can go to the dialogs themselves. A dialog is made of
 | |
| # several rules. Each rule is made of keywords, preconditions,
 | |
| # postconditions, answers and pre/postfunction.
 | |
| # - Keywords are what the rule will be an answer to. For example, if
 | |
| #   you want a rule to be triggered when the player will say "hi",
 | |
| #   then "hi" is the keyword to use. You can associate more than a
 | |
| #   keyword to a rule by concatenating them with the "|" character.
 | |
| #   Finally, "*" is a special keyword that means: "match everything".
 | |
| #   "*" is useful to provide generic answers.
 | |
| # - Answers are what will be said when the rule is triggered. This is
 | |
| #   what the NPC replies to the player. Answers are stored in a list.
 | |
| #   When there is more than one answer in that list, one will be
 | |
| #   selected at random.
 | |
| # - Preconditions are flags that must match for the rule to be triggered.
 | |
| #   Each precondition has a name and a value. The default value of a
 | |
| #   precondition is "0". The flags are stored into each player, and will
 | |
| #   survive between gaming sessions. They are useful to set the point in
 | |
| #   a dialog reached by a player - you can see those as an "NPC memory".
 | |
| #   All conditions must have a name and a value. The ":" and ";" characters
 | |
| #   are forbidden. For a rule to be triggered, all the player's flags should
 | |
| #   match the preconditions. If you give "*" as the value for a precondition,
 | |
| #   it means that there is no need to match it.
 | |
| #   A precondition is a list in the form: [key, value]. All preconditions
 | |
| #   are stored in a list.
 | |
| #   - Postconditions are the status changes to apply to the player's
 | |
| #   conditions after the rule has been triggered. Their format is similar to
 | |
| #   preconditions. A value of "*" means that the condition will not be touched.
 | |
| #   - 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, 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 you have defined your rules, you have to assemble them into a dialog.
 | |
| # Each dialog involves somebody who triggers it, somebody who answers, and
 | |
| # has 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 chose whatever
 | |
| # you want for the dialog name, as long as it contains no space or special
 | |
| # characters, and 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
 | |
| #=================
 | |
| # I want to create a dialog for an old man. If I say "hello" or "hi" for the
 | |
| # first time, grandpa will greet me. If I say it for the second time, he'll
 | |
| # grumble (because he's like that, you know :)). I also need a generic answer
 | |
| # if I say whatever else.
 | |
| # In this example, the player is stored in 'player', and the old man in 'grandpa'.
 | |
| # What 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 is to mark "hello" as "1", to remember we already
 | |
| ## greeted grandpa.
 | |
| # 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 an hello if we already said it before.
 | |
| ## Notice that we used "*" for the postcondition value, meaning that we
 | |
| ## are leaving it as it is.
 | |
| # 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)
 | |
| #
 | |
| ## And finally, the generic answer. This is the last rule of the list.
 | |
| ## We don't need to match any condition, and we don't need to change them,
 | |
| ## 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)
 | |
| #
 | |
| import Crossfire
 | |
| import string
 | |
| import random
 | |
| 
 | |
| class DialogRule:
 | |
|     def __init__(self,keyword,presemaphores, message, postsemaphores, prefunction = None, postfunction = None):
 | |
|         self.__keyword = keyword
 | |
|         self.__presems = presemaphores
 | |
|         self.__message = message
 | |
|         self.__postsems= postsemaphores
 | |
| 	self.__prefunction = prefunction
 | |
| 	self.__postfunction = postfunction
 | |
|     def getKeyword(self):
 | |
|         return self.__keyword
 | |
|     def getMessage(self):
 | |
|         msg = self.__message
 | |
|         l = len(msg)
 | |
|         r = random.randint(0,l-1)
 | |
|         return msg[r]
 | |
|     def getPreconditions(self):
 | |
|         return self.__presems
 | |
|     def getPostconditions(self):
 | |
|         return self.__postsems
 | |
|     def getPrefunction(self):
 | |
|         return self.__prefunction
 | |
|     def getPostfunction(self):
 | |
|         return self.__postfunction
 | |
| 
 | |
| class Dialog:
 | |
|     def __init__(self,character,speaker,location):
 | |
|         self.__character = character
 | |
|         self.__location = location
 | |
|         self.__speaker = speaker
 | |
|         self.__rules = []
 | |
| 
 | |
|     def addRule(self, rule, index):
 | |
|         self.__rules.insert(index,rule)
 | |
| 
 | |
|     def speak(self, msg):
 | |
|         for rule in self.__rules:
 | |
|             if self.isAnswer(msg, rule.getKeyword())==1:
 | |
|                 if self.matchConditions(rule)==1:
 | |
|                     self.__speaker.Say(rule.getMessage())
 | |
|                     self.setConditions(rule)
 | |
|                     return 0
 | |
|         return 1
 | |
|     def isAnswer(self,msg, keyword):
 | |
|         if keyword=="*":
 | |
|             return 1
 | |
|         keys=string.split(keyword,"|")
 | |
|         for ckey in keys:
 | |
|             if string.find(msg,ckey)!=-1:
 | |
|                 return 1
 | |
|         return 0
 | |
| 
 | |
|     def matchConditions(self,rule):
 | |
|         for condition in rule.getPreconditions():
 | |
|             status = self.getStatus(condition[0])
 | |
|             if status!=condition[1]:
 | |
|                 if condition[1]!="*":
 | |
|                     return 0
 | |
|         if rule.getPrefunction() <> None:
 | |
|                 return rule.getPrefunction()(self.__character, rule)
 | |
|         return 1
 | |
| 
 | |
|     def setConditions(self,rule):
 | |
|         for condition in rule.getPostconditions():
 | |
|             key = condition[0]
 | |
|             val = condition[1]
 | |
|             if val!="*":
 | |
|                 self.setStatus(key,val)
 | |
|         if rule.getPostfunction() <> None:
 | |
|                 rule.getPostfunction()(self.__character, rule)
 | |
| 
 | |
|     def getStatus(self,key):
 | |
|         character_status=self.__character.ReadKey("dialog_"+self.__location);
 | |
|         if character_status=="":
 | |
|             return "0"
 | |
|         pairs=string.split(character_status,";")
 | |
|         for i in pairs:
 | |
|             subpair=string.split(i,":")
 | |
|             if subpair[0]==key:
 | |
|                 return subpair[1]
 | |
|         return "0"
 | |
| 
 | |
|     def setStatus(self,key, value):
 | |
|         character_status=self.__character.ReadKey("dialog_"+self.__location);
 | |
|         finished=""
 | |
|         ishere=0
 | |
|         if character_status!="":
 | |
|             pairs=string.split(character_status,";")
 | |
|             for i in pairs:
 | |
|                 subpair=string.split(i,":")
 | |
|                 if subpair[0]==key:
 | |
|                     subpair[1]=value
 | |
|                     ishere=1
 | |
|                 if finished!="":
 | |
|                     finished=finished+";"
 | |
|                 finished=finished+subpair[0]+":"+subpair[1]
 | |
|         if ishere==0:
 | |
|             if finished!="":
 | |
|                 finished=finished+";"
 | |
|             finished=finished+key+":"+value
 | |
|         self.__character.WriteKey("dialog_"+self.__location, finished, 1)
 |