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

Revision 350, 9.5 KB checked in by orlandov, 10 years ago (diff)

Patch by Vaporice and or1andov:

  • add a quest model to the dialogue demo
  • refactor zenbit's dialogue tree to use quest engine
  • Property svn:eol-style set to native
Line 
1#!/usr/bin/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 itertools
20
21class EndException(Exception):
22    """EndException is used to bail out from a deeply nested
23       runSection/continueWithResponse call stack and end the
24       conversation"""
25    pass
26
27class ResponseException(Exception):
28    """ResponseException is used to bail out from a deeply nested
29       runSection/continueWithResponse call stack and allow the user to
30       specify a response"""
31    pass
32
33class BackException(Exception):
34    """BackException is used to bail out from a deeply nested
35       runSection/continueWithResponse call stack and rewind the section
36       stack"""
37    pass
38
39class DialogueEngine(object):
40    def __init__(self, obj, callbacks={}, state={}):
41        """A very simple dialogue engine for a game.
42           d = DialogueEngine(tree, callbacks)
43           d = DialogueEngine('screenplay.yaml', callbacks)"""
44
45        if isinstance(obj, dict):
46            self.tree = obj
47        elif isinstance(obj, str):
48            import yaml
49            self.tree = yaml.load(file(obj))
50
51        logging.basicConfig(level=logging.INFO)
52
53        self.callbacks = callbacks
54        self.state = state
55        self.section_stack = []
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
63        npc_avatar_cb = self.callbacks.get('npc_avatar')
64        if npc_avatar_cb:
65            npc_avatar_cb(self.state, self.tree['AVATAR'])
66
67        try:
68            self.runSection(start_section)
69        except EndException:
70            # we stopped talking to the NPC
71            logging.debug("Reached the end")
72            end_cb = self.callbacks.get('end')
73            if end_cb:
74                end_cb()
75            return
76        except ResponseException, e:
77            return e.args[0]
78        except BackException, e:
79            self.section_stack.pop(-1)
80            try:
81                self.runSection(self.section_stack[-1])
82                return e
83            except ResponseException, e:
84                return e.args[0]
85
86    def getSection(self, section_name):
87        """Return a section object.
88        @type section_name: string
89        @param section_name: The section to get
90        @return: dict"""
91        return self.tree['SECTIONS'][section_name]
92
93    def reply(self, response):
94        """After being prompted to provide a response, reply is called to
95           submit a response.
96           @type choice: int
97           @param choice: the index of the response to submit
98           @return: list of lists (if requesting a response)
99           @return: None (if at the end of the script)"""
100        while True:
101            try:
102                if response is not None:
103                    self.continueWithResponse(self.section_stack[-1], \
104                                                response)
105                else:
106                    self.runSection(self.section_stack[-1])
107            except ResponseException, e:
108                logging.debug("Got response exception %s" % (e.args, ))
109                return e.args[0]
110            except BackException, e:
111                # e.args contains the section to jump back to
112                if e.args:
113                    stack = self.section_stack[:]
114                    stack.reverse()
115                    for i, s in enumerate(stack):
116                        if s == e.args[0]:
117                            # remove the end of the section stack up to desired
118                            # section
119                            del self.section_stack[-i:]
120                            break
121                else:
122                    self.section_stack.pop(-1)
123                response = None
124                continue
125            except EndException:
126                end_cb = self.callbacks.get('end')
127                if end_cb:
128                    end_cb()
129                logging.debug("Reached the end")
130                return
131
132    def continueWithResponse(self, section_name, response):
133        """Reply to a response in a section and continue executing dialogue
134           script
135           @type section_name: str
136           @param section_name: the section to continue
137           @type response: int
138           @param response: the index [0,n-1] of the desired response
139           @raises: EndException on end of script
140           @raises: BackException on "back" reply
141           @return: None"""
142        state = self.state
143        if len(self.section_stack) > 1:
144            if self.section_stack[-1] == self.section_stack[-2]:
145                self.section_stack.pop(-1)
146
147        for command in itertools.cycle(self.getSection(section_name)):
148            if not command.get('responses'):
149                continue
150
151            responses = []
152            for r in command.get('responses'):
153                cond = r[2:]
154                if not cond or eval(cond[0], state, {}):
155                    responses.append(r)
156
157            section = responses[response][1]
158            logging.debug("User chose %s" % (section,))
159
160            if section == "back":
161                raise BackException()
162            elif section.startswith("back "):
163                raise BackException(section[5:])
164            elif section == "end":
165                raise EndException()
166
167            self.runSection(section)
168
169    def runSection(self, section_name):
170        """Run a section
171           @type section_name: string
172           @param section_name: The section to run
173           @return: None
174           @raises: EndException on end of script
175           @raises: BackException on "back" reply"""
176
177        state = self.state
178        self.section_stack.append(section_name)
179
180        if len(self.section_stack) > 1:
181            if self.section_stack[-1] == self.section_stack[-2]:
182                self.section_stack.pop(-1)
183
184        logging.debug("In runSection %s %s" % (section_name, \
185                                               self.section_stack,))
186        for command in itertools.cycle(self.getSection(section_name)):
187            logging.debug("command was %s" % (command,))
188            if command.get("say"):
189                if self.callbacks.get('say'):
190                    self.callbacks["say"](state, command["say"])
191
192            elif command.get("responses"):
193                responses = []
194                for response in command.get('responses'):
195                    cond = response[2:]
196                    if not cond or eval(cond[0], state, {}):
197                        responses.append(response)
198                if self.callbacks.get("responses"):
199                    self.callbacks["responses"](state, responses)
200
201                raise ResponseException(responses)
202
203            elif command.get("start_quest"):
204                self.callbacks["start_quest"](state,
205                        command.get("start_quest"))
206
207            elif command.get("complete_quest"):
208                self.callbacks["complete_quest"](state,
209                        command.get("complete_quest"))
210
211            elif command.get("delete_quest"):
212                self.callbacks["delete_quest"](state,
213                        command.get("delete_quest"))
214
215            elif command.get("increase_value"):
216                self.callbacks["increase_value"](state,
217                        command.get("increase_value")["quest"],
218                        command.get("increase_value")["variable"],
219                        command.get("increase_value")["value"])
220
221            elif command.get("decrease_value"):
222                self.callbacks["decrease_value"](state,
223                        command.get("decrease_value")["quest"],
224                        command.get("decrease_value")["variable"],
225                        command.get("decrease_value")["value"])
226
227            elif command.get("set_value"):
228                self.callbacks["set_value"](state, 
229                        command.get("set_value")["quest"],
230                        command.get("set_value")["variable"],
231                        command.get("set_value")["value"])
232
233            elif command.get("meet"):
234                self.callbacks["meet"](state, command.get("meet"))
235
236            elif command.get("get_stuff"):
237                self.callbacks["get_stuff"](state, command.get("get_stuff"))
238
239            elif command.get("take_stuff"):
240                self.callbacks["take_stuff"](state, command.get("take_stuff"))
241
242            elif command.get("dialogue"):
243                command = command.get("dialogue")
244                if command == "end":
245                    # indicate we"d like to stop talking
246                    raise EndException
247                elif command == "back":
248                    raise BackException()
249                elif command.startswith("back "):
250                    raise BackException(command[5:])
251                else:
252                    raise Exception("Unknown command %s" % (command,))
253
254            else:
255                raise Exception("Unknown command %s %s" % (command,))
Note: See TracBrowser for help on using the repository browser.