Changeset 698


Ignore:
Timestamp:
01/08/11 01:41:53 (9 years ago)
Author:
technomage
Message:

Patch by Technomage

  • Added support for dynamic/conditional dialogue roots;
  • Updated the Dialogue class to hold RootDialogueSections?, which are

DialogueSections? with a conditional statement used to select them as
the root section of dialogue;

as a "default_root_section" attribute, which is used as the default
root of the dialogue;

  • Moved the getRootSection method from the Dialogue class to the

DialogueParser? class as getRootDialogueSection and updated it to
evaluate the RootDialogueSection? conditionals;

syntax;

  • Updated the YAML dialogue files with the new syntax;
  • Updated the test_dialogueprocessor.py unittests;
Location:
trunk/game
Files:
24 edited

Legend:

Unmodified
Added
Removed
  • trunk/game/dialogue/bouncer.yaml

    r686 r698  
    1616NPC_NAME: Dig 
    1717AVATAR_PATH: gui/portraits/bouncer.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "What do you want?  Can't you see I'm drinking here?" 
    2221    ACTIONS: 
     
    6766        GOTO: parting_shot 
    6867         
     68SECTIONS: 
    6969-   ID: explain_camilla 
    7070    SAY: "Well, it's probably because she gets hit on by greasy layabouts all\ 
  • trunk/game/dialogue/camilla.yaml

    r686 r698  
    1616NPC_NAME: Camilla Niitty 
    1717AVATAR_PATH: gui/portraits/camilla.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Oh, another vagabond.  What do you want?" 
    2221    ACTIONS: 
     
    5655        GOTO: end 
    5756         
     57SECTIONS: 
    5858-   ID: buy_drink 
    5959    SAY: "I'm off duty right now.  Go talk to Jacob." 
  • trunk/game/dialogue/crazy_swede.yaml

    r686 r698  
    1616NPC_NAME: Skwisgaar the Crazy Swede 
    1717AVATAR_PATH: gui/portraits/crazy_swede.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Chop!  Chop!  Chopity Chop-chop?" 
    2221    ACTIONS: 
     
    3332        GOTO: end 
    3433         
     34SECTIONS: 
    3535-   ID: a1 
    3636    SAY: "Ah, you speak the lingo!  How long have you been chopping the good\ 
  • trunk/game/dialogue/drunkard.yaml

    r688 r698  
    1616NPC_NAME: Bart The Drunkard 
    1717AVATAR_PATH: gui/portraits/drunkard.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hey there, back up... no need to gang up on a poor guy!" 
    2221    RESPONSES: 
     
    4241        GOTO: end 
    4342         
     43SECTIONS: 
    4444-   ID: first_impression 
    4545    SAY: "Oh... yeah... sorry.  My vision goes a little funny sometimes.\ 
  • trunk/game/dialogue/farm_boy1.yaml

    r686 r698  
    1616NPC_NAME: Sami 
    1717AVATAR_PATH: gui/portraits/farm_boy_temp.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hi, I'm Sami.  How can I help you?" 
    2221    RESPONSES: 
     
    3938        GOTO: end 
    4039         
     40SECTIONS: 
    4141-   ID: help_beer 
    4242    SAY: "Oh, you better ask the boss about that." 
  • trunk/game/dialogue/farm_boy2.yaml

    r686 r698  
    1616NPC_NAME: Rasmus 
    1717AVATAR_PATH: gui/portraits/farm_boy_temp.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hi, I'm Rasmus.  What can I do for you?" 
    2221    RESPONSES: 
     
    3938        GOTO: end 
    4039         
     40SECTIONS: 
    4141-   ID: help_beer 
    4242    SAY: "Not my place to hand out any stuff.  I just work here.  Talk to\ 
  • trunk/game/dialogue/farmer.yaml

    r686 r698  
    1616NPC_NAME: Farmer Manslow 
    1717AVATAR_PATH: gui/portraits/farmer.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Who the hell are you??" 
    2221    ACTIONS: 
     
    6463        GOTO: leave 
    6564         
     65SECTIONS: 
    6666-   ID: convince_farmer 
    6767    SAY: "Never touch the stuff." 
  • trunk/game/dialogue/fguard.yaml

    r686 r698  
    1616NPC_NAME: Janie 
    1717AVATAR_PATH: gui/portraits/fguard.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Halt, identify yourself!" 
    2221    RESPONSES: 
     
    3736        GOTO: end 
    3837         
     38SECTIONS: 
    3939-   ID: first_impression 
    4040    SAY: "Hey Stranger.  You're new around here, I don't recognize your\ 
  • trunk/game/dialogue/grifter1.yaml

    r686 r698  
    1616NPC_NAME: Cali 
    1717AVATAR_PATH: gui/portraits/grifter_temp.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hi there stranger.  Buy a gal a drink?" 
    2221    ACTIONS: 
     
    4342        GOTO: end 
    4443         
     44SECTIONS: 
    4545-   ID: help_beer 
    4646    SAY: "Trying to horn in on old Jacob here, huh?  I wouldn't if I were you." 
  • trunk/game/dialogue/grifter2.yaml

    r686 r698  
    1616NPC_NAME: Kandi 
    1717AVATAR_PATH: gui/portraits/grifter_temp.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hi there stranger.  Buy a gal a drink?" 
    2221    ACTIONS: 
     
    4342        GOTO: end 
    4443         
     44SECTIONS: 
    4545-   ID: help_beer 
    4646    SAY: "Well, good luck with that.  But I'm a little too close to Jake to\ 
  • trunk/game/dialogue/innkeeper.yaml

    r686 r698  
    1616NPC_NAME: Jacob 
    1717AVATAR_PATH: gui/portraits/innkeeper.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hi there, Stranger!  Welcome to Jacob's.  How can I help you?" 
    2221    ACTIONS: 
     
    5049        GOTO: end 
    5150         
     51SECTIONS: 
    5252-   ID: buy_drink 
    5353    SAY: "Doesn't look like you got much to trade for any of my premium booze.\ 
  • trunk/game/dialogue/leader.yaml

    r686 r698  
    1616NPC_NAME: Kimmo Niitty 
    1717AVATAR_PATH: gui/portraits/leader.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "What do you want?" 
    2221    ACTIONS: 
     
    6665        GOTO: end 
    6766         
     67SECTIONS: 
    6868-   ID: help_alcohol 
    6969    SAY: "Well, if you've got enough to pay him, Jacob can set you up, over at\ 
  • trunk/game/dialogue/ma.yaml

    r686 r698  
    1616NPC_NAME: Ma Niitty 
    1717AVATAR_PATH: gui/portraits/ma.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Why hello there... I don't recognize your face..." 
    2221    ACTIONS: 
     
    4948        GOTO: end 
    5049         
     50SECTIONS: 
    5151-   ID: dead_pekko 
    5252    SAY: "Oh, don't be silly.  I'm sure he is just off in the wilderness again" 
  • trunk/game/dialogue/mall_template.yaml

    r686 r698  
    1616NPC_NAME: Anyone 
    1717AVATAR_PATH: gui/portraits/npc.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hello.  How can I help you?" 
    2221    RESPONSES: 
     
    4039        GOTO: end 
    4140         
     41SECTIONS: 
    4242-   ID: help_beer 
    4343    SAY: "I am always happy to give intimate details of our home to strangers,\ 
  • trunk/game/dialogue/old_man.yaml

    r686 r698  
    1616NPC_NAME: Old Man 
    1717AVATAR_PATH: gui/portraits/crazy_swede.png 
    18 START_SECTION: main_dialogue 
    19 SECTIONS: 
    20 -   ID: main_dialogue 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialogue 
    2120    SAY: "The old man looks up at you from his chair with a dazed look upon\ 
    2221       \ his face.\n\"...Lucy?\"" 
    2322    ACTIONS: 
    2423    -   meet: 
    25         - old_man 
     24        -   old_man 
    2625    RESPONSES: 
    2726    -   REPLY: "Who's Lucy?" 
    2827        GOTO: help_find_lucy 
    2928         
    30     -   REPLY: "I found your ring." 
    31         CONDITION: "quest.hasActiveQuest('memories') and\ 
    32            \ pc.inventory.hasItem('RustyWeddingRing')" 
    33         ACTIONS: 
    34         -   give_stuff: 
    35             - RustyWeddingRing 
    36         -   complete_quest: 
    37             - memories 
    38         GOTO: good_memories 
    39          
    4029    -   REPLY: "I don't have time for your delusions old man!" 
    4130        GOTO: parting_shot 
    4231         
     32START_SECTIONS: 
     33-   ID: found_ring 
     34    SAY: "\"Lucy... Where is my Lucy?\"" 
     35    CONDITION: "quest.hasActiveQuest('memories') and\ 
     36       \ pc.inventory.hasItem('RustyWeddingRing')" 
     37    RESPONSES: 
     38    -   REPLY: "I found your ring." 
     39        ACTIONS: 
     40        -   give_stuff: 
     41            -   RustyWeddingRing 
     42        -   complete_quest: 
     43            -   memories 
     44        GOTO: good_memories 
     45SECTIONS: 
    4346-   ID: good_memories 
    4447    SAY: "An intense expression of joy spreads across the old man's face as\ 
     
    6972        ACTIONS: 
    7073        -   start_quest: 
    71             - memories 
     74            -   memories 
    7275        GOTO: parting_shot 
    7376         
  • trunk/game/dialogue/quartermaster.yaml

    r686 r698  
    1616NPC_NAME: Helja 
    1717AVATAR_PATH: gui/portraits/quartermaster.jpg 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hello, there!  A new addition to the labor pool, I see." 
    2221    ACTIONS: 
     
    5251        GOTO: end 
    5352         
     53SECTIONS: 
    5454-   ID: expound_quartermaster 
    5555    SAY: "I am the like the supply sergeant.  I am in charge of all the\ 
  • trunk/game/dialogue/quest_sample.yaml

    r688 r698  
    1616NPC_NAME: NPC 
    1717AVATAR_PATH: gui/portraits/npc.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Things are tough around here, let me tell you our problems" 
    2221    RESPONSES: 
     
    5756        GOTO: end 
    5857         
     58SECTIONS: 
    5959-   ID: listen_more 
    6060    SAY: "Raiders stole our cattle, our well was poisoned, and the beer is all\ 
  • trunk/game/dialogue/sample.yaml

    r688 r698  
    1616NPC_NAME: Friendly NPC 
    1717AVATAR_PATH: gui/portraits/npc.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Things are tough around here, let me tell you our problems" 
    2221    RESPONSES: 
     
    2928        GOTO: end 
    3029         
     30SECTIONS: 
    3131-   ID: listen_more 
    3232    SAY: "Raiders stole our cattle, our well was poisoned, and the beer is all\ 
  • trunk/game/dialogue/snowshoveler.yaml

    r686 r698  
    1616NPC_NAME: Matti 
    1717AVATAR_PATH: gui/portraits/snowshoveler.png 
    18 START_SECTION: opening_dialog 
    19 SECTIONS: 
    20 -   ID: opening_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: opening_dialog 
    2120    SAY: "Good to talk to someone, Matti could use a break" 
    2221    ACTIONS: 
     
    4746        GOTO: main_dialog 
    4847         
     48SECTIONS: 
    4949-   ID: main_dialog 
    5050    SAY: "Matti shovels the snow." 
  • trunk/game/dialogue/synnove.yaml

    r686 r698  
    1616NPC_NAME: Synnove Niitty 
    1717AVATAR_PATH: gui/portraits/synnove.png 
    18 START_SECTION: main_dialog 
    19 SECTIONS: 
    20 -   ID: main_dialog 
     18DEFAULT_ROOT_SECTION: 
     19    ID: main_dialog 
    2120    SAY: "Hi there!  I don't recognize you.  Are you new here?" 
    2221    RESPONSES: 
     
    4847        GOTO: end 
    4948         
     49SECTIONS: 
    5050-   ID: meeting 
    5151    SAY: "My name's Synnove.  I live here." 
  • trunk/game/scripts/dialogue.py

    r690 r698  
    3030    data belonging to a particular NPC. 
    3131    """ 
    32     __slots__ = ['npc_name', 'avatar_path', 'start_section_id', 'sections'] 
     32    __slots__ = ['npc_name', 'avatar_path', 'default_root_section', 
     33                 'root_sections', 'sections'] 
    3334     
    34     def __init__(self, npc_name, avatar_path, start_section_id, 
    35                  sections=None): 
     35    def __init__(self, npc_name, avatar_path, default_root_section, 
     36                 root_sections=None, sections=None): 
    3637        """ 
    3738        Initialize a new L{Dialogue} instance. 
     
    4243            NPC's avatar. 
    4344        @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 
     45        @param default_root_section: section of dialogue that should be 
     46            displayed when the dialogue is first initiated and no other start 
     47            sections are available. 
     48        @type default_root_section: L{DialogueSection} 
     49        @param root_sections: sections of dialogue defining the conditions 
     50            under which each should be displayed when the dialogue is first 
     51            initiated. 
     52        @type root_section_references: list of  
     53            L{RootDialogueSections<RootDialogueSection>} 
    4754        @param sections: sections of dialogue that make up this 
    4855            L{Dialogue} instance. 
     
    5158        self.npc_name = npc_name 
    5259        self.avatar_path = avatar_path 
     60        self.default_root_section = default_root_section 
     61        self.root_sections = root_sections 
    5362        self.sections = OrderedDict() 
     63        all_sections = sections + [default_root_section] + root_sections 
    5464        if (__debug__): 
    55             section_ids = [section.id for section in sections] 
    56         if (sections is not None): 
     65            section_ids = [section.id for section in all_sections] 
     66        for section in all_sections: 
    5767            # Sanity check: All DialogueResponses should have next_section_id 
    5868            # attributes that refer to valid DialogueSections in the Dialogue. 
    59             for section in 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 
     69            if (__debug__): 
     70                for response in section.responses: 
     71                    assert response.next_section_id in section_ids + \ 
     72                        ['end', 'back'], ('"{0}" does not refer to a '  
     73                                          'DialogueSection in this Dialogue')\ 
     74                        .format(response.next_section_id) 
     75            self.sections[section.id] = section 
    7176     
    7277    def __str__(self): 
    7378        """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         ) 
     79        string_representation = 'Dialogue(npc_id={0.npc_name})'.format(self) 
    7880        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 
    9381 
    9482 
     
    113101class DialogueSection(DialogueNode): 
    114102    """DialogueNode that represents a distinct section of the dialogue.""" 
    115     __slots__ = ['id', 'text', 'actions', 'responses'] 
     103    __slots__ = ['id', 'text', 'responses', 'actions'] 
    116104     
    117     def __init__(self, id, text, responses=None, actions=None): 
     105    def __init__(self, id_, text, responses=None, actions=None): 
    118106        """ 
    119107        Initialize a new L{DialogueSection} instance. 
    120108         
    121         @param id: named used to uniquely identify the L{DialogueSection} 
     109        @param id_: named used to uniquely identify the L{DialogueSection} 
    122110            within a L{Dialogue}. 
    123         @type id: basestring 
     111        @type id_: basestring 
    124112        @param text: text displayed as the NPC's part of the L{Dialogue}. 
    125113        @type text: basestring 
     
    130118        @type actions: list of L{DialogueActions<DialogueAction>} 
    131119        """ 
     120        print(id_, text, responses, actions) 
    132121        DialogueNode.__init__(self, text=text, actions=actions) 
    133         self.id = id 
     122        self.id = id_ 
    134123        if (responses is not None): 
    135124            self.responses = list(responses) 
     125 
     126 
     127class RootDialogueSection(DialogueSection): 
     128    """ 
     129    Represents a root section of dialogue in a L{Dialogue} along with the 
     130    conditional statement used to determine the whether this section should be 
     131    displayed first upon dialogue initiation. 
     132     
     133    @ivar id: Name used to uniquely identify the L{DialogueSection} to which 
     134        the L{DialogueRootSectionReference} points. 
     135    @type id: basestring 
     136    @ivar condition: Boolean Python expression used to determine if the 
     137        L{DialogueSection} referenced is a valid starting section. 
     138    @type condition: basestring 
     139    """ 
     140    __slots__ = ['id', 'condition', 'text', 'actions', 'responses'] 
     141     
     142    def __init__(self, id_, condition, text, responses=None, actions=None): 
     143        """ 
     144        Initialize a new L{RootDialogueSection} instance. 
     145         
     146        @param id_: named used to uniquely identify the L{DialogueSection} 
     147            within a L{Dialogue}. 
     148        @type id_: basestring 
     149        @param condition: Boolean Python expression used to determine if this 
     150            root dialogue section should be displayed. 
     151        @type condition: basestring 
     152        @param text: text displayed as the NPC's part of the L{Dialogue}. 
     153        @type text: basestring 
     154        @param responses: possible responses that the player can choose from. 
     155        @type responses: list of L{DialogueResponses<DialogueResponse>} 
     156        @param actions: dialogue actions that should be executed when the 
     157            L{DialogueSection} is reached. 
     158        @type actions: list of L{DialogueActions<DialogueAction>} 
     159        """ 
     160        DialogueSection.__init__(self, id_=id_, text=text, responses=responses, 
     161                                 actions=actions) 
     162        self.condition = condition 
    136163 
    137164 
     
    143170    __slots__ = ['text', 'actions', 'condition', 'next_section_id'] 
    144171     
    145     def __init__(self, text, next_section_id, actions=None, 
    146                  condition=None): 
     172    def __init__(self, text, next_section_id, actions=None, condition=None): 
    147173        """ 
    148174        Initialize a new L{DialogueResponse} instance. 
  • trunk/game/scripts/dialogueparsers.py

    r690 r698  
    4444 
    4545from scripts import COPYRIGHT_HEADER 
    46 from scripts.dialogue import Dialogue, DialogueSection, DialogueResponse 
     46from scripts.dialogue import (Dialogue, DialogueSection, DialogueResponse, 
     47    RootDialogueSection) 
    4748from scripts.dialogueactions import DialogueAction 
    4849 
     
    191192        dialogue_dict['NPC_NAME'] = dialogue.npc_name 
    192193        dialogue_dict['AVATAR_PATH'] = dialogue.avatar_path 
    193         dialogue_dict['START_SECTION'] = dialogue.start_section_id 
     194        dialogue_dict['DEFAULT_ROOT_SECTION'] = \ 
     195            self._representDialogueSection(dumper, 
     196                                           dialogue.default_root_section) 
    194197        # NOTE Technomage 2010-11-16: Dialogue stores its sections in an 
    195198        #     OrderedDict, so a round-trip load, dump, and load will preserve 
    196199        #     the order of DialogueSections. 
    197         sections_list_node = dumper.represent_list([]) 
    198         sections_list = sections_list_node.value 
    199         for section in dialogue.sections.values(): 
    200             section_node = self._representDialogueSection(dumper, section) 
    201             sections_list.append(section_node) 
    202         dialogue_dict['SECTIONS'] = sections_list_node 
     200        if (len(dialogue.root_sections) > 0): 
     201            root_sections_list_node = dumper.represent_list([]) 
     202            root_sections_list = root_sections_list_node.value 
     203            for root_section in dialogue.root_sections: 
     204                root_section_node = \ 
     205                    self._representRootDialogueSection(dumper, root_section) 
     206                root_sections_list.append(root_section_node) 
     207            dialogue_dict['ROOT_SECTIONS'] = root_sections_list_node 
     208        if (len(dialogue.setions) > 0): 
     209            sections_list_node = dumper.represent_list([]) 
     210            sections_list = sections_list_node.value 
     211            for section in dialogue.sections.values(): 
     212                section_node = self._representDialogueSection(dumper, section) 
     213                sections_list.append(section_node) 
     214            dialogue_dict['SECTIONS'] = sections_list_node 
    203215         
    204216        for key, value in dialogue_dict.items(): 
     
    213225            dialogue_node.value.append((key_node, value_node)) 
    214226        return dialogue_node 
     227     
     228    def _representRootDialogueSection(self, dumper, root_section): 
     229        root_section_node = dumper.represent_dict({}) 
     230        root_section_dict = OrderedDict() 
     231        root_section_dict['ID'] = root_section.id 
     232        root_section_dict['CONDITION'] = dumper.represent_scalar( 
     233            'tag:yaml.org,2002:str', 
     234            root_section.condition, 
     235            style='"' 
     236        ) 
     237        for key, value in root_section_dict.items(): 
     238            if (isinstance(key, yaml.Node)): 
     239                key_node = key 
     240            else: 
     241                key_node = dumper.represent_data(key) 
     242            if (isinstance(value, yaml.Node)): 
     243                value_node = value 
     244            else: 
     245                value_node = dumper.represent_data(value) 
     246            root_section_node.value.append((key_node, value_node)) 
     247        return root_section_node 
    215248     
    216249    def _representDialogueSection(self, dumper, dialogue_section): 
     
    313346        npc_name = None 
    314347        avatar_path = None 
    315         start_section_id = None 
     348        default_root_section = None 
     349        root_sections = [] 
    316350        sections = [] 
    317351         
     
    323357                elif (key == u'AVATAR_PATH'): 
    324358                    avatar_path = loader.construct_object(value_node) 
    325                 elif (key == u'START_SECTION'): 
    326                     start_section_id = loader.construct_object(value_node) 
     359                elif (key == u'DEFAULT_ROOT_SECTION'): 
     360                    default_root_section = \ 
     361                        self._constructDialogueSection(loader, value_node) 
     362                elif (key == u'ROOT_SECTIONS'): 
     363                    for root_section_node in value_node.value: 
     364                        root_section = self._constructRootDialogueSection( 
     365                                loader, 
     366                                root_section_node 
     367                        ) 
     368                        root_sections.append( 
     369                            root_section 
     370                        ) 
    327371                elif (key == u'SECTIONS'): 
    328372                    for section_node in value_node.value: 
     
    336380         
    337381        dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path, 
    338                             start_section_id=start_section_id, 
     382                            default_root_section=default_root_section, 
     383                            root_sections=root_sections, 
    339384                            sections=sections) 
    340385        return dialogue 
    341386     
     387    def _constructRootDialogueSection(self, loader, root_section_node): 
     388        id = None 
     389        text = None 
     390        condition = None 
     391        responses = [] 
     392        actions = [] 
     393        root_section = None 
     394         
     395        try: 
     396            for key_node, value_node in root_section_node.value: 
     397                key = key_node.value 
     398                if (key == u'ID'): 
     399                    id = loader.construct_object(value_node) 
     400                elif (key == u'SAY'): 
     401                    text = loader.construct_object(value_node) 
     402                elif (key == u'CONDITION'): 
     403                    condition = loader.construct_object(value_node) 
     404                elif (key == u'RESPONSES'): 
     405                    for response_node in value_node.value: 
     406                        dialogue_response = self._constructDialogueResponse( 
     407                            loader, 
     408                            response_node 
     409                        ) 
     410                        responses.append(dialogue_response) 
     411                elif (key == u'ACTIONS'): 
     412                    for action_node in value_node.value: 
     413                        action = self._constructDialogueAction(loader, 
     414                                                             action_node) 
     415                        actions.append(action) 
     416        except (AttributeError, TypeError, ValueError) as e: 
     417            raise DialogueFormatError(e) 
     418        else: 
     419            root_section = DialogueSection(id=id, text=text, 
     420                                           condition=condition, 
     421                                           responses=responses, 
     422                                           actions=actions) 
     423         
     424        return root_section 
     425     
    342426    def _constructDialogueSection(self, loader, section_node): 
    343         id = None 
     427        id_ = None 
    344428        text = None 
    345429        responses = [] 
     
    351435                key = key_node.value 
    352436                if (key == u'ID'): 
    353                     id = loader.construct_object(value_node) 
     437                    id_ = loader.construct_object(value_node) 
    354438                elif (key == u'SAY'): 
    355439                    text = loader.construct_object(value_node) 
     
    369453            raise DialogueFormatError(e) 
    370454        else: 
    371             dialogue_section = DialogueSection(id=id, text=text, 
     455            dialogue_section = DialogueSection(id_=id_, text=text, 
    372456                                               responses=responses, 
    373457                                               actions=actions) 
  • trunk/game/scripts/dialogueprocessor.py

    r690 r698  
    149149        self._in_dialogue = False 
    150150     
     151    def getRootDialogueSection(self): 
     152        """ 
     153        Evaluate the L{RootDialogueSections<RootDialogueSection>} conditions 
     154        and return the valid L{DialogueSection} which should be displayed 
     155        first. 
     156         
     157        @return: Valid root dialogue section. 
     158        @rtype: L{DialogueSection} 
     159        """ 
     160        dialogue = self.dialogue 
     161        root_dialogue_section = None 
     162        for root_section in dialogue.root_sections: 
     163            if (eval(root_section.condition, self.game_state)): 
     164                root_dialogue_section = root_section 
     165        if (root_dialogue_section is None): 
     166            root_dialogue_section = dialogue.default_root_section 
     167         
     168        return root_dialogue_section 
     169     
    151170    def initiateDialogue(self): 
    152171        """ 
    153         Prepare the L{DialogueProcessor} to process the L{Dialogue} by pushing the 
    154         starting L{DialogueSection} onto the L{dialogue_section_stack}. 
     172        Prepare the L{DialogueProcessor} to process the L{Dialogue} by pushing 
     173        the starting L{DialogueSection} onto the L{dialogue_section_stack}. 
    155174         
    156175        @raise TypeError: Unable to determine the root L{DialogueSection} 
    157176            defined by the L{Dialogue}. 
    158177        """ 
     178        if (self.in_dialogue): 
     179            self.endDialogue() 
    159180        dialogue = self.dialogue 
    160181        try: 
    161             root_dialogue_section = dialogue.getRootSection() 
     182            root_dialogue_section = self.getRootDialogueSection() 
    162183        except (RuntimeError,) as error: 
    163184            self._logger.error(str(error)) 
    164             raise TypeError(('unable to determine start DialogueSection for ' 
     185            raise TypeError(('unable to determine root DialogueSection for ' 
    165186                             '{0}').format(dialogue)) 
    166187        else: 
  • trunk/game/tests/test_dialogueprocessor.py

    r690 r698  
    2727#    data structures that don't require much testing of their own so I feel 
    2828#    that it isn't a mistake to use them. 
    29 from scripts.dialogue import Dialogue, DialogueSection, DialogueResponse 
    30  
    31  
    32 class NotInDict(object): 
    33     """ 
    34     Object to return when a key could not be found in a dictionary and 
    35     None is not usable (e.g. because None can appear as a value in the dict). 
    36     """ 
    37     __slots__ = [] 
    38      
    39     def __init__(self): 
    40         raise TypeError('NotInDict cannot be instantiated.') 
    41  
    42  
    43 class MockPlayerCharacter(object): 
    44     pass 
    45  
    46  
    47 class MockQuestEngine(object): 
    48     pass 
    49  
     29from scripts.dialogue import (Dialogue, DialogueSection, DialogueResponse, 
     30    RootDialogueSection) 
    5031 
    5132class MockDialogueAction(object): 
     
    7253        object_dict = {} 
    7354        for key in state.keys(): 
    74             actual_value = getattr(object_, key, NotInDict) 
    75             if (actual_value is not NotInDict): 
     55            if (hasattr(object_, key)): 
     56                actual_value = getattr(object_, key) 
    7657                object_dict[key] = actual_value 
    7758        self.assertDictContainsSubset(state, object_dict) 
     
    8263            npc_name='Mr. NPC', 
    8364            avatar_path='/some/path', 
    84             start_section_id='main_section', 
    85             sections=[ 
    86                 DialogueSection( 
    87                     id='main_section', 
    88                     text='This is the main dialogue section.', 
    89                     actions=[ 
    90                         MockDialogueAction('foo'), 
    91                     ], 
     65            default_root_section=DialogueSection( 
     66                id_='root_section', 
     67                text='This is the root dialogue section.', 
     68                actions=[ 
     69                    MockDialogueAction('foo'), 
     70                ], 
     71                responses=[ 
     72                    DialogueResponse( 
     73                        text='A response.', 
     74                        next_section_id='another_section', 
     75                    ), 
     76                    DialogueResponse( 
     77                        text='A conditional response evaluated to True.', 
     78                        condition='True', 
     79                        actions=[ 
     80                            MockDialogueAction('foo'), 
     81                        ], 
     82                        next_section_id='another_section', 
     83                    ), 
     84                    DialogueResponse( 
     85                        text='A conditional response evaluated to False.', 
     86                        condition='False', 
     87                        next_section_id='another_section', 
     88                    ), 
     89                    DialogueResponse( 
     90                        text='A response that ends the dialogue.', 
     91                        next_section_id='end', 
     92                    ), 
     93                ], 
     94            ), 
     95            root_sections=[ 
     96                RootDialogueSection( 
     97                    id_='alternative_root_section', 
     98                    condition='use_alternative_root is True', 
     99                    text='This is an alternate root section.', 
    92100                    responses=[ 
    93101                        DialogueResponse( 
    94                             text='A response.', 
    95                             next_section_id='another_section', 
    96                         ), 
    97                         DialogueResponse( 
    98                             text='A conditional response evaluated to True.', 
    99                             condition='True', 
    100                             actions=[ 
    101                                 MockDialogueAction('foo'), 
    102                             ], 
    103                             next_section_id='another_section', 
    104                         ), 
    105                         DialogueResponse( 
    106                             text='A conditional response evaluated to False.', 
    107                             condition='False', 
    108                             next_section_id='another_section', 
    109                         ), 
    110                         DialogueResponse( 
    111                             text='A response that ends the dialogue.', 
     102                            text='End dialogue.', 
    112103                            next_section_id='end', 
    113104                        ), 
    114105                    ], 
    115106                ), 
     107            ], 
     108            sections=[ 
    116109                DialogueSection( 
    117                     id='another_section', 
     110                    id_='another_section', 
    118111                    text='This is another dialogue section.', 
    119112                    responses=[ 
     
    126119            ] 
    127120        ) 
    128         self.game_state = {'pc': MockPlayerCharacter(), 
    129                            'quest': MockQuestEngine()} 
    130         self.dialogue_processor = DialogueProcessor(self.dialogue, 
    131                                                     self.game_state) 
     121        self.game_state = {'use_alternative_root': False} 
    132122 
    133123 
    134124class TestInitiateDialogue(TestDialogueProcessor): 
    135125    """Tests of the L{DialogueProcessor.initiateDialogue} method.""" 
     126    def setUp(self): 
     127        TestDialogueProcessor.setUp(self) 
     128        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     129                                                    self.game_state) 
     130     
    136131    def testInitiateDialogue_setsState(self): 
    137132        """Test initiateDialogue correctly sets DialogueProcessor state""" 
     
    139134        dialogue_processor.initiateDialogue() 
    140135         
    141         # Root dialogue section should have been pushed onto the stack. 
    142         root_dialogue_section = \ 
    143             self.dialogue.sections[self.dialogue.start_section_id] 
     136        # Default root dialogue section should have been pushed onto the stack. 
     137        root_dialogue_section = self.dialogue.default_root_section 
    144138        self.assertStateEqual(dialogue_processor, in_dialogue=True, 
    145139                              dialogue=self.dialogue, 
     
    149143class TestEndDialogue(TestDialogueProcessor): 
    150144    """Tests of the L{DialogueProcessor.endDialogue} method.""" 
     145    def setUp(self): 
     146        TestDialogueProcessor.setUp(self) 
     147        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     148                                                    self.game_state) 
     149     
    151150    def testEndDialogue_resetsState(self): 
    152151        """Test endDialogue correctly resets DialogueProcessor state""" 
     
    173172    def setUp(self): 
    174173        TestDialogueProcessor.setUp(self) 
    175         self.dialogue_processor.initiateDialogue() 
    176         self.dialogue_action = self.dialogue.getRootSection().actions[0] 
     174        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     175                                                    self.game_state) 
     176        self.dialogue_processor.initiateDialogue() 
     177        self.dialogue_action = \ 
     178            self.dialogue.default_root_section.actions[0] 
    177179     
    178180    def testRunsDialogueActions(self): 
     
    200202        self.assertItemsEqual(responses, valid_responses) 
    201203 
     204 
     205class TestGetRootDialogueSection(TestDialogueProcessor): 
     206    """Tests of the L{DialogueProcessor.getRootDialogueSection} method.""" 
     207    def setUp(self): 
     208        TestDialogueProcessor.setUp(self) 
     209        self.dialogue_processor = DialogueProcessor( 
     210            self.dialogue, 
     211            {'use_alternative_root': True} 
     212        ) 
     213        self.dialogue_processor.initiateDialogue() 
     214     
     215    def testReturnsCorrectDialogueSection(self): 
     216        """getRootDialogueSection returns first section with true condition""" 
     217        dialogue_processor = self.dialogue_processor 
     218        dialogue = self.dialogue 
     219        root_dialogue_section = dialogue_processor.getRootDialogueSection() 
     220        expected_dialogue_section = dialogue.root_sections[0] 
     221        self.assertEqual(root_dialogue_section, expected_dialogue_section) 
     222 
     223 
    202224class TestGetCurrentDialogueSection(TestDialogueProcessor): 
    203225    """Tests of the L{DialogueProcessor.getCurrentDialogueSection} method.""" 
    204226    def setUp(self): 
    205227        TestDialogueProcessor.setUp(self) 
     228        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     229                                                    self.game_state) 
    206230        self.dialogue_processor.initiateDialogue() 
    207231     
     
    209233        """Test getCurrentDialogueSection returns section at top of stack""" 
    210234        dialogue_processor = self.dialogue_processor 
    211         expected_dialogue_section = \ 
    212             self.dialogue.sections[self.dialogue.start_section_id] 
     235        expected_dialogue_section = self.dialogue.default_root_section 
    213236        actual_dialogue_section = \ 
    214237            dialogue_processor.getCurrentDialogueSection() 
     
    220243    def setUp(self): 
    221244        TestDialogueProcessor.setUp(self) 
     245        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     246                                                    self.game_state) 
    222247        self.dialogue_processor.initiateDialogue() 
    223248        self.dialogue_section = DialogueSection( 
    224             id='some_section', 
     249            id_='some_section', 
    225250            text='Test dialogue section.', 
    226251            actions=[ 
     
    258283    def setUp(self): 
    259284        TestDialogueProcessor.setUp(self) 
     285        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     286                                                    self.game_state) 
    260287        self.dialogue_processor.initiateDialogue() 
    261288     
     
    270297        for response in valid_responses: 
    271298            if (response.condition is not None): 
    272                 result = eval(response.condition, self.game_state, {}) 
     299                result = eval(response.condition, {}, {}) 
    273300                self.assertTrue(result) 
    274301        responses = dialogue_processor.continueDialogue() 
     
    280307    def setUp(self): 
    281308        TestDialogueProcessor.setUp(self) 
    282         self.response = self.dialogue.getRootSection().responses[1] 
    283         self.ending_response = self.dialogue.getRootSection().responses[3] 
     309        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     310                                                    self.game_state) 
     311        self.response = self.dialogue.default_root_section.responses[1] 
     312        self.ending_response = \ 
     313            self.dialogue.default_root_section.responses[3] 
    284314     
    285315    def testRaisesExceptionWhenNotInitiated(self): 
     
    292322     
    293323    def testExecutesDialogueActions(self): 
    294         """Test reply correctly executes DialogueActions in DialogueResponse""" 
     324        """reply correctly executes DialogueActions in a DialogueResponse""" 
    295325        dialogue_processor = self.dialogue_processor 
    296326        dialogue_processor.initiateDialogue() 
     
    303333     
    304334    def testJumpsToCorrectSection(self): 
    305         """Test reply pushes section specified by response onto stack""" 
     335        """reply pushes section specified by response onto stack""" 
    306336        dialogue_processor = self.dialogue_processor 
    307337        dialogue_processor.initiateDialogue() 
     
    311341                      self.dialogue.sections.keys()) 
    312342        dialogue_processor.reply(self.response) 
    313         root_section = self.dialogue.getRootSection() 
     343        root_section = self.dialogue.default_root_section 
    314344        next_section = self.dialogue.sections[self.response.next_section_id] 
    315345        self.assertStateEqual( 
Note: See TracChangeset for help on using the changeset viewer.