Ticket #1: actionStack.3.patch

File actionStack.3.patch, 9.6 KB (added by tie <parpg@…>, 11 years ago)

Removed a debugging print line that slipped in

  • 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, action_func, actionArgs = (), actionKwargs = {}, 
     33                               success_func = None, successArgs = (),successKwargs = {}): 
     34                """ 
     35                Appends an action entry to the action_list. Supports both args and  
     36                kwargs. If a previous batch of actions was inserted, and then ran,  
     37                this will first empty the stack before adding the new entry. 
     38                """ 
     39                if not callable (action_func): 
     40                        # Basic check to see if the action funciton is callable 
     41                        # Optional TODO: We can add more checks, if this tends to be misused   
     42                        raise ValueError ("%s is not callable!" % action_func) 
     43                if self.running: 
     44                        # We have a running queue; clean it up before adding anything new 
     45                        self.clear() 
     46                        self.running = False 
     47 
     48                self.action_list.append ( (action_func, actionArgs, actionKwargs,  
     49                                           success_func, successArgs , successKwargs)) 
     50 
     51        def add_action (self, action_func, actionArgs = (), 
     52                        success_func = None, successArgs = ()): 
     53                """ 
     54                Appends an action entry to the action_list. Shortcut to  
     55                add_kwargs_action() without kwargs. Only supports positinal arguments  
     56                for the action_func/success_func 
     57                """ 
     58                self.add_kwargs_action (action_func, actionArgs, {},  
     59                                        success_func, successArgs , {}) 
     60 
     61        def remove_current_action (self): 
     62                """Removes an action from the bottom of the list""" 
     63                if self.action_list: 
     64                        self.action_list.pop(0) 
     65 
     66        def clear (self): 
     67                """Removes all actions from the stack""" 
     68                self.action_list = [] 
     69 
     70        def execute_current_action (self, pop=False): 
     71                """Unconditionally runs the action that is currently queued. 
     72                If the pop argument is set to True, it will also remove the  
     73                action from the stack. 
     74                """ 
     75                if pop: 
     76                        action = self.action_list.pop(0) 
     77                else: 
     78                        action = self.action_list[0] 
     79                # read items from the action entry 
     80                a_func, a_args, a_kwargs = action[:3] 
     81                a_func (*a_args, **a_kwargs) 
     82                return True 
     83 
     84        def run (self): 
     85                """ 
     86                Runs an action from the queue. 
     87 
     88                If there is no success function, just executes the current action  
     89                and removes it from the queue. If the success function evaluates 
     90                to True, removes the current action and tries to .run() the next  
     91                one. If the success function evaluates to False, only executes  
     92                the current action. 
     93                 
     94                Returns True if some action was executed, False otherwise. 
     95                """ 
     96 
     97                if not self.action_list: 
     98                        return False 
     99                # we set the running flag 
     100                self.running = True 
     101                # load the current entry success function details 
     102                s_func, s_args, s_kwargs = self.action_list[0][3:6] 
     103 
     104                if (not s_func): 
     105                        # No success function defined - meaning that the  
     106                        # action will popped form the from the queue and executed once. 
     107                        return self.execute_current_action(pop=True) 
     108 
     109                if s_func (*s_args, **s_kwargs): 
     110                        # The success condition is true - meaning that the current action is 
     111                        # popped out, and the next action (if any) is executed instead. 
     112                        # We pop out all successful actions from the bottom of the list 
     113                        # until we reach an empty or false successful condition or until 
     114                        # deplete the list. 
     115                        self.action_list.pop(0) 
     116                        for action in self.action_list[:]: 
     117                                s_func, s_args, s_kwargs = action[3:6] 
     118                                if not s_func or not s_func (*s_args, **s_kwargs): 
     119                                        break 
     120                        if self.action_list: 
     121                                # The list is not emtpy - there's an action waiting for exec 
     122                                # If the action got no success_func, we need to pop it too 
     123                                return self.execute_current_action(pop=not bool(s_func)) 
     124                        else: 
     125                                # All queued success conditions are True, so no execution  
     126                                return False 
     127                         
     128                # Success func evaluated to False, so keep running current action 
     129                return self.execute_current_action() 
     130 
    4131class Agent(fife.InstanceActionListener): 
    5132        def __init__(self, model, agentName, layer, uniqInMap=True): 
    6133                fife.InstanceActionListener.__init__(self) 
    7134                self.model = model 
    8135                self.agentName = agentName 
    9136                self.layer = layer 
     137                self.action_stack = ActionStack() 
    10138                if uniqInMap: 
    11139                        self.agent = layer.getInstance(agentName) 
    12140                        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