Package horizons :: Package world :: Package units :: Module unit
[hide private]
[frames] | no frames]

Source Code for Module horizons.world.units.unit

  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  import logging 
 23  import math 
 24   
 25  from fife import fife 
 26   
 27  from horizons.component.commandablecomponent import CommandableComponent 
 28  from horizons.component.healthcomponent import HealthComponent 
 29  from horizons.constants import LAYERS 
 30  from horizons.extscheduler import ExtScheduler 
 31  from horizons.util.python.callback import Callback 
 32  from horizons.util.python.weakmethod import WeakMethod 
 33  from horizons.util.shapes import Point 
 34  from horizons.util.worldobject import WorldObject 
 35  from horizons.world.resourcehandler import ResourceTransferHandler 
 36  from horizons.world.units.movingobject import MovingObject 
37 38 39 -class Unit(MovingObject, ResourceTransferHandler):
40 log = logging.getLogger("world.units") 41 is_unit = True 42 is_ship = False 43 health_bar_y = -30 44 45 AUTOMATIC_HEALTH_DISPLAY_TIMEOUT = 10 # show health for 10 sec after damage has been taken 46
47 - def __init__(self, x, y, owner=None, **kwargs):
48 super().__init__(x=x, y=y, **kwargs) 49 self.__init(x, y, owner)
50
51 - def __init(self, x, y, owner):
52 self.owner = owner 53 54 class Tmp(fife.InstanceActionListener): 55 pass
56 self.InstanceActionListener = Tmp() 57 self.InstanceActionListener.onInstanceActionFinished = \ 58 WeakMethod(self.onInstanceActionFinished) 59 self.InstanceActionListener.onInstanceActionCancelled = \ 60 WeakMethod(self.onInstanceActionCancelled) 61 self.InstanceActionListener.onInstanceActionFrame = lambda *args: None 62 self.InstanceActionListener.thisown = 0 # fife will claim ownership of this 63 64 self._instance = self.session.view.layers[LAYERS.OBJECTS].createInstance( 65 self.__class__._fife_object, fife.ModelCoordinate(int(x), int(y), 0), str(self.worldid)) 66 fife.InstanceVisual.create(self._instance) 67 location = fife.Location(self._instance.getLocation().getLayer()) 68 location.setExactLayerCoordinates(fife.ExactModelCoordinate(x + x, y + y, 0)) 69 self.act(self._action, location, True) 70 self._instance.addActionListener(self.InstanceActionListener) 71 72 self.loading_area = self.position 73 74 self._health_displayed = False 75 76 if self.has_component(HealthComponent): 77 self.get_component(HealthComponent).add_damage_dealt_listener(self._on_damage)
78
79 - def remove(self):
80 self.log.debug("Unit.remove for %s started", self) 81 if hasattr(self.owner, 'remove_unit'): 82 self.owner.remove_unit(self) 83 self._instance.removeActionListener(self.InstanceActionListener) 84 ExtScheduler().rem_all_classinst_calls(self) 85 super().remove() 86 self.log.debug("Unit.remove finished")
87
88 - def onInstanceActionFinished(self, instance, action):
89 """ 90 @param instance: fife.Instance 91 @param action: string representing the action that is finished. 92 """ 93 location = fife.Location(self._instance.getLocation().getLayer()) 94 location.setExactLayerCoordinates(fife.ExactModelCoordinate( 95 self.position.x + self.position.x - self.last_position.x, 96 self.position.y + self.position.y - self.last_position.y, 0)) 97 98 facing_loc = self._instance.getFacingLocation() 99 if action.getId().startswith('move_'): 100 # Remember: this means we *ended* a "move" action just now! 101 facing_loc = location 102 103 self.act(self._action, facing_loc=facing_loc, repeating=True)
104
105 - def onInstanceActionCancelled(self, instance, action):
106 pass
107
108 - def _on_damage(self, caller=None):
109 """Called when health has changed""" 110 if not self._instance: # dead 111 # it is sometimes hard to avoid this being called after the unit has died, 112 # e.g. when it's part of a list of changelisteners, and one of the listeners executed before kills the unit 113 return 114 health_was_displayed_before = self._health_displayed 115 # always update 116 self.draw_health() 117 if health_was_displayed_before: 118 return # don't schedule removal 119 # remember that it has been drawn automatically 120 self._last_draw_health_call_on_damage = True 121 # remove later (but only in case there's no manual interference) 122 ExtScheduler().add_new_object(Callback(self.draw_health, auto_remove=True), 123 self, self.__class__.AUTOMATIC_HEALTH_DISPLAY_TIMEOUT)
124
125 - def draw_health(self, remove_only=False, auto_remove=False):
126 """Draws the units current health as a healthbar over the unit.""" 127 if not self.has_component(HealthComponent): 128 return 129 render_name = "health_" + str(self.worldid) 130 renderer = self.session.view.renderer['GenericRenderer'] 131 renderer.removeAll(render_name) 132 if remove_only or (auto_remove and not self._last_draw_health_call_on_damage): 133 # only remove on auto_remove if this health was actually displayed as reacton to _on_damage 134 # else we might remove something that the user still wants 135 self._health_displayed = False 136 return 137 self._last_draw_health_call_on_damage = False 138 self._health_displayed = True 139 health_component = self.get_component(HealthComponent) 140 health = health_component.health 141 max_health = health_component.max_health 142 zoom = self.session.view.zoom 143 height = int(5 * zoom) 144 width = int(50 * zoom) 145 y_pos = int(self.health_bar_y * zoom) 146 relative_x = int((width * health) // max_health - (width // 2)) 147 # mid_node is the coord separating healthy (green) and damaged (red) quads 148 mid_node_top = fife.RendererNode(self._instance, fife.Point(relative_x, y_pos - height)) 149 mid_node_btm = fife.RendererNode(self._instance, fife.Point(relative_x, y_pos)) 150 151 left_upper = fife.RendererNode(self._instance, fife.Point(-width // 2, y_pos - height)) 152 right_upper = fife.RendererNode(self._instance, fife.Point(width // 2, y_pos - height)) 153 left_lower = fife.RendererNode(self._instance, fife.Point(-width // 2, y_pos)) 154 right_lower = fife.RendererNode(self._instance, fife.Point(width // 2, y_pos)) 155 156 if health > 0: # draw healthy part of health bar 157 renderer.addQuad(render_name, 158 left_upper, 159 left_lower, 160 mid_node_btm, 161 mid_node_top, 162 0, 255, 0) 163 if health < max_health: # draw damaged part 164 renderer.addQuad(render_name, 165 mid_node_top, 166 mid_node_btm, 167 right_lower, 168 right_upper, 169 255, 0, 0)
170
171 - def hide(self):
172 """Hides the unit.""" 173 vis = self._instance.get2dGfxVisual() 174 vis.setVisible(False)
175
176 - def show(self):
177 vis = self._instance.get2dGfxVisual() 178 vis.setVisible(True)
179
180 - def save(self, db):
181 super().save(db) 182 183 owner_id = 0 if self.owner is None else self.owner.worldid 184 db("INSERT INTO unit (rowid, type, x, y, owner) VALUES(?, ?, ?, ?, ?)", 185 self.worldid, self.__class__.id, self.position.x, self.position.y, owner_id)
186
187 - def load(self, db, worldid):
188 super().load(db, worldid) 189 190 x, y, owner_id = db("SELECT x, y, owner FROM unit WHERE rowid = ?", worldid)[0] 191 if owner_id == 0: 192 owner = None 193 else: 194 owner = WorldObject.get_object_by_id(owner_id) 195 self.__init(x, y, owner) 196 197 return self
198
199 - def get_random_location(self, in_range):
200 """Returns a random location in walking_range, that we can find a path to 201 Does not check every point, only a few samples are tried. 202 @param in_range: int, max distance to returned point from current position 203 @return: tuple(Instance of Point or None, path or None)""" 204 range_squared = in_range * in_range 205 randint = self.session.random.randint 206 # pick a sample, try tries times 207 tries = range_squared // 2 208 for i in range(tries): 209 # choose x-difference, then y-difference so that the distance is in the range. 210 x_diff = randint(1, in_range) # always go at least 1 field 211 y_max_diff = int(math.sqrt(range_squared - x_diff * x_diff)) 212 y_diff = randint(0, y_max_diff) 213 # use randomness of x/y_diff below, randint calls are expensive 214 # this results in a higher chance for x > y than y < x, so equalize 215 if (x_diff + y_diff) % 2 == 0: 216 x_diff, y_diff = y_diff, x_diff 217 # direction 218 if x_diff % 2 == 0: 219 y_diff = -y_diff 220 if y_diff % 2 == 0: 221 x_diff = -x_diff 222 # check this target 223 possible_target = Point(self.position.x + x_diff, self.position.y + y_diff) 224 path = self.check_move(possible_target) 225 if path: 226 return (possible_target, path) 227 return (None, None)
228
229 - def go(self, x, y):
230 self.get_component(CommandableComponent).go(x, y)
231 232 @property
233 - def classname(self):
234 return self.session.db.get_unit_type_name(self.id)
235
236 - def __str__(self): # debug
237 return '{}(id={};worldid={})'.format(self.name, self.id, self.worldid if hasattr(self, 'worldid') else 'none') 238