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

Revision 300, 7.9 KB checked in by orlandov, 10 years ago (diff)

Patch by or1andov:

  • Only if specified should the npc_avatar callback be called.
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       run_section/continue_with_response 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       run_section/continue_with_response 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       run_section/continue_with_response 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.run_section(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: 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.run_section(self.section_stack[-1])
82                return e
83            except ResponseException, e:
84                return e.args[0]
85
86    def get_section(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.continue_with_response(self.section_stack[-1], response)
104                else:
105                    self.run_section(self.section_stack[-1])
106            except ResponseException, e:
107                logging.debug("Got response exception %s" % (e.args, ))
108                return e.args[0]
109            except BackException, e:
110                # e.args contains the section to jump back to
111                if e.args:
112                    stack = self.section_stack[:]
113                    stack.reverse()
114                    for i,s in enumerate(stack):
115                       if s == e.args[0]:
116                           # remove the end of the section stack up to desired
117                           # section
118                           del self.section_stack[-i:]
119                           break
120                else:
121                    self.section_stack.pop(-1)
122                response = None
123                continue
124            except EndException:
125                end_cb = self.callbacks.get('end')
126                if end_cb: end_cb()
127                logging.debug("Reached the end")
128                return
129
130    def continue_with_response(self, section_name, response):
131        """Reply to a response in a section and continue executing dialogue script
132           @type section_name: str
133           @param section_name: the section to continue
134           @type response: int
135           @param response: the index [0,n-1] of the desired response
136           @raises: EndException on end of script
137           @raises: BackException on "back" reply
138           @return: None"""
139        state = self.state
140        if len(self.section_stack) > 1:
141            if self.section_stack[-1] == self.section_stack[-2]:
142                self.section_stack.pop(-1)
143
144        for command in itertools.cycle(self.get_section(section_name)):
145            if not command.get('responses'): continue
146
147            responses = []
148            for r in command.get('responses'):
149                cond = r[2:]
150                if not cond or eval(cond[0], state, {}):
151                    responses.append(r)
152
153            section = responses[response][1]
154            logging.debug("User chose %s" % (section,))
155
156            if section == "back":
157                raise BackException()
158            elif section.startswith("back "):
159                raise BackException(section[5:])
160            elif section == "end":
161                raise EndException()
162
163            self.run_section(section)
164
165    def run_section(self, section_name):
166        """Run a section
167           @type section_name: string
168           @param section_name: The section to run
169           @return: None
170           @raises: EndException on end of script
171           @raises: BackException on "back" reply"""
172
173        state = self.state
174
175        self.section_stack.append(section_name)
176
177        if len(self.section_stack) > 1:
178            if self.section_stack[-1] == self.section_stack[-2]:
179                self.section_stack.pop(-1)
180
181        logging.debug("In run_section %s %s" % (section_name, self.section_stack,))
182        for command in itertools.cycle(self.get_section(section_name)):
183            if command.get("say"):
184                if self.callbacks.get('say'):
185                    self.callbacks["say"](state, command["say"])
186
187            elif command.get("responses"):
188                responses = []
189                for response in command.get('responses'):
190                    cond = response[2:]
191                    if not cond or eval(cond[0], state, {}):
192                        responses.append(response)
193                if self.callbacks.get("responses"):
194                    self.callbacks["responses"](state, responses)
195
196                raise ResponseException(responses)
197
198            elif command.get("start_quest"):
199                self.callbacks["start_quest"](state, command.get("start_quest"))
200
201            elif command.get("complete_quest"):
202                self.callbacks["complete_quest"](state, command.get("complete_quest"))
203
204            elif command.get("dialogue"):
205                command = command.get("dialogue")
206                if command == "end":
207                    # indicate we"d like to stop talking
208                    raise EndException
209                elif command == "back":
210                    raise BackException()
211                elif command.startswith("back "):
212                    raise BackException(command[5:])
213                else:
214                    raise Exception("Unknown command %s" % (command,))
215
216            else:
217                raise Exception("Unknown command %s %s" % (command,))
Note: See TracBrowser for help on using the repository browser.