source: trunk/game/scripts/dialogue.py @ 320

Revision 317, 8.1 KB checked in by barra_parpg, 10 years ago (diff)

Patch by sirren & mvbarracuda:

  • New player character animations
  • Changed PC object id to player for consistency reasons (agentxmlgen.py uses the folder name for the object id)
  • Renamed parpg-editor >> parpg_editor (use underscores as space characters!)
  • SVL eol-style >> native fixes
  • Property svn:eol-style set to native
Line 
1#!python
2
3#   This file is part of PARPG.
4
5#   PARPG is free software: you can redistribute it and/or modify
6#   it under the terms of the GNU General Public License as published by
7#   the Free Software Foundation, either version 3 of the License, or
8#   (at your option) any later version.
9
10#   PARPG is distributed in the hope that it will be useful,
11#   but WITHOUT ANY WARRANTY; without even the implied warranty of
12#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#   GNU General Public License for more details.
14
15#   You should have received a copy of the GNU General Public License
16#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
17
18import logging
19import sys
20import itertools
21
22class EndException(Exception):
23    """EndException is used to bail out from a deeply nested
24       runSection/continueWithResponse call stack and end the
25       conversation"""
26    pass
27
28class ResponseException(Exception):
29    """ResponseException is used to bail out from a deeply nested
30       runSection/continueWithResponse call stack and allow the user to
31       specify a response"""
32    pass
33
34class BackException(Exception):
35    """BackException is used to bail out from a deeply nested
36       runSection/continueWithResponse call stack and rewind the section
37       stack"""
38    pass
39
40class DialogueEngine(object):
41    def __init__(self, obj, callbacks={}, state={}):
42        """A very simple dialogue engine for a game.
43           d = DialogueEngine(tree, callbacks)
44           d = DialogueEngine('screenplay.yaml', callbacks)"""
45
46        if isinstance(obj, dict):
47            self.tree = obj
48        elif isinstance(obj, str):
49            import yaml
50            self.tree = yaml.load(file(obj))
51
52        logging.basicConfig(level=logging.INFO)
53
54        self.callbacks = callbacks
55        self.state = state
56
57    def run(self):
58        """Start running the dialogue engine.
59        @returns: list of lists (if requesting a response)
60        @returns: None (if at the end of the script)"""
61        start_section = self.tree['START']
62        self.section_stack = []
63
64        npc_avatar_cb = self.callbacks.get('npc_avatar')
65        if npc_avatar_cb:
66            npc_avatar_cb(self.state, self.tree['AVATAR'])
67
68        try:
69            self.runSection(start_section)
70        except EndException:
71            # we stopped talking to the NPC
72            logging.debug("Reached the end")
73            end_cb = self.callbacks.get('end')
74            if end_cb:
75                end_cb()
76            return
77        except ResponseException, e:
78            return e.args[0]
79        except BackException, e:
80            self.section_stack.pop(-1)
81            try:
82                self.runSection(self.section_stack[-1])
83                return e
84            except ResponseException, e:
85                return e.args[0]
86
87    def getSection(self, section_name):
88        """Return a section object.
89        @type section_name: string
90        @param section_name: The section to get
91        @return: dict"""
92        return self.tree['SECTIONS'][section_name]
93
94    def reply(self, response):
95        """After being prompted to provide a response, reply is called to
96           submit a response.
97           @type choice: int
98           @param choice: the index of the response to submit
99           @return: list of lists (if requesting a response)
100           @return: None (if at the end of the script)"""
101        while True:
102            try:
103                if response is not None:
104                    self.continueWithResponse(self.section_stack[-1], \
105                                                response)
106                else:
107                    self.runSection(self.section_stack[-1])
108            except ResponseException, e:
109                logging.debug("Got response exception %s" % (e.args, ))
110                return e.args[0]
111            except BackException, e:
112                # e.args contains the section to jump back to
113                if e.args:
114                    stack = self.section_stack[:]
115                    stack.reverse()
116                    for i,s in enumerate(stack):
117                       if s == e.args[0]:
118                           # remove the end of the section stack up to desired
119                           # section
120                           del self.section_stack[-i:]
121                           break
122                else:
123                    self.section_stack.pop(-1)
124                response = None
125                continue
126            except EndException:
127                end_cb = self.callbacks.get('end')
128                if end_cb:
129                    end_cb()
130                logging.debug("Reached the end")
131                return
132
133    def continueWithResponse(self, section_name, response):
134        """Reply to a response in a section and continue executing dialogue
135           script
136           @type section_name: str
137           @param section_name: the section to continue
138           @type response: int
139           @param response: the index [0,n-1] of the desired response
140           @raises: EndException on end of script
141           @raises: BackException on "back" reply
142           @return: None"""
143        state = self.state
144        if len(self.section_stack) > 1:
145            if self.section_stack[-1] == self.section_stack[-2]:
146                self.section_stack.pop(-1)
147
148        for command in itertools.cycle(self.getSection(section_name)):
149            if not command.get('responses'):
150                continue
151
152            responses = []
153            for r in command.get('responses'):
154                cond = r[2:]
155                if not cond or eval(cond[0], state, {}):
156                    responses.append(r)
157
158            section = responses[response][1]
159            logging.debug("User chose %s" % (section,))
160
161            if section == "back":
162                raise BackException()
163            elif section.startswith("back "):
164                raise BackException(section[5:])
165            elif section == "end":
166                raise EndException()
167
168            self.runSection(section)
169
170    def runSection(self, section_name):
171        """Run a section
172           @type section_name: string
173           @param section_name: The section to run
174           @return: None
175           @raises: EndException on end of script
176           @raises: BackException on "back" reply"""
177
178        state = self.state
179
180        self.section_stack.append(section_name)
181
182        if len(self.section_stack) > 1:
183            if self.section_stack[-1] == self.section_stack[-2]:
184                self.section_stack.pop(-1)
185
186        logging.debug("In runSection %s %s" % (section_name, \
187                                               self.section_stack,))
188        for command in itertools.cycle(self.getSection(section_name)):
189            if command.get("say"):
190                if self.callbacks.get('say'):
191                    self.callbacks["say"](state, command["say"])
192
193            elif command.get("responses"):
194                responses = []
195                for response in command.get('responses'):
196                    cond = response[2:]
197                    if not cond or eval(cond[0], state, {}):
198                        responses.append(response)
199                if self.callbacks.get("responses"):
200                    self.callbacks["responses"](state, responses)
201
202                raise ResponseException(responses)
203
204            elif command.get("start_quest"):
205                self.callbacks["start_quest"](state, \
206                                              command.get("start_quest"))
207
208            elif command.get("complete_quest"):
209                self.callbacks["complete_quest"](state, \
210                                                 command.get("complete_quest"))
211
212            elif command.get("dialogue"):
213                command = command.get("dialogue")
214                if command == "end":
215                    # indicate we"d like to stop talking
216                    raise EndException
217                elif command == "back":
218                    raise BackException()
219                elif command.startswith("back "):
220                    raise BackException(command[5:])
221                else:
222                    raise Exception("Unknown command %s" % (command,))
223
224            else:
225                raise Exception("Unknown command %s %s" % (command,))
Note: See TracBrowser for help on using the repository browser.