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

Source Code for Module horizons.world.units.weapon

  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   
 24  from horizons.component.healthcomponent import HealthComponent 
 25  from horizons.constants import GAME_SPEED 
 26  from horizons.scheduler import Scheduler 
 27  from horizons.util.changelistener import metaChangeListenerDecorator 
 28  from horizons.util.python.callback import Callback 
 29  from horizons.util.shapes import Point 
30 31 32 @metaChangeListenerDecorator("attack_ready") 33 @metaChangeListenerDecorator("weapon_fired") 34 -class Weapon:
35 """ 36 Generic Weapon class 37 it has the modifiers: 38 damage - damage dealt in hp 39 weapon_range - tuple with minimum and maximum attack range 40 cooldown_time - number of seconds until the attack is ready again 41 attack_speed - speed that calculates the time until attack reaches target 42 attack_radius - radius affected by attack 43 44 attack_ready callbacks are executed when the attack is made ready 45 """ 46 log = logging.getLogger("world.combat") 47
48 - def __init__(self, session, id):
49 """ 50 @param session: game session 51 @param id: weapon id to be initialized 52 """ 53 data = session.db("SELECT id, type, damage,\ 54 min_range, max_range,\ 55 cooldown_time, attack_speed,\ 56 attack_radius \ 57 FROM weapon WHERE id = ?", id) 58 data = data[0] 59 self.weapon_id = data[0] 60 self.weapon_type = data[1] 61 self.damage = data[2] 62 self.weapon_range = data[3], data[4] 63 self.cooldown_time = data[5] 64 self.attack_speed = data[6] 65 self.attack_radius = data[7] 66 self.attack_ready = True 67 self.session = session
68
69 - def get_damage_modifier(self):
70 return self.damage
71
72 - def get_minimum_range(self):
73 return self.weapon_range[0]
74
75 - def get_maximum_range(self):
76 return self.weapon_range[1]
77 78 @classmethod
79 - def on_impact(cls, session, weapon_id, damage, position):
80 """ 81 Classmethod that deals damage to units at position, depending on weapon_id 82 Damage is done independent of the weapon instance, which may not exist at the time damage is done 83 @param session : UH session 84 @param weapon_id : id of the weapon 85 @param damage : damage to be done 86 @param position : Point with position where damage needs to be done 87 """ 88 cls.log.debug("%s impact", cls) 89 # deal damage to units in position callback 90 attack_radius = session.db.get_weapon_attack_radius(weapon_id) 91 92 units = session.world.get_health_instances(position, attack_radius) 93 94 for unit in units: 95 cls.log.debug("dealing damage to %s", unit) 96 unit.get_component(HealthComponent).deal_damage(weapon_id, damage)
97
98 - def make_attack_ready(self):
99 self.attack_ready = True 100 self.on_attack_ready()
101
102 - def fire(self, destination, position):
103 """ 104 Fires the weapon at a certain destination 105 @param destination: Point with position where weapon will be fired 106 @param position: position where the weapon is fired from 107 """ 108 self.log.debug("%s fire; ready: %s", self, self.attack_ready) 109 if not self.attack_ready: 110 return 111 112 distance = round(position.distance(destination.center)) 113 if not self.check_target_in_range(distance): 114 self.log.debug("%s target not in range", self) 115 return 116 117 # Calculate the ticks until impact. 118 impact_ticks = int(GAME_SPEED.TICKS_PER_SECOND * distance / self.attack_speed) 119 # Deal damage when attack reaches target. 120 Scheduler().add_new_object(Callback(Weapon.on_impact, 121 self.session, self.weapon_id, self.get_damage_modifier(), destination), 122 Weapon, impact_ticks) 123 124 # Calculate the ticks until attack is ready again. 125 ready_ticks = int(GAME_SPEED.TICKS_PER_SECOND * self.cooldown_time) 126 Scheduler().add_new_object(self.make_attack_ready, self, ready_ticks) 127 128 self.log.debug("fired %s at %s, impact in %s", self, destination, impact_ticks) 129 130 self.attack_ready = False 131 self.on_weapon_fired()
132
133 - def check_target_in_range(self, distance):
134 """ 135 Checks if the distance between the weapon and target is in weapon range 136 @param distance : distance between weapon and target 137 """ 138 return self.weapon_range[0] <= distance <= self.weapon_range[1]
139
140 - def get_ticks_until_ready(self):
141 """ 142 Returns the number of ticks until the attack is ready 143 If attack is ready return 0 144 """ 145 return 0 if self.attack_ready else Scheduler().get_remaining_ticks(self, self.make_attack_ready)
146 147 @classmethod
148 - def load_attacks(cls, session, db):
149 """ 150 Loads ongoing attacks from savegame database 151 Creates scheduled calls for on_impact 152 """ 153 attacks = db("SELECT remaining_ticks, weapon_id, damage, dest_x, dest_y FROM attacks") 154 for (ticks, weapon_id, damage, dx, dy) in attacks: 155 Scheduler().add_new_object(Callback(Weapon.on_impact, 156 session, weapon_id, damage, Point(dx, dy)), Weapon, ticks)
157 158 @classmethod
159 - def save_attacks(cls, db):
160 """ 161 Saves ongoing attacks 162 """ 163 calls = Scheduler().get_classinst_calls(Weapon) 164 for call in calls: 165 callback = call.callback 166 weapon_id = callback.args[1] 167 damage = callback.args[2] 168 dest_x = callback.args[3].x 169 dest_y = callback.args[3].y 170 ticks = calls[call] 171 db("INSERT INTO attacks(remaining_ticks, weapon_id, damage, dest_x, dest_y) VALUES (?, ?, ?, ?, ?)", 172 ticks, weapon_id, damage, dest_x, dest_y)
173
174 - def __str__(self):
175 return "Weapon(id:{};type:{};rang:{})".format(self.weapon_id, self.weapon_type, self.weapon_range)
176
177 178 -class SetStackableWeaponNumberError(Exception):
179 """ 180 Raised when setting the number of weapons for a stackable weapon fails 181 """ 182 pass
183
184 185 -class StackableWeapon(Weapon):
186 """ 187 Stackable Weapon class 188 A generic Weapon that can have a number of weapons binded per instance 189 It deals the number of weapons times weapon's default damage 190 This is used for cannons, reducing the number of instances and bullets fired 191 """
192 - def __init__(self, session, id):
193 super().__init__(session, id) 194 self.__init()
195
196 - def __init(self):
197 self.number_of_weapons = 1 198 self.max_number_of_weapons = 1
199
200 - def set_number_of_weapons(self, number):
201 """ 202 Sets number of cannons as resource binded to a StackableWeapon object 203 the number of cannons increases the damage dealt by one StackableWeapon instance 204 @param number : number of cannons 205 """ 206 if number > self.max_number_of_weapons: 207 raise SetStackableWeaponNumberError 208 else: 209 self.number_of_weapons = number
210
211 - def increase_number_of_weapons(self, number):
212 """ 213 Increases number of cannons as resource binded to a StackableWeapon object 214 @param number : number of cannons 215 """ 216 if number + self.number_of_weapons > self.max_number_of_weapons: 217 raise SetStackableWeaponNumberError 218 else: 219 self.number_of_weapons += number
220
221 - def decrease_number_of_weapons(self, number):
222 """ 223 Decreases number of cannons as resource binded to a StackableWeapon object 224 @param number : number of cannons 225 """ 226 if self.number_of_weapons - number <= 0: 227 raise SetStackableWeaponNumberError 228 else: 229 self.number_of_weapons -= number
230
231 - def get_damage_modifier(self):
232 return self.number_of_weapons * super().get_damage_modifier()
233