Changeset 21
- Timestamp:
- 03/14/09 00:50:55 (18 months ago)
- Location:
- trunk/game
- Files:
-
- 2 added
- 3 modified
-
run_tests.py (added)
-
scripts/agents/ActionStack_test.py (added)
-
scripts/agents/agent.py (modified) (2 diffs)
-
scripts/agents/hero.py (modified) (2 diffs)
-
scripts/world.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/game/scripts/agents/agent.py
r19 r21 1 1 import fife 2 2 from scripts.common.common import ProgrammingError 3 4 class 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 3 119 4 120 class Agent(fife.InstanceActionListener): … … 8 124 self.agentName = agentName 9 125 self.layer = layer 126 self.action_stack = ActionStack() 10 127 if uniqInMap: 11 128 self.agent = layer.getInstance(agentName) -
trunk/game/scripts/agents/hero.py
r19 r21 14 14 15 15 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 16 19 self.idle() 17 20 if action.getId() != 'stand': … … 42 45 self.state = _STATE_TALK 43 46 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 53 53 def show_instancemenu(self, clickpoint, instance): 54 54 """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" 59 60 60 61 # no right-click menu for clicking on Hero 61 62 if instance.getFifeId() == self.hero.agent.getFifeId(): 62 63 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 65 73 NAME_PATTERN = "%sHandler" 66 74 67 75 if self.instance_to_agent.has_key(instance.getFifeId()): 68 # we got an agent here; remove any actions that the agent cannot handle76 # we got an agent here; remove any unhandled actions 69 77 for btn in self.all_btns: 70 78 #do we have this button in the current menu? … … 75 83 if btn_needed and not btn_present: 76 84 self.instancemenu.addChild(btn) 77 if not btn_needed and btn_present:85 elif not btn_needed and btn_present: 78 86 self.instancemenu.removeChild(btn) 79 87 else: 80 # we got some other kind of object, soonly leave always_present actions88 # inst is not Agent; only leave always_present actions 81 89 for btn in self.instancemenu.children[:]: 82 90 if not btn.name in ALWAYS_PRESENT_BTNS: 83 91 self.instancemenu.removeChild(btn) 84 92 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]) 87 96 self.instancemenu.mapEvents (mapdict) 88 97 … … 207 216 208 217 def mousePressed(self, evt): 218 # quick and dirty way to clear queued actions 219 self.hero.action_stack.clear() 209 220 if evt.isConsumedByWidgets(): 210 221 return … … 263 274 def kickButtonHandler(self): 264 275 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 265 288 self.hero.kick(self.instancemenu.instance.getLocationRef()) 266 289 self.instancemenu.instance.say('Hey!', 1000)
