Ticket #1: actionStack.4.patch

File actionStack.4.patch, 9.2 KB (added by tie <parpg@…>, 10 years ago)

Fixed some minor issues, improved .run() loop

  • scripts/agents/agent.py

     
    11import fife 
    22from scripts.common.common import ProgrammingError 
    33 
     4class ActionStack (object): 
     5        """  
     6    An object, 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_kwargs, 
     9    (success_func, success_func_args, success_func_kwargs).  
     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 
     27        # This is the actual list that stores the entries 
     28        action_list = [] 
     29        # A flag to track if the stack was ran after the last item was added 
     30        running = False 
     31 
     32        def add_kwargs_action (self,  
     33                        action_func, action_args = (), action_kw_args = {}, 
     34                        success_func = None, success_args = (),success_kw_args = {}): 
     35                """ 
     36                Appends an action entry to the action_list. Supports both args and  
     37                kwargs. If a previous batch of actions was inserted, and then ran,  
     38                this will first empty the stack before adding the new entry. 
     39                """ 
     40                # Basic checks to see if the action and success funcitons are callable  
     41                if not callable (action_func): 
     42                        raise ValueError ("%s is not callable!" % action_func) 
     43                if success_func and not callable(success_func): 
     44                        raise ValueError ("%s is not callable!" % success_func) 
     45                if self.running: 
     46                        # We have a running queue; clean it up before adding anything new 
     47                        self.clear() 
     48                        self.running = False 
     49 
     50                self.action_list.append ( (action_func, action_args, action_kw_args,  
     51                                           success_func, success_args, success_kw_args)) 
     52 
     53        def add_action (self, action_func, action_args = (), 
     54                        success_func = None, success_args = ()): 
     55                """ 
     56                Appends an action entry to the action_list. Shortcut to  
     57                add_kwargs_action() without kwargs. Only supports positinal arguments  
     58                for the action_func/success_func 
     59                """ 
     60                self.add_kwargs_action (action_func, action_args, {},  
     61                                        success_func, success_args , {}) 
     62 
     63        def remove_current_action (self): 
     64                """Removes an action from the bottom of the list""" 
     65                if self.action_list: 
     66                        self.action_list.pop(0) 
     67 
     68        def clear (self): 
     69                """Removes all actions from the stack""" 
     70                self.action_list = [] 
     71         
     72        def perform_action (self, action_item): 
     73                """Runs the provided action_item. It is a tuple following the same 
     74                format as the items in self.action_list """ 
     75                a_func, a_args, a_kwargs = action_item[:3] 
     76                a_func (*a_args, **a_kwargs) 
     77                return True 
     78 
     79        def run (self): 
     80                """ 
     81                Runs an action from the queue. 
     82 
     83                If there is no success function, just executes the current action  
     84                and removes it from the queue. If the success function evaluates 
     85                to True, removes the current action and proceeds with the next one.  
     86                If the success function evaluates to False, only performs  
     87                the current action. 
     88                 
     89                Returns True if some action was executed, False otherwise. 
     90                """ 
     91 
     92                if not self.action_list: 
     93                        return False 
     94                # We set the running flag 
     95                self.running = True 
     96                 
     97                # Loop that will go over the aciton_list and exit after performing an  
     98                # action. Note that the loop iterates over a copy of the action_list 
     99                for action in self.action_list[:]: 
     100                        # Load the current entry success function details 
     101                        s_func, s_args, s_kwargs = self.action_list[0][3:6] 
     102 
     103                        if (not s_func): 
     104                                # No success function defined - meaning that the  
     105                                # action will be popped form the queue and executed once. 
     106                                return self.perform_action(self.action_list.pop(0)) 
     107                        elif s_func (*s_args, **s_kwargs): 
     108                                # The success condition is true - meaning that the current action is 
     109                                # popped out, and we proceed to the next item in the list 
     110                                self.action_list.pop(0) 
     111                                continue 
     112                        else: 
     113                                # The current success conditions is set to false True so, keep  
     114                                # performing the current action 
     115                                return self.perform_action(self.action_list[0]) 
     116                 
     117                # We went through the entire action_list but no action was performed 
     118                return Flase 
     119 
    4120class Agent(fife.InstanceActionListener): 
    5121        def __init__(self, model, agentName, layer, uniqInMap=True): 
    6122                fife.InstanceActionListener.__init__(self) 
    7123                self.model = model 
    8124                self.agentName = agentName 
    9125                self.layer = layer 
     126                self.action_stack = ActionStack() 
    10127                if uniqInMap: 
    11128                        self.agent = layer.getInstance(agentName) 
    12129                        self.agent.addActionListener(self) 
  • scripts/agents/hero.py

     
    1313                self.idlecounter = 1 
    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': 
    1821                        self.idlecounter = 1 
     
    4144        def talk(self, target): 
    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()) 
  • scripts/world.py

     
    5252                 
    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? 
    7179                                btn_present = bool(self.instancemenu.findChild(name=btn.name)) 
     
    7482                                           or btn.name in ALWAYS_PRESENT_BTNS 
    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                 
    8998                # add some global data 
     
    206215                        self.cameras['main'].setRotation((currot + 5) % 360) 
    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 
    211222 
     
    262273 
    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.run,  
     280                                                          (inst.getLocationRef(),),  
     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) 
    267290