Changeset 21


Ignore:
Timestamp:
03/14/09 00:50:55 (10 years ago)
Author:
icelus_parpg
Message:

Apply ActionStack? patch from tie (ticket #1).

  • queue actions for hero to perform (e.g. move to target, act)
  • unit tests for ActionStack?
  • new run_tests script
  • kick added as an always present to allow queueing to be seen.
Location:
trunk/game
Files:
2 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/game/scripts/agents/agent.py

    r19 r21  
    11import fife 
    22from scripts.common.common import ProgrammingError 
     3 
     4class ActionStack (object): 
     5        """  
     6    Class, which implements queueing of various user actions. 
     7    Each entry in the ActoinStack is a 6-item tuple consisting of 
     8    (action_func, action_func_args, action_func_kw_args, 
     9    (success_func, success_func_args, success_func_kw_args).  
     10 
     11    Items are processed from index 0 upwards. New actions 
     12    are appended to the list. Successful actions are popped from the 
     13    bottom of the list. If the success function of the current 
     14    action returns True, the current action is popped out, and the 
     15    next action is processed. 
     16 
     17    IMPORTANT: To avoid the danger of endlessly pushing actions to the  
     18    stack, it is automatically cleared when an action is added while  
     19    the stack is in a running state. 
     20 
     21    Example: 
     22    To define something like "Kick him while he's down", you coud do: 
     23    .add_action (kick, (him,), isDown, (him,)) 
     24 
     25    """ 
     26        def __init__ (self): 
     27                 
     28                # This is the actual list that stores the entries 
     29                self.action_list = [] 
     30                # A flag to track if the stack was ran after the last item was added 
     31                self.running = False 
     32 
     33        def add_kw_args_action (self,  
     34                        action_func, action_args = (), action_kw_args = {}, 
     35                        success_func = None, success_args = (),success_kw_args = {}): 
     36                """ 
     37                Appends an action entry to the action_list. Supports both args and  
     38                kwargs. If a previous batch of actions was inserted, and then ran,  
     39                this will first empty the stack before adding the new entry. 
     40                """ 
     41                # Basic checks to see if the action and success funcitons are callable  
     42                if not callable (action_func): 
     43                        raise ValueError ("%s is not callable!" % action_func) 
     44                if success_func and not callable(success_func): 
     45                        raise ValueError ("%s is not callable!" % success_func) 
     46                if self.running: 
     47                        # We have a running queue; clean it up before adding anything new 
     48                        self.clear() 
     49                        self.running = False 
     50 
     51                self.action_list.append ( (action_func, action_args, action_kw_args,  
     52                                           success_func, success_args, success_kw_args)) 
     53 
     54        def add_action (self, action_func, action_args = (), 
     55                        success_func = None, success_args = ()): 
     56                """ 
     57                Appends an action entry to the action_list. Shortcut to  
     58                add_kw_args_action() without kwargs. Only supports positinal arguments  
     59                for the action_func/success_func 
     60                """ 
     61                self.add_kw_args_action (action_func, action_args, {},  
     62                                        success_func, success_args , {}) 
     63 
     64        def remove_current_action (self): 
     65                """Removes an action from the bottom of the list""" 
     66                if self.action_list: 
     67                        self.action_list.pop(0) 
     68 
     69        def clear (self): 
     70                """Removes all actions from the stack""" 
     71                self.action_list = [] 
     72         
     73        def perform_action (self, action_item): 
     74                """Runs the provided action_item. It is a tuple following the same 
     75                format as the items in self.action_list """ 
     76                a_func, a_args, a_kw_args = action_item[:3] 
     77                a_func (*a_args, **a_kw_args) 
     78                return True 
     79 
     80        def run (self): 
     81                """ 
     82                Runs an action from the queue. 
     83 
     84                If there is no success function, just executes the current action  
     85                and removes it from the queue. If the success function evaluates 
     86                to True, removes the current action and proceeds with the next one.  
     87                If the success function evaluates to False, only performs  
     88                the current action. 
     89                 
     90                Returns True if some action was executed, False otherwise. 
     91                """ 
     92 
     93                if not self.action_list: 
     94                        return False 
     95                # We set the running flag 
     96                self.running = True 
     97                 
     98                # Loop that will go over the aciton_list and exit after performing an  
     99                # action. Note that the loop iterates over a copy of the action_list 
     100                for action in self.action_list[:]: 
     101                        # Load the current entry success function details 
     102                        s_func, s_args, s_kw_args = self.action_list[0][3:6] 
     103 
     104                        if (not s_func): 
     105                                # No success function defined - meaning that the  
     106                                # action will be popped form the queue and executed once. 
     107                                return self.perform_action(self.action_list.pop(0)) 
     108                        elif not s_func (*s_args, **s_kw_args): 
     109                                # The current success conditions evaluated to False, keep  
     110                                # performing the current action 
     111                                return self.perform_action(self.action_list[0]) 
     112                        else: 
     113                                # The success condition is true - meaning that the current action is 
     114                                # popped out, and we proceed to the next item in the list 
     115                                self.action_list.pop(0) 
     116                 
     117                # We went through the entire action_list but no action was performed 
     118                return False 
    3119 
    4120class Agent(fife.InstanceActionListener): 
     
    8124                self.agentName = agentName 
    9125                self.layer = layer 
     126                self.action_stack = ActionStack() 
    10127                if uniqInMap: 
    11128                        self.agent = layer.getInstance(agentName) 
  • trunk/game/scripts/agents/hero.py

    r19 r21  
    1414 
    1515        def onInstanceActionFinished(self, instance, action): 
     16                if self.action_stack and self.action_stack.run(): 
     17                        # The stack executed something, ignore state change 
     18                        return 
    1619                self.idle() 
    1720                if action.getId() != 'stand': 
     
    4245                self.state = _STATE_TALK 
    4346                self.agent.act('talk', target) 
     47         
     48        def distance_to (self, target): 
     49                return self.agent.getLocationRef().getLayerDistanceTo(target.getLocationRef()) 
  • trunk/game/scripts/world.py

    r19 r21  
    5353        def show_instancemenu(self, clickpoint, instance): 
    5454                """Handles the display of the right-click context menu.""" 
    55                 # IMPORTANT: We assume that the ALWAYS_PRESENT_BUTTONS are _NEVER_ removed from the instancemenu 
    56                 # IMPORTANT: World and Agent functions that handle different actions  
    57                 # must be named "buttonNameHandler", e.g. "talkButtonHandler" 
    58                 # TODO: Add a range check; if we are out of range we should queue (get_in_range, perform_action) 
     55                # IMPORTANT: We assume that the ALWAYS_PRESENT_BUTTONS are  
     56                # _NEVER_ removed from the instancemenu 
     57                # IMPORTANT: World and Agent functions that handle different  
     58                # actions must be named "buttonNameHandler", e.g.  
     59                # "talkButtonHandler" 
    5960                 
    6061                # no right-click menu for clicking on Hero 
    6162                if instance.getFifeId() == self.hero.agent.getFifeId(): 
    6263                        return 
    63                 # buttons that will always be available, regardless of the instance type 
    64                 ALWAYS_PRESENT_BTNS = ('moveButton', 'inspectButton') 
     64                # Buttons that will always be available, regardless of the  
     65                # instance type. 
     66                # IMPORTANT: kickButton is listed here only to allow easier  
     67                # testing of the range check/queueing code  
     68                ALWAYS_PRESENT_BTNS = ('moveButton',  
     69                                       'inspectButton',  
     70                                       'kickButton' 
     71                                       ) 
     72                # Pattern matching the name of the handler functions 
    6573                NAME_PATTERN = "%sHandler" 
    6674                 
    6775                if self.instance_to_agent.has_key(instance.getFifeId()): 
    68                         # we got an agent here; remove any actions that the agent cannot handle 
     76                        # we got an agent here; remove any unhandled actions 
    6977                        for btn in self.all_btns: 
    7078                                #do we have this button in the current menu? 
     
    7583                                if btn_needed and not btn_present: 
    7684                                        self.instancemenu.addChild(btn) 
    77                                 if not btn_needed and btn_present: 
     85                                elif not btn_needed and btn_present: 
    7886                                        self.instancemenu.removeChild(btn) 
    7987                else: 
    80                         # we got some other kind of object, so only leave always_present actions 
     88                        # inst is not Agent; only leave always_present actions 
    8189                        for btn in self.instancemenu.children[:]: 
    8290                                if not btn.name in ALWAYS_PRESENT_BTNS: 
    8391                                        self.instancemenu.removeChild(btn) 
    8492 
    85                 # map a dictionary of button names to their corresponding World fuctions 
    86                 mapdict = dict([ (btn.name,getattr (self, NAME_PATTERN % btn.name)) for btn in self.instancemenu.children]) 
     93                # map a dictionary of button names to the their World fuctions 
     94                mapdict = dict([ (btn.name,getattr(self, NAME_PATTERN % btn.name)) 
     95                                 for btn in self.instancemenu.children]) 
    8796                self.instancemenu.mapEvents (mapdict) 
    8897                 
     
    207216 
    208217        def mousePressed(self, evt): 
     218                # quick and dirty way to clear queued actions 
     219                self.hero.action_stack.clear() 
    209220                if evt.isConsumedByWidgets(): 
    210221                        return 
     
    263274        def kickButtonHandler(self): 
    264275                self.hide_instancemenu() 
     276                inst = self.instancemenu.instance 
     277                #proof-of-concept range checking + action queueing 
     278                if self.hero.distance_to (inst) > 3: 
     279                        self.hero.action_stack.add_action (self.hero.agent.follow,  
     280                                                          ('run',inst,4 * float(TDS.readSetting("TestAgentSpeed"))),  
     281                                                         lambda x: self.hero.distance_to(x) <= 3, 
     282                                                         (inst,) 
     283                                                         ) 
     284                        self.hero.action_stack.add_action (self.kickButtonHandler) 
     285                        self.hero.action_stack.run() 
     286                        return 
     287                 
    265288                self.hero.kick(self.instancemenu.instance.getLocationRef()) 
    266289                self.instancemenu.instance.say('Hey!', 1000) 
Note: See TracChangeset for help on using the changeset viewer.