Package horizons :: Package component :: Module stancecomponent
[hide private]
[frames] | no frames]

Source Code for Module horizons.component.stancecomponent

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5   
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 20  # ################################################### 
 21   
 22  from horizons.component import Component 
 23  from horizons.scheduler import Scheduler 
 24  from horizons.util.python.callback import Callback 
 25  from horizons.util.shapes import Annulus, Circle 
 26  from horizons.world.units.unitexeptions import MoveNotPossible 
 27   
 28   
29 -class StanceComponent(Component):
30 """ 31 Class to be inherited for all unit stances 32 It has methods defined for specific instance states 33 The default methods are defined that the instance only listens to user commands 34 If a state different from user_attack or user_move is passed, it stops any action 35 and switches to idle. 36 37 NOTE: 38 This does not use stance inheritance as intended, but multiple stance 39 components are present at the same time, while only one is used at a time. 40 """ 41 42 # Store the name of this component 43 NAME = 'stance' 44
45 - def __init__(self):
46 super().__init__() 47 self.state = 'idle' 48 self.action = { 49 'idle': self.act_idle, 50 'user_attack': self.act_user_attack, 51 'user_move': self.act_user_move, 52 'move_back': self.act_move_back, 53 'auto_attack': self.act_auto_attack, 54 'flee': self.act_flee, 55 }
56
57 - def initialize(self):
58 # change state to 'user_attack' when the user issues attack via right click 59 self.instance.add_user_attack_issued_listener(Callback(self.set_state, 'user_attack')) 60 # change state to 'user_move' when the user issues movement via right click 61 try: 62 self.instance.add_user_move_issued_listener(Callback(self.set_state, 'user_move')) 63 except AttributeError: 64 pass # temporary workaround to make it work for towers
65
66 - def remove(self):
67 self.instance.remove_user_attack_issued_listener(Callback(self.set_state, 'user_attack')) 68 try: 69 self.instance.remove_user_move_issued_listener(Callback(self.set_state, 'user_move')) 70 except AttributeError: 71 pass # temporary workaround to make it work for towers 72 super().remove()
73
74 - def set_state(self, state):
75 self.state = state
76
77 - def get_state(self):
78 return self.state
79
80 - def act(self):
81 """ 82 Act according to current state 83 This is called every few second in the instance code 84 """ 85 self.action[self.state]()
86
87 - def act_idle(self):
88 """ 89 Method executed when the instance is idle 90 """ 91 pass
92
93 - def act_user_attack(self):
94 """ 95 Method executed when the instance is trying to attack a target selected by the user 96 """ 97 if not self.instance.is_attacking(): 98 self.state = 'idle'
99
100 - def act_user_move(self):
101 """ 102 Method executed when the instance is moving to a location selected by the user 103 """ 104 if not self.instance.is_moving(): 105 self.state = 'idle'
106
107 - def act_move_back(self):
108 """ 109 Method executed when the instance is moving back to it's default location 110 """ 111 self.instance.stop() 112 self.state = 'idle'
113
114 - def act_auto_attack(self):
115 """ 116 Method executed when the instance has auto acquired target 117 """ 118 self.instance.stop_attack() 119 self.state = 'idle'
120
121 - def act_flee(self):
122 """ 123 Method executed when the instance is trying to evade an attack 124 """ 125 self.instance.stop() 126 self.state = 'idle'
127 128
129 -class LimitedMoveStance(StanceComponent):
130 """ 131 Stance that attacks any unit in stance range and follows it with limited move space 132 This is inherited by Aggressive and Hold Ground stances 133 In adition to StanceComponent it has the following attributes: 134 stance_radius - int with the radius in which instance shold look for target 135 move_range - int with the radius in which instance shold move when attacking it 136 It also keeps track of the return position in which the unit should return when stopped attacking 137 """ 138
139 - def __init__(self):
140 super().__init__() 141 #TODO get range from db 142 self.stance_radius = 0 143 self.move_range = 0 144 # get instance data after it was inited 145 Scheduler().add_new_object(self.get_instance_data, self, run_in=0)
146
147 - def get_instance_data(self):
148 # get a copy of the center Point object 149 self.return_position = self.instance.position.center.copy()
150
151 - def act_idle(self):
152 """ 153 Find target in range then attack it 154 """ 155 target = self.get_target(self.stance_radius + self.instance._max_range) 156 if target: 157 self.instance.attack(target) 158 self.state = 'auto_attack'
159
160 - def act_user_move(self):
161 """ 162 At the end of user move change the returning position 163 """ 164 if not self.instance.is_moving(): 165 self.state = 'idle' 166 self.return_position = self.instance.position.center.copy()
167
168 - def act_user_attack(self):
169 """ 170 If attack ends, update the returning position 171 """ 172 if not self.instance.is_attacking(): 173 self.state = 'idle' 174 self.return_position = self.instance.position.center.copy()
175
176 - def act_move_back(self):
177 """ 178 When moving back try to find target. If found, attack it and drop movement 179 """ 180 target = self.get_target(self.stance_radius + self.instance._max_range) 181 if target: 182 self.instance.attack(target) 183 self.state = 'auto_attack' 184 elif self.instance.position.center == self.return_position: 185 self.state = 'idle'
186
187 - def act_auto_attack(self):
188 """ 189 Check if target still exists or if unit exited the hold ground area 190 """ 191 if not Circle(self.return_position, self.move_range).contains(self.instance.position.center) or \ 192 not self.instance.is_attacking(): 193 try: 194 self.instance.move(self.return_position) 195 except MoveNotPossible: 196 self.instance.move(Circle(self.return_position, self.stance_radius)) 197 self.state = 'move_back'
198
199 - def get_target(self, radius):
200 """ 201 Returns closest attackable unit in radius 202 """ 203 enemies = [u for u in self.session.world.get_health_instances(self.instance.position.center, radius) 204 if self.session.world.diplomacy.are_enemies(u.owner, self.instance.owner)] 205 206 if not enemies: 207 return None 208 209 return min(enemies, key=lambda e: self.instance.position.distance(e.position))
210 211
212 -class AggressiveStance(LimitedMoveStance):
213 """ 214 Stance that attacks units in close range when doing movement 215 """ 216 217 NAME = 'aggressive_stance' 218
219 - def __init__(self):
220 super().__init__() 221 #TODO get range from db 222 self.stance_radius = 15 223 self.move_range = 25
224
225 - def act_user_move(self):
226 """ 227 Check if it can attack while moving 228 """ 229 super().act_user_move() 230 target = self.get_target(self.instance._max_range) 231 if target: 232 self.instance.fire_all_weapons(target.position.center)
233
234 - def act_user_attack(self):
235 """ 236 Check if can attack while moving to another attack 237 """ 238 super().act_user_attack() 239 target = self.get_target(self.instance._max_range) 240 if target: 241 self.instance.fire_all_weapons(target.position.center)
242 243
244 -class HoldGroundStance(LimitedMoveStance):
245 """Stance in radius and not attacks units in close range 246 """ 247 248 NAME = 'hold_ground_stance' 249
250 - def __init__(self):
251 super().__init__() 252 self.stance_radius = 5 253 self.move_range = 15
254 255
256 -class NoneStance(StanceComponent):
257 """No settings for stance 258 """ 259 NAME = 'none_stance'
260 261
262 -class FleeStance(StanceComponent):
263 """ 264 Move away from any approaching units 265 """ 266 267 NAME = 'flee_stance' 268
269 - def __init__(self):
270 super().__init__() 271 self.lookout_distance = 20
272
273 - def act_idle(self):
274 """ 275 If an enemy unit approaches move away from it 276 """ 277 unit = self.get_approaching_unit() 278 if unit: 279 try: 280 distance = unit._max_range + self.lookout_distance 281 self.instance.move(Annulus(unit.position.center, distance, distance + 2)) 282 self.state = 'flee' 283 except MoveNotPossible: 284 pass
285
286 - def act_flee(self):
287 """ 288 If movemen stops, switch to idle 289 """ 290 if not self.instance.is_moving(): 291 self.state = 'idle' 292 # check again for target and move 293 self.act_idle()
294
295 - def get_approaching_unit(self):
296 """ 297 Gets the closest unit that can fire to instance 298 """ 299 enemies = [u for u in self.session.world.get_health_instances(self.instance.position.center, self.lookout_distance) 300 if self.session.world.diplomacy.are_enemies(u.owner, self.instance.owner) and hasattr(u, '_max_range')] 301 302 if not enemies: 303 return None 304 305 sort_order = lambda e: self.instance.position.distance(e.position) + e._max_range 306 return min(enemies, key=sort_order)
307 308 309 DEFAULT_STANCES = [HoldGroundStance, AggressiveStance, NoneStance, FleeStance] 310