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

Revision 685, 6.7 KB checked in by technomage, 9 years ago (diff)

Patch by Technomage

  • Replaced the test_dialogue.py testsuite with the test_dialogueprocessor.py testsuite and added/updated unittest TestCases? for all public methods of the DialogueProcessor? class.
  • DialogueProcessor? was refactored to be much more liberal about raising exceptions instead of silently handling errors; the code documentation has been updated to describe this new behavior.
  • Refactored exception-handling code in the dialogueparsers.py gamemodel.py modules related to the dialogue engine to deal with the changes to the DialogueProcessor? class.
  • Added a new method to the Dialogue class, getRootSection, which returns the root DialogueSection? for the a particular dialogue; also added some error checking code for inputs to the constructor.
  • Updated the run_tests.py script to import config.py and attempt to read the FIFE Python module path from it, just like run.py.
  • Property svn:eol-style set to native
Line 
1#!/usr/bin/env 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"""
18Provides classes used to contain and organize dialogue data for use within
19in-game dialogues between the player character and NPCs.
20"""
21try:
22    from collections import OrderedDict
23except ImportError:
24    # Python version 2.4-2.6 doesn't have the OrderedDict
25    from scripts.common.ordereddict import OrderedDict
26
27class Dialogue(object):
28    """
29    Represents a complete dialogue and acts as a container for the dialogue
30    data belonging to a particular NPC.
31    """
32    __slots__ = ['npc_name', 'avatar_path', 'start_section_id', 'sections']
33   
34    def __init__(self, npc_name, avatar_path, start_section_id,
35                 dialogue_sections=None):
36        """
37        Initialize a new L{Dialogue} instance.
38       
39        @param npc_name: name displayed for the NPC in the dialogue.
40        @type npc_name: basestring
41        @param avatar_path: path to the image that should be displayed as the
42            NPC's avatar.
43        @type avatar_path: basestring
44        @param start_section_id: ID of the L{DialogueSection} that should be
45            displayed when the dialogue is first initiated.
46        @type start_section_id: basestring
47        @param dialogue_sections: sections of dialogue that make up this
48            L{Dialogue} instance.
49        @type dialogue_sections: list of L{DialogueSections<DialogueSection>}
50        """
51        self.npc_name = npc_name
52        self.avatar_path = avatar_path
53        self.sections = OrderedDict()
54        if (__debug__):
55            section_ids = [section.id for section in dialogue_sections]
56        if (dialogue_sections is not None):
57            # Sanity check: All DialogueResponses should have next_section_id
58            # attributes that refer to valid DialogueSections in the Dialogue.
59            for section in dialogue_sections:
60                if (__debug__):
61                    for response in section.responses:
62                        assert response.next_section_id in section_ids + \
63                            ['end', 'back'], \
64                            '"{0}" is not a valid DialogueSection ID'\
65                            .format(response.next_section_id)
66                self.sections[section.id] = section
67        assert start_section_id in self.sections.keys(), \
68            'start_section_id "{0}" not found in specified sections'\
69            .format(start_section_id)
70        self.start_section_id = start_section_id
71   
72    def __str__(self):
73        """Return the string representation of a L{Dialogue} instance."""
74        string_representation = (
75            ('Dialogue(npc_id={0.npc_name}, avatar_path={0.avatar_path}, '
76             'start_section_id={0.start_section_id}, ...)').format(self)
77        )
78        return string_representation
79   
80    def getRootSection(self):
81        """
82        Return the root DialogueSection.
83       
84        @raise RuntimeError: L{start_section_id} contains an invalid section
85            ID.
86        """
87        try:
88            root_section = self.sections[self.start_section_id]
89        except (IndexError,):
90            raise RuntimeError('the DialogueSection ID "{0}" is not valid or '
91                               'does not refer to a valid DialogueSection')
92        return root_section
93
94
95class DialogueNode(object):
96    """
97    Abstract base class that represents a node or related group of attributes
98    within a Dialogue.
99    """
100    def __init__(self, text, actions=None):
101        """
102        Initialize a new L{DialogueNode} instance.
103       
104        @param text: textual content of the L{DialogueNode}.
105        @type text: basestring
106        @param actions: dialogue actions associated with the L{DialogueNode}.
107        @type actions: list of L{DialogueActions<DialogueAction>}
108        """
109        self.text = text
110        self.actions = actions or []
111
112
113class DialogueSection(DialogueNode):
114    """DialogueNode that represents a distinct section of the dialogue."""
115    __slots__ = ['id', 'text', 'actions', 'responses']
116   
117    def __init__(self, id, text, responses=None, actions=None):
118        """
119        Initialize a new L{DialogueSection} instance.
120       
121        @param id: named used to uniquely identify the L{DialogueSection}
122            within a L{Dialogue}.
123        @type id: basestring
124        @param text: text displayed as the NPC's part of the L{Dialogue}.
125        @type text: basestring
126        @param responses: possible responses that the player can choose from.
127        @type responses: list of L{DialogueResponses<DialogueResponse>}
128        @param actions: dialogue actions that should be executed when the
129            L{DialogueSection} is reached.
130        @type actions: list of L{DialogueActions<DialogueAction>}
131        """
132        DialogueNode.__init__(self, text=text, actions=actions)
133        self.id = id
134        if (responses is not None):
135            self.responses = list(responses)
136
137
138class DialogueResponse(DialogueNode):
139    """
140    L{DialogueNode} that represents one possible player response to a
141    particular L{DialogueSection}.
142    """
143    __slots__ = ['text', 'actions', 'condition', 'next_section_id']
144   
145    def __init__(self, text, next_section_id, actions=None,
146                 condition=None):
147        """
148        Initialize a new L{DialogueResponse} instance.
149       
150        @param text: text displayed as the content of the player's response.
151        @type text: basestring
152        @param next_section_id: ID of the L{DialogueSection} that should be
153            jumped to if this response is chosen by the player.
154        @type next_section_id: basestring
155        @param actions: dialogue actions that should be executed if this
156            response is chosen by the player.
157        @type actions: list of L{DialogueActions<DialogueAction>}
158        @param condition: Python expression that when evaluated determines
159            whether the L{DialogueResponse} should be displayed to the player
160            as a valid response.
161        @type condition: basestring
162        """
163        DialogueNode.__init__(self, text=text, actions=actions)
164        self.condition = condition
165        self.next_section_id = next_section_id
Note: See TracBrowser for help on using the repository browser.