Ticket #1: actionStack.patch

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

Added an actionStack property to the Agent, allowing queuing of actions

  • 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        (actionFunc, actionFuncArgs, actionFuncKwargs, 
     9        (successFunc, successFuncArgs, successFuncKwargs).  
     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 next action is processed. 
     15          
     16        Example: 
     17        To define something like "Kick him while he's down", you coud do: 
     18        .addAction (kick, (him,), isDown, (him,)) 
     19          
     20        """ 
     21         
     22        # this is the actual list that stores the entries 
     23        actionList = [] 
     24         
     25        def addKwargsAction (self, actionFunc, actionArgs = (), actionKwargs = {},  
     26                             successFunc = None, successArgs = (),successKwargs = {}): 
     27                """Appends an action entry to the action list. Supports both args and kwargs.""" 
     28                if not callable (actionFunc): 
     29                        # basic check to see if the action funciton is callable 
     30                        # optional TODO: We can add more checks, if this tends to be misused   
     31                        raise ValueError ("%s is not a callable action fucntion!" % actionFunc) 
     32                self.actionList.append ( (actionFunc, actionArgs, actionKwargs, successFunc, successArgs , successKwargs)) 
     33                 
     34        def addAction (self, actionFunc, actionArgs = (),successFunc = None, successArgs = ()): 
     35                """Appends an action entry to the action list. Shortcut to .addKwargsAction() without kwargs. 
     36                Only supports positinal arguments for the actionFunc/successFunc""" 
     37                self.addKwargsAction (actionFunc, actionArgs, {}, successFunc, successArgs , {}) 
     38         
     39        def removeCurrentAction (self): 
     40                """Removes an action from the bottom of the list""" 
     41                if self.actionList: 
     42                        self.actionList.pop(0) 
     43         
     44        def clear (self): 
     45                """Removes all actions from the stack""" 
     46                self.actionList = [] 
     47                 
     48        def run (self): 
     49                """ 
     50                Runs an action from the queue. 
     51                 
     52                If there is no success function, just executes the current action  
     53                and removes it from the queue. If the success function evaluates 
     54                to True, removes the current action and tries to .run() the next  
     55                one. If the success function evaluates to False, only executes  
     56                the current action. 
     57                 
     58                Returns True if some action was executed, False otherwise.""" 
     59                if not self.actionList: 
     60                        return False 
     61                # load the current entry details 
     62                a_func, a_args, a_kwargs, s_func, s_args, s_kwargs = self.actionList[0] 
     63                 
     64                if (not s_func): 
     65                        # no success function defined - meaning that the  
     66                        # action will popped form the from the queue and executed once 
     67                        self.actionList.pop(0) 
     68                        a_func (*a_args, **a_kwargs)             
     69                        return True 
     70                 
     71                if s_func (*s_args, **s_kwargs): 
     72                        # the success condition is true - meaning that the current action 
     73                        # is popped out, and the next action (if any) is executed instead 
     74                        # Going into a recursoin here should be safe 
     75                        self.actionList.pop(0) 
     76                        return self.run() 
     77                else: 
     78                        a_func (*a_args, **a_kwargs) 
     79                        return True 
     80 
    481class Agent(fife.InstanceActionListener): 
    582        def __init__(self, model, agentName, layer, uniqInMap=True): 
    683                fife.InstanceActionListener.__init__(self) 
    784                self.model = model 
    885                self.agentName = agentName 
    986                self.layer = layer 
     87                self.actionStack = ActionStack() 
    1088                if uniqInMap: 
    1189                        self.agent = layer.getInstance(agentName) 
    1290                        self.agent.addActionListener(self) 
     
    1694 
    1795        def start(self): 
    1896                raise ProgrammingError('No start defined for Agent') 
    19          
     97 
    2098        def kickButtonHandler (self): 
    2199                """Placeholder function to enable kick menu display for all children""" 
    22100                pass 
  • scripts/agents/hero.py

     
    1313                self.idlecounter = 1 
    1414 
    1515        def onInstanceActionFinished(self, instance, action): 
     16                if self.actionStack and self.actionStack.run(): 
     17                        # the actionStack performed some action 
     18                        # IMPORTANT: With this type of hooking, our Hero will miss moving targets 
     19                        return 
    1620                self.idle() 
    1721                if action.getId() != 'stand': 
    1822                        self.idlecounter = 1 
     
    4145        def talk(self, target): 
    4246                self.state = _STATE_TALK 
    4347                self.agent.act('talk', target) 
     48         
     49        def distanceTo (self, target): 
     50                return self.agent.getLocationRef().getLayerDistanceTo(target.getLocationRef()) 
  • scripts/world.py

     
    5555                # IMPORTANT: We assume that the ALWAYS_PRESENT_BUTTONS are _NEVER_ removed from the instancemenu 
    5656                # IMPORTANT: World and Agent functions that handle different actions  
    5757                # 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) 
    5958                 
    6059                # no right-click menu for clicking on Hero 
    6160                if instance.getFifeId() == self.hero.agent.getFifeId(): 
    6261                        return 
    6362                # buttons that will always be available, regardless of the instance type 
    64                 ALWAYS_PRESENT_BTNS = ('moveButton', 'inspectButton') 
     63                # IMPORTANT: kickButton is listed here only to allow easier testing of the range check/queueing code  
     64                ALWAYS_PRESENT_BTNS = ('moveButton', 'inspectButton', 'kickButton') 
    6565                NAME_PATTERN = "%sHandler" 
    6666                 
    6767                if self.instance_to_agent.has_key(instance.getFifeId()): 
     
    7474                                           or btn.name in ALWAYS_PRESENT_BTNS 
    7575                                if btn_needed and not btn_present: 
    7676                                        self.instancemenu.addChild(btn) 
    77                                 if not btn_needed and btn_present: 
     77                                elif not btn_needed and btn_present: 
    7878                                        self.instancemenu.removeChild(btn) 
    7979                else: 
    8080                        # we got some other kind of object, so only leave always_present actions 
     
    206206                        self.cameras['main'].setRotation((currot + 5) % 360) 
    207207 
    208208        def mousePressed(self, evt): 
     209                # quick and dirty way to clear queued actions 
     210                self.hero.actionStack.clear() 
    209211                if evt.isConsumedByWidgets(): 
    210212                        return 
    211213 
     
    262264 
    263265        def kickButtonHandler(self): 
    264266                self.hide_instancemenu() 
     267                inst = self.instancemenu.instance 
     268                #proof-of-concept range checking + action queueing 
     269                if self.hero.distanceTo (inst) > 3: 
     270                        self.hero.actionStack.addAction (self.hero.run, (inst.getLocationRef(),),  
     271                                                         lambda x: self.hero.distanceTo(x) <= 3, (inst,)) 
     272                        self.hero.actionStack.addAction (self.kickButtonHandler) 
     273                        self.hero.actionStack.run() 
     274                        return 
     275                 
    265276                self.hero.kick(self.instancemenu.instance.getLocationRef()) 
    266277                self.instancemenu.instance.say('Hey!', 1000) 
    267278                 
     
    271282 
    272283        def inspectButtonHandler(self): 
    273284                self.hide_instancemenu() 
    274                 inst = self.instancemenu.instance 
     285                inst = self.instancemenu.instance   
    275286                saytext = ['Engine told me that this instance has'] 
    276287                if inst.getId(): 
    277288                        saytext.append(' name %s,' % inst.getId())