Mercurial > parpg-core
comparison src/parpg/dialogueparsers.py @ 0:1fd2201f5c36
Initial commit of parpg-core.
author | M. George Hansen <technopolitica@gmail.com> |
---|---|
date | Sat, 14 May 2011 01:12:35 -0700 |
parents | |
children | d60f1dab8469 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1fd2201f5c36 |
---|---|
1 # This file is part of PARPG. | |
2 # | |
3 # PARPG is free software: you can redistribute it and/or modify | |
4 # it under the terms of the GNU General Public License as published by | |
5 # the Free Software Foundation, either version 3 of the License, or | |
6 # (at your option) any later version. | |
7 # | |
8 # PARPG is distributed in the hope that it will be useful, | |
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 # GNU General Public License for more details. | |
12 # | |
13 # You should have received a copy of the GNU General Public License | |
14 # along with PARPG. If not, see <http://www.gnu.org/licenses/>. | |
15 """ | |
16 Contains classes for parsing and validating L{Dialogues<Dialogue>} and other | |
17 dialogue-related data. | |
18 | |
19 @TODO Technomage 2010-11-13: Exception handling + validation needs work. | |
20 Currently YAML files are only crudely validated - the code assumes that | |
21 the file contains valid dialogue data, and if that assumption is | |
22 violated and causes the code to raise any TypeErrors, AttributeErrors or | |
23 ValueErrors the code then raises a DialogueFormatError with the | |
24 original (and mostly unhelpful) error message. | |
25 @TODO Technomage 2010-11-13: Support reading and writing unicode. | |
26 """ | |
27 try: | |
28 from cStringIO import StringIO | |
29 except ImportError: | |
30 from StringIO import StringIO | |
31 from collections import Sequence | |
32 try: | |
33 from collections import OrderedDict | |
34 except ImportError: | |
35 # Python version 2.4-2.6 doesn't have the OrderedDict | |
36 from parpg.common.ordereddict import OrderedDict | |
37 import re | |
38 import textwrap | |
39 | |
40 import yaml | |
41 | |
42 from parpg import COPYRIGHT_HEADER | |
43 from parpg.dialogue import (Dialogue, DialogueSection, DialogueResponse, | |
44 DialogueGreeting) | |
45 from parpg.dialogueactions import DialogueAction | |
46 | |
47 import logging | |
48 logger = logging.getLogger('dialogueparser') | |
49 | |
50 class DialogueFormatError(Exception): | |
51 """Exception thrown when the DialogueParser has encountered an error.""" | |
52 | |
53 | |
54 class AbstractDialogueParser(object): | |
55 """ | |
56 Abstract base class defining the interface for parsers responsible for | |
57 constructing a L{Dialogue} from its serialized representation. | |
58 """ | |
59 def load(self, stream): | |
60 """ | |
61 Parse a stream and attempt to construct a new L{Dialogue} instance from | |
62 its serialized representation. | |
63 | |
64 @param stream: open stream containing the serialized representation of | |
65 a Dialogue. | |
66 @type stream: BufferType | |
67 """ | |
68 raise NotImplementedError('AbstractDialogueParser subclasses must ' | |
69 'override the load method.') | |
70 | |
71 def dump(self, dialogue, stream): | |
72 """ | |
73 Serialize a L{Dialogue} instance and dump it to an open stream. | |
74 | |
75 @param dialogue: dialogue to serialize. | |
76 @type dialogue: L{Dialogue} | |
77 @param stream: open stream into which the serialized L{Dialogue} should | |
78 be dumped. | |
79 @type stream: BufferType | |
80 """ | |
81 raise NotImplementedError('AbstractDialogueParser subclasses must ' | |
82 'override the dump method.') | |
83 | |
84 def validate(self, stream): | |
85 """ | |
86 Parse a stream and verify that it contains a valid serialization of a | |
87 L{Dialogue instance}. | |
88 | |
89 @param stream: stream containing the serialized representation of a | |
90 L{Dialogue} | |
91 @type stream: BufferType | |
92 """ | |
93 raise NotImplementedError('AbstractDialogueParser subclasses must ' | |
94 'override the validate method.') | |
95 | |
96 | |
97 class YamlDialogueParser(AbstractDialogueParser): | |
98 """ | |
99 L{AbstractDialogueParser} subclass responsible for parsing dialogues | |
100 serialized in YAML. | |
101 """ | |
102 logger = logging.getLogger('dialogueparser.OldYamlDialogueParser') | |
103 | |
104 def load(self, stream, loader_class=yaml.Loader): | |
105 """ | |
106 Parse a YAML stream and attempt to construct a new L{Dialogue} | |
107 instance. | |
108 | |
109 @param stream: stream containing the serialized YAML representation of | |
110 a L{Dialogue}. | |
111 @type stream: BufferType | |
112 @param loader_class: PyYAML loader class to use for reading the | |
113 serialization. | |
114 @type loader_class: yaml.BaseLoader subclass | |
115 """ | |
116 loader = loader_class(stream) | |
117 try: | |
118 dialogue = \ | |
119 self._constructDialogue(loader, loader.get_single_node()) | |
120 except (AssertionError,) as error: | |
121 raise DialogueFormatError(str(error)) | |
122 return dialogue | |
123 | |
124 def dump(self, dialogue, output_stream, dumper_class=yaml.Dumper): | |
125 """ | |
126 Serialize a L{Dialogue} instance as YAML and dump it to an open stream. | |
127 | |
128 @param dialogue: dialogue to serialize. | |
129 @type dialogue: L{Dialogue} | |
130 @param stream: open stream into which the serialized L{Dialogue} should | |
131 be dumped. | |
132 @type stream: BufferType | |
133 @param dumper_class: PyYAML dumper class to use for formatting the | |
134 serialization. | |
135 @type dumper_class: yaml.BaseDumper subclass | |
136 """ | |
137 intermediate_stream = StringIO() | |
138 # KLUDE Technomage 2010-11-16: The "width" argument seems to be broken, | |
139 # as it doesn't take into about current line indentation and fails | |
140 # to correctly wrap at word boundaries. | |
141 dumper = dumper_class(intermediate_stream, default_flow_style=False, | |
142 indent=4, width=99999, line_break='\n', | |
143 allow_unicode=True, explicit_start=True, | |
144 explicit_end=True, tags=False) | |
145 dialogue_node = self._representDialogue(dumper, dialogue) | |
146 dumper.open() | |
147 dumper.serialize(dialogue_node) | |
148 dumper.close() | |
149 file_contents = intermediate_stream.getvalue() | |
150 | |
151 file_contents = re.sub(r'(\n|\r|\r\n)(\s*)(GOTO: .*)', r'\1\2\3\1\2', | |
152 file_contents) | |
153 lines = file_contents.splitlines() | |
154 max_line_length = 76 # 79 - 3 chars for escaping newlines | |
155 for i in range(len(lines)): | |
156 line = lines[i] | |
157 match = re.match( | |
158 r'^(\s*(?:-\s+)?)(SAY|REPLY|CONDITION):\s+"(.*)"$', | |
159 line | |
160 ) | |
161 if (match and len(line) > max_line_length): | |
162 # Wrap long lines for readability. | |
163 initial_indent = len(match.group(1)) | |
164 subsequent_indent = initial_indent + 4 | |
165 text_wrapper = textwrap.TextWrapper( | |
166 max_line_length, | |
167 subsequent_indent=' ' * subsequent_indent, | |
168 break_long_words=False, | |
169 break_on_hyphens=False | |
170 ) | |
171 new_lines = text_wrapper.wrap(line) | |
172 new_lines = ( | |
173 new_lines[:1] + [re.sub(r'^(\s*) (.*)$', r'\1\ \2', l) | |
174 for l in new_lines[1:]] | |
175 ) | |
176 lines[i] = '\\\n'.join(new_lines) | |
177 | |
178 output_stream.write(COPYRIGHT_HEADER) | |
179 output_stream.write('\n'.join(lines)) | |
180 | |
181 | |
182 def _representDialogue(self, dumper, dialogue): | |
183 dialogue_node = dumper.represent_dict({}) | |
184 dialogue_dict = OrderedDict() | |
185 dialogue_dict['NPC_NAME'] = dialogue.npc_name | |
186 dialogue_dict['AVATAR_PATH'] = dialogue.avatar_path | |
187 dialogue_dict['DEFAULT_GREETING'] = \ | |
188 self._representDialogueSection(dumper, | |
189 dialogue.default_greeting) | |
190 # NOTE Technomage 2010-11-16: Dialogue stores its sections in an | |
191 # OrderedDict, so a round-trip load, dump, and load will preserve | |
192 # the order of DialogueSections. | |
193 if (len(dialogue.greetings) > 0): | |
194 greetings_list_node = dumper.represent_list([]) | |
195 greetings_list = greetings_list_node.value | |
196 for greeting in dialogue.greetings: | |
197 greeting_node = \ | |
198 self._representRootDialogueSection(dumper, greeting) | |
199 greetings_list.append(greeting_node) | |
200 dialogue_dict['GREETINGS'] = greetings_list_node | |
201 if (len(dialogue.setions) > 0): | |
202 sections_list_node = dumper.represent_list([]) | |
203 sections_list = sections_list_node.value | |
204 for section in dialogue.sections.values(): | |
205 section_node = self._representDialogueSection(dumper, section) | |
206 sections_list.append(section_node) | |
207 dialogue_dict['SECTIONS'] = sections_list_node | |
208 | |
209 for key, value in dialogue_dict.items(): | |
210 if (isinstance(key, yaml.Node)): | |
211 key_node = key | |
212 else: | |
213 key_node = dumper.represent_data(key) | |
214 if (isinstance(value, yaml.Node)): | |
215 value_node = value | |
216 else: | |
217 value_node = dumper.represent_data(value) | |
218 dialogue_node.value.append((key_node, value_node)) | |
219 return dialogue_node | |
220 | |
221 def _representRootDialogueSection(self, dumper, greeting): | |
222 greeting_node = dumper.represent_dict({}) | |
223 greeting_dict = OrderedDict() | |
224 greeting_dict['ID'] = greeting.id | |
225 greeting_dict['CONDITION'] = dumper.represent_scalar( | |
226 'tag:yaml.org,2002:str', | |
227 greeting.condition, | |
228 style='"' | |
229 ) | |
230 for key, value in greeting_dict.items(): | |
231 if (isinstance(key, yaml.Node)): | |
232 key_node = key | |
233 else: | |
234 key_node = dumper.represent_data(key) | |
235 if (isinstance(value, yaml.Node)): | |
236 value_node = value | |
237 else: | |
238 value_node = dumper.represent_data(value) | |
239 greeting_node.value.append((key_node, value_node)) | |
240 return greeting_node | |
241 | |
242 def _representDialogueSection(self, dumper, dialogue_section): | |
243 section_node = dumper.represent_dict({}) | |
244 section_dict = OrderedDict() # OrderedDict is required to preserve | |
245 # the order of attributes. | |
246 section_dict['ID'] = dialogue_section.id | |
247 # KLUDGE Technomage 2010-11-16: Hard-coding the tag like this could be | |
248 # a problem when writing unicode. | |
249 section_dict['SAY'] = dumper.represent_scalar('tag:yaml.org,2002:str', | |
250 dialogue_section.text, | |
251 style='"') | |
252 actions_list_node = dumper.represent_list([]) | |
253 actions_list = actions_list_node.value | |
254 for action in dialogue_section.actions: | |
255 action_node = self._representDialogueAction(dumper, action) | |
256 actions_list.append(action_node) | |
257 if (actions_list): | |
258 section_dict['ACTIONS'] = actions_list_node | |
259 responses_list_node = dumper.represent_list([]) | |
260 responses_list = responses_list_node.value | |
261 for response in dialogue_section.responses: | |
262 response_node = self._representDialogueResponse(dumper, response) | |
263 responses_list.append(response_node) | |
264 section_dict['RESPONSES'] = responses_list_node | |
265 | |
266 for key, value in section_dict.items(): | |
267 if (isinstance(key, yaml.Node)): | |
268 key_node = key | |
269 else: | |
270 key_node = dumper.represent_data(key) | |
271 if (isinstance(value, yaml.Node)): | |
272 value_node = value | |
273 else: | |
274 value_node = dumper.represent_data(value) | |
275 section_node.value.append((key_node, value_node)) | |
276 return section_node | |
277 | |
278 def _representDialogueResponse(self, dumper, dialogue_response): | |
279 response_node = dumper.represent_dict({}) | |
280 response_dict = OrderedDict() | |
281 # KLUDGE Technomage 2010-11-16: Hard-coding the tag like this could be | |
282 # a problem when writing unicode. | |
283 response_dict['REPLY'] = dumper.represent_scalar( | |
284 'tag:yaml.org,2002:str', | |
285 dialogue_response.text, | |
286 style='"') | |
287 if (dialogue_response.condition is not None): | |
288 response_dict['CONDITION'] = dumper.represent_scalar( | |
289 'tag:yaml.org,2002:str', | |
290 dialogue_response.condition, | |
291 style='"' | |
292 ) | |
293 actions_list_node = dumper.represent_list([]) | |
294 actions_list = actions_list_node.value | |
295 for action in dialogue_response.actions: | |
296 action_node = self._representDialogueAction(dumper, action) | |
297 actions_list.append(action_node) | |
298 if (actions_list): | |
299 response_dict['ACTIONS'] = actions_list_node | |
300 response_dict['GOTO'] = dialogue_response.next_section_id | |
301 | |
302 for key, value in response_dict.items(): | |
303 if (isinstance(key, yaml.Node)): | |
304 key_node = key | |
305 else: | |
306 key_node = dumper.represent_data(key) | |
307 if (isinstance(value, yaml.Node)): | |
308 value_node = value | |
309 else: | |
310 value_node = dumper.represent_data(value) | |
311 response_node.value.append((key_node, value_node)) | |
312 return response_node | |
313 | |
314 def _representDialogueAction(self, dumper, dialogue_action): | |
315 action_node = dumper.represent_dict({}) | |
316 action_dict = OrderedDict() | |
317 args, kwargs = dialogue_action.arguments | |
318 if (args and not kwargs): | |
319 arguments = list(args) | |
320 elif (kwargs and not args): | |
321 arguments = kwargs | |
322 else: | |
323 arguments = [list(args), kwargs] | |
324 action_dict[dialogue_action.keyword] = arguments | |
325 | |
326 for key, value in action_dict.items(): | |
327 if (isinstance(key, yaml.Node)): | |
328 key_node = key | |
329 else: | |
330 key_node = dumper.represent_data(key) | |
331 if (isinstance(value, yaml.Node)): | |
332 value_node = value | |
333 else: | |
334 value_node = dumper.represent_data(value) | |
335 action_node.value.append((key_node, value_node)) | |
336 return action_node | |
337 | |
338 def _constructDialogue(self, loader, yaml_node): | |
339 npc_name = None | |
340 avatar_path = None | |
341 default_greeting = None | |
342 greetings = [] | |
343 sections = [] | |
344 | |
345 try: | |
346 for key_node, value_node in yaml_node.value: | |
347 key = key_node.value | |
348 if (key == u'NPC_NAME'): | |
349 npc_name = loader.construct_object(value_node) | |
350 elif (key == u'AVATAR_PATH'): | |
351 avatar_path = loader.construct_object(value_node) | |
352 elif (key == u'DEFAULT_GREETING'): | |
353 default_greeting = \ | |
354 self._constructDialogueSection(loader, value_node) | |
355 elif (key == u'GREETINGS'): | |
356 for greeting_node in value_node.value: | |
357 greeting = self._constructRootDialogueSection( | |
358 loader, | |
359 greeting_node | |
360 ) | |
361 greetings.append( | |
362 greeting | |
363 ) | |
364 elif (key == u'SECTIONS'): | |
365 for section_node in value_node.value: | |
366 dialogue_section = self._constructDialogueSection( | |
367 loader, | |
368 section_node | |
369 ) | |
370 sections.append(dialogue_section) | |
371 except (AttributeError, TypeError, ValueError) as e: | |
372 raise DialogueFormatError(e) | |
373 | |
374 dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path, | |
375 default_greeting=default_greeting, | |
376 greetings=greetings, | |
377 sections=sections) | |
378 return dialogue | |
379 | |
380 def _constructRootDialogueSection(self, loader, greeting_node): | |
381 id = None | |
382 text = None | |
383 condition = None | |
384 responses = [] | |
385 actions = [] | |
386 greeting = None | |
387 | |
388 try: | |
389 for key_node, value_node in greeting_node.value: | |
390 key = key_node.value | |
391 if (key == u'ID'): | |
392 id = loader.construct_object(value_node) | |
393 elif (key == u'SAY'): | |
394 text = loader.construct_object(value_node) | |
395 elif (key == u'CONDITION'): | |
396 condition = loader.construct_object(value_node) | |
397 elif (key == u'RESPONSES'): | |
398 for response_node in value_node.value: | |
399 dialogue_response = self._constructDialogueResponse( | |
400 loader, | |
401 response_node | |
402 ) | |
403 responses.append(dialogue_response) | |
404 elif (key == u'ACTIONS'): | |
405 for action_node in value_node.value: | |
406 action = self._constructDialogueAction(loader, | |
407 action_node) | |
408 actions.append(action) | |
409 except (AttributeError, TypeError, ValueError) as e: | |
410 raise DialogueFormatError(e) | |
411 else: | |
412 greeting = DialogueSection(id=id, text=text, | |
413 condition=condition, | |
414 responses=responses, | |
415 actions=actions) | |
416 | |
417 return greeting | |
418 | |
419 def _constructDialogueSection(self, loader, section_node): | |
420 id_ = None | |
421 text = None | |
422 responses = [] | |
423 actions = [] | |
424 dialogue_section = None | |
425 | |
426 try: | |
427 for key_node, value_node in section_node.value: | |
428 key = key_node.value | |
429 if (key == u'ID'): | |
430 id_ = loader.construct_object(value_node) | |
431 elif (key == u'SAY'): | |
432 text = loader.construct_object(value_node) | |
433 elif (key == u'RESPONSES'): | |
434 for response_node in value_node.value: | |
435 dialogue_response = self._constructDialogueResponse( | |
436 loader, | |
437 response_node | |
438 ) | |
439 responses.append(dialogue_response) | |
440 elif (key == u'ACTIONS'): | |
441 for action_node in value_node.value: | |
442 action = self._constructDialogueAction(loader, | |
443 action_node) | |
444 actions.append(action) | |
445 except (AttributeError, TypeError, ValueError) as e: | |
446 raise DialogueFormatError(e) | |
447 else: | |
448 dialogue_section = DialogueSection(id_=id_, text=text, | |
449 responses=responses, | |
450 actions=actions) | |
451 | |
452 return dialogue_section | |
453 | |
454 def _constructDialogueResponse(self, loader, response_node): | |
455 text = None | |
456 next_section_id = None | |
457 actions = [] | |
458 condition = None | |
459 | |
460 try: | |
461 for key_node, value_node in response_node.value: | |
462 key = key_node.value | |
463 if (key == u'REPLY'): | |
464 text = loader.construct_object(value_node) | |
465 elif (key == u'ACTIONS'): | |
466 for action_node in value_node.value: | |
467 action = self._constructDialogueAction(loader, | |
468 action_node) | |
469 actions.append(action) | |
470 elif (key == u'CONDITION'): | |
471 condition = loader.construct_object(value_node) | |
472 elif (key == u'GOTO'): | |
473 next_section_id = loader.construct_object(value_node) | |
474 except (AttributeError, TypeError, ValueError) as e: | |
475 raise DialogueFormatError(e) | |
476 | |
477 dialogue_response = DialogueResponse(text=text, | |
478 next_section_id=next_section_id, | |
479 actions=actions, | |
480 condition=condition) | |
481 return dialogue_response | |
482 | |
483 def _constructDialogueAction(self, loader, action_node): | |
484 mapping = loader.construct_mapping(action_node, deep=True) | |
485 keyword, arguments = mapping.items()[0] | |
486 if (isinstance(arguments, dict)): | |
487 # Got a dictionary of keyword arguments. | |
488 args = () | |
489 kwargs = arguments | |
490 elif (not isinstance(arguments, Sequence) or | |
491 isinstance(arguments, basestring)): | |
492 # Got a single positional argument. | |
493 args = (arguments,) | |
494 kwargs = {} | |
495 elif (not len(arguments) == 2 or not isinstance(arguments[1], dict)): | |
496 # Got a list of positional arguments. | |
497 args = arguments | |
498 kwargs = {} | |
499 else: | |
500 self.logger.error( | |
501 '{0} is an invalid DialogueAction argument'.format(arguments) | |
502 ) | |
503 return None | |
504 | |
505 action_type = DialogueAction.registered_actions.get(keyword) | |
506 if (action_type is None): | |
507 self.logger.error( | |
508 'no DialogueAction with keyword "{0}"'.format(keyword) | |
509 ) | |
510 dialogue_action = None | |
511 else: | |
512 dialogue_action = action_type(*args, **kwargs) | |
513 return dialogue_action | |
514 | |
515 | |
516 class OldYamlDialogueParser(YamlDialogueParser): | |
517 """ | |
518 L{YAMLDialogueParser} that can read and write dialogues in the old | |
519 Techdemo1 dialogue file format. | |
520 | |
521 @warning: This class is deprecated and likely to be removed in a future | |
522 version. | |
523 """ | |
524 logger = logging.getLogger('dialogueparser.OldYamlDialogueParser') | |
525 | |
526 def __init__(self): | |
527 self.response_actions = {} | |
528 | |
529 def load(self, stream): | |
530 dialogue = YamlDialogueParser.load(self, stream) | |
531 # Place all DialogueActions that were in DialogueSections into the | |
532 # DialogueResponse that led to the action's original section. | |
533 for section in dialogue.sections.values(): | |
534 for response in section.responses: | |
535 actions = self.response_actions.get(response.next_section_id) | |
536 if (actions is not None): | |
537 response.actions = actions | |
538 return dialogue | |
539 | |
540 def _constructDialogue(self, loader, yaml_node): | |
541 npc_name = None | |
542 avatar_path = None | |
543 start_section_id = None | |
544 sections = [] | |
545 | |
546 try: | |
547 for key_node, value_node in yaml_node.value: | |
548 key = key_node.value | |
549 if (key == u'NPC'): | |
550 npc_name = loader.construct_object(value_node) | |
551 elif (key == u'AVATAR'): | |
552 avatar_path = loader.construct_object(value_node) | |
553 elif (key == u'START'): | |
554 start_section_id = loader.construct_object(value_node) | |
555 elif (key == u'SECTIONS'): | |
556 for id_node, section_node in value_node.value: | |
557 dialogue_section = self._constructDialogueSection( | |
558 loader, | |
559 id_node, | |
560 section_node | |
561 ) | |
562 sections.append(dialogue_section) | |
563 except (AttributeError, TypeError, ValueError) as e: | |
564 raise DialogueFormatError(e) | |
565 | |
566 dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path, | |
567 start_section_id=start_section_id, | |
568 sections=sections) | |
569 return dialogue | |
570 | |
571 def _constructDialogueSection(self, loader, id_node, section_node): | |
572 id = loader.construct_object(id_node) | |
573 text = None | |
574 responses = [] | |
575 actions = [] | |
576 dialogue_section = None | |
577 | |
578 try: | |
579 for node in section_node.value: | |
580 key_node, value_node = node.value[0] | |
581 key = key_node.value | |
582 if (key == u'say'): | |
583 text = loader.construct_object(value_node) | |
584 elif (key == u'meet'): | |
585 action = self._constructDialogueAction(loader, node) | |
586 actions.append(action) | |
587 elif (key in [u'start_quest', u'complete_quest', u'fail_quest', | |
588 u'restart_quest', u'set_value', | |
589 u'decrease_value', u'increase_value', | |
590 u'give_stuff', u'get_stuff']): | |
591 action = self._constructDialogueAction(loader, node) | |
592 if (id not in self.response_actions.keys()): | |
593 self.response_actions[id] = [] | |
594 self.response_actions[id].append(action) | |
595 elif (key == u'responses'): | |
596 for response_node in value_node.value: | |
597 dialogue_response = self._constructDialogueResponse( | |
598 loader, | |
599 response_node | |
600 ) | |
601 responses.append(dialogue_response) | |
602 except (AttributeError, TypeError, ValueError) as e: | |
603 raise DialogueFormatError(e) | |
604 else: | |
605 dialogue_section = DialogueSection(id=id, text=text, | |
606 responses=responses, | |
607 actions=actions) | |
608 | |
609 return dialogue_section | |
610 | |
611 def _constructDialogueResponse(self, loader, response_node): | |
612 text = None | |
613 next_section_id = None | |
614 actions = [] | |
615 condition = None | |
616 | |
617 try: | |
618 text = loader.construct_object(response_node.value[0]) | |
619 next_section_id = loader.construct_object(response_node.value[1]) | |
620 if (len(response_node.value) == 3): | |
621 condition = loader.construct_object(response_node.value[2]) | |
622 except (AttributeError, TypeError, ValueError) as e: | |
623 raise DialogueFormatError(e) | |
624 | |
625 dialogue_response = DialogueResponse(text=text, | |
626 next_section_id=next_section_id, | |
627 actions=actions, | |
628 condition=condition) | |
629 return dialogue_response | |
630 | |
631 def _constructDialogueAction(self, loader, action_node): | |
632 mapping = loader.construct_mapping(action_node, deep=True) | |
633 keyword, arguments = mapping.items()[0] | |
634 if (keyword == 'get_stuff'): | |
635 # Renamed keyword in new syntax. | |
636 keyword = 'take_stuff' | |
637 elif (keyword == 'set_value'): | |
638 keyword = 'set_quest_value' | |
639 elif (keyword == 'increase_value'): | |
640 keyword = 'increase_quest_value' | |
641 elif (keyword == 'decrease_value'): | |
642 keyword = 'decrease_quest_value' | |
643 if (isinstance(arguments, dict)): | |
644 # Got a dictionary of keyword arguments. | |
645 args = () | |
646 kwargs = arguments | |
647 elif (not isinstance(arguments, Sequence) or | |
648 isinstance(arguments, basestring)): | |
649 # Got a single positional argument. | |
650 args = (arguments,) | |
651 kwargs = {} | |
652 elif (not len(arguments) == 2 or not isinstance(arguments[1], dict)): | |
653 # Got a list of positional arguments. | |
654 args = arguments | |
655 kwargs = {} | |
656 else: | |
657 self.logger.error( | |
658 '{0} is an invalid DialogueAction argument'.format(arguments) | |
659 ) | |
660 return None | |
661 action_type = DialogueAction.registered_actions.get(keyword) | |
662 if (action_type is None): | |
663 self.logger.error( | |
664 'no DialogueAction with keyword "{0}"'.format(keyword) | |
665 ) | |
666 dialogue_action = None | |
667 else: | |
668 dialogue_action = action_type(*args, **kwargs) | |
669 return dialogue_action |