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

Revision 361, 9.7 KB checked in by orlandov, 10 years ago (diff)

Ticket #104 - Patch by Vaporice and or1andov. Integrate quests into game dialogue and make it possible to persist quest state. fixes[s:trac, t:134]

  • Integrate Vaporice's quest engine implementation
  • Discontinue or1andov's sample dialogue and use Zenbitz's Drunkard
  • Reuse Zenbitz's dialogue functions and callbacks in the game PlayerCharacter? class
  • Whitespace and style tweaks
  • 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                try:
155                    if not cond or eval(cond[0], state, {}):
156                        responses.append(r)
157                except Exception, e:
158                    print "Error in response conditional: %s" % (cond[0],)
159                    #raise e
160
161            section = responses[response][1]
162            logging.debug("User chose %s" % (section,))
163
164            if section == "back":
165                raise BackException()
166            elif section.startswith("back "):
167                raise BackException(section[5:])
168            elif section == "end":
169                raise EndException()
170
171            self.runSection(section)
172
173    def runSection(self, section_name):
174        """Run a section
175           @type section_name: string
176           @param section_name: The section to run
177           @return: None
178           @raises: EndException on end of script
179           @raises: BackException on "back" reply"""
180
181        state = self.state
182        self.section_stack.append(section_name)
183
184        if len(self.section_stack) > 1:
185            if self.section_stack[-1] == self.section_stack[-2]:
186                self.section_stack.pop(-1)
187
188        logging.debug("In runSection %s %s" % (section_name, \
189                                               self.section_stack,))
190        for command in itertools.cycle(self.getSection(section_name)):
191            logging.debug("command was %s" % (command,))
192            if command.get("say"):
193                if self.callbacks.get('say'):
194                    self.callbacks["say"](state, command["say"])
195
196            elif command.get("responses"):
197                responses = []
198                for response in command.get('responses'):
199                    cond = response[2:]
200                    if not cond or eval(cond[0], state, {}):
201                        responses.append(response)
202                if self.callbacks.get("responses"):
203                    self.callbacks["responses"](state, responses)
204
205                raise ResponseException(responses)
206
207            elif command.get("start_quest"):
208                self.callbacks["start_quest"](state,
209                        command.get("start_quest"))
210
211            elif command.get("complete_quest"):
212                self.callbacks["complete_quest"](state,
213                        command.get("complete_quest"))
214
215            elif command.get("delete_quest"):
216                self.callbacks["delete_quest"](state,
217                        command.get("delete_quest"))
218
219            elif command.get("increase_value"):
220                self.callbacks["increase_value"](state,
221                        command.get("increase_value")["quest"],
222                        command.get("increase_value")["variable"],
223                        command.get("increase_value")["value"])
224
225            elif command.get("decrease_value"):
226                self.callbacks["decrease_value"](state,
227                        command.get("decrease_value")["quest"],
228                        command.get("decrease_value")["variable"],
229                        command.get("decrease_value")["value"])
230
231            elif command.get("set_value"):
232                self.callbacks["set_value"](state, 
233                        command.get("set_value")["quest"],
234                        command.get("set_value")["variable"],
235                        command.get("set_value")["value"])
236
237            elif command.get("meet"):
238                self.callbacks["meet"](state, command.get("meet"))
239
240            elif command.get("get_stuff"):
241                self.callbacks["get_stuff"](state, command.get("get_stuff"))
242
243            elif command.get("take_stuff"):
244                self.callbacks["take_stuff"](state, command.get("take_stuff"))
245
246            elif command.get("dialogue"):
247                command = command.get("dialogue")
248                if command == "end":
249                    # indicate we"d like to stop talking
250                    raise EndException
251                elif command == "back":
252                    raise BackException()
253                elif command.startswith("back "):
254                    raise BackException(command[5:])
255                else:
256                    raise Exception("Unknown command %s" % (command,))
257
258            else:
259                raise Exception("Unknown command %s %s" % (command,))
Note: See TracBrowser for help on using the repository browser.