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

Source Code for Module horizons.world.building.building

  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 fife import fife 
 25   
 26  from horizons.command.building import Build 
 27  from horizons.component.collectingcomponent import CollectingComponent 
 28  from horizons.component.componentholder import ComponentHolder 
 29  from horizons.component.storagecomponent import StorageComponent 
 30  from horizons.constants import GAME, LAYERS, RES 
 31  from horizons.i18n import disable_translations 
 32  from horizons.scheduler import Scheduler 
 33  from horizons.util.loaders.actionsetloader import ActionSetLoader 
 34  from horizons.util.shapes import ConstRect, Point, distances 
 35  from horizons.util.worldobject import WorldObject 
 36  from horizons.world.building.buildable import BuildableSingle 
 37  from horizons.world.concreteobject import ConcreteObject 
 38  from horizons.world.settlement import Settlement 
39 40 41 -class BasicBuilding(ComponentHolder, ConcreteObject):
42 """Class that represents a building. The building class is mainly a super class for other buildings.""" 43 44 # basic properties of class 45 walkable = False # whether we can walk on this building (True for e.g. streets, trees..) 46 buildable_upon = False # whether we can build upon this building 47 is_building = True 48 tearable = True 49 layer = LAYERS.OBJECTS 50 51 log = logging.getLogger("world.building") 52 53 """ 54 @param x, y: int position of the building. 55 @param rotation: value passed to getInstance 56 @param owner: Player that owns the building. 57 @param level: start in this tier 58 @param action_set_id: use this action set id. None means choose one at random 59 """
60 - def __init__(self, x, y, rotation, owner, island, level=None, **kwargs):
61 self.__pre_init(owner, rotation, Point(x, y), level=level) 62 super().__init__(x=x, y=y, rotation=rotation, owner=owner, 63 island=island, **kwargs) 64 self.__init() 65 self.island = island 66 67 settlements = self.island.get_settlements(self.position, owner) 68 self.settlement = None 69 if settlements: 70 self.settlement = settlements[0] 71 elif owner: 72 # create one if we have an owner 73 self.settlement = self.island.add_settlement(self.position, self.radius, owner) 74 75 assert self.settlement is None or isinstance(self.settlement, Settlement)
76
77 - def __pre_init(self, owner, rotation, origin, level=None):
78 """Here we face the awkward situation of requiring a fourth init function. 79 It is called like __init, but before other parts are inited via super(). 80 This is necessary since some attributes are used by these other parts.""" 81 self.owner = owner 82 if level is None: 83 level = 0 if self.owner is None else self.owner.settler_level 84 self.level = level 85 self.rotation = rotation 86 if self.rotation in (135, 315): # Rotate the rect correctly 87 self.position = ConstRect(origin, self.size[1] - 1, self.size[0] - 1) 88 else: 89 self.position = ConstRect(origin, self.size[0] - 1, self.size[1] - 1)
90
91 - def __init(self, remaining_ticks_of_month=None):
92 self.loading_area = self.position # shape where collector get resources 93 94 origin = self.position.origin 95 self._instance, action_name = self.getInstance(self.session, origin.x, origin.y, 96 rotation=self.rotation, action_set_id=self._action_set_id, world_id=str(self.worldid)) 97 98 if self.has_running_costs: # Get payout every 30 seconds 99 interval = self.session.timer.get_ticks(GAME.INGAME_TICK_INTERVAL) 100 run_in = remaining_ticks_of_month if remaining_ticks_of_month is not None else interval 101 Scheduler().add_new_object(self.get_payout, self, 102 run_in=run_in, loops=-1, loop_interval=interval)
103
104 - def toggle_costs(self):
105 self.running_costs, self.running_costs_inactive = \ 106 self.running_costs_inactive, self.running_costs
107
108 - def running_costs_active(self):
109 """Returns whether the building currently pays the running costs for status 'active'""" 110 return (self.running_costs > self.running_costs_inactive)
111
112 - def get_payout(self):
113 """Gets the payout from the settlement in form of its running costs""" 114 self.owner.get_component(StorageComponent).inventory.alter(RES.GOLD, -self.running_costs)
115
116 - def remove(self):
117 """Removes the building""" 118 self.log.debug("building: remove %s", self.worldid) 119 if hasattr(self, "disaster"): 120 self.disaster.recover(self) 121 self.island.remove_building(self) 122 super().remove()
123 # NOTE: removing layers from the renderer here will affect others players too! 124
125 - def save(self, db):
126 super().save(db) 127 db("INSERT INTO building (rowid, type, x, y, rotation, location, level) \ 128 VALUES (?, ?, ?, ?, ?, ?, ?)", 129 self.worldid, self.__class__.id, self.position.origin.x, 130 self.position.origin.y, self.rotation, 131 (self.settlement or self.island).worldid, self.level) 132 if self.has_running_costs: 133 remaining_ticks = Scheduler().get_remaining_ticks(self, self.get_payout) 134 db("INSERT INTO remaining_ticks_of_month(rowid, ticks) VALUES(?, ?)", self.worldid, remaining_ticks)
135
136 - def load(self, db, worldid):
137 self.island, self.settlement = self.load_location(db, worldid) 138 x, y, location, rotation, level = db.get_building_row(worldid) 139 owner_id = db.get_settlement_owner(location) 140 owner = None if owner_id is None else WorldObject.get_object_by_id(owner_id) 141 142 # early init before super() call 143 self.__pre_init(owner, rotation, Point(x, y), level=level) 144 145 super().load(db, worldid) 146 147 remaining_ticks_of_month = None 148 if self.has_running_costs: 149 db_data = db("SELECT ticks FROM remaining_ticks_of_month WHERE rowid=?", worldid) 150 if not db_data: 151 # this can happen when running costs are set when there were no before 152 # we shouldn't crash because of changes in yaml code, still it's suspicious 153 self.log.warning('Object %s of type %s does not know when to pay its rent.\n' 154 'Disregard this when loading old savegames or on running cost changes.', 155 self.worldid, self.id) 156 remaining_ticks_of_month = 1 157 else: 158 remaining_ticks_of_month = db_data[0][0] 159 160 self.__init(remaining_ticks_of_month=remaining_ticks_of_month) 161 162 # island.add_building handles registration of building for island and settlement 163 self.island.add_building(self, self.owner, load=True)
164
165 - def load_location(self, db, worldid):
166 """ 167 Does not alter self, just gets island and settlement from a savegame. 168 @return: tuple: (island, settlement) 169 """ 170 location_obj = WorldObject.get_object_by_id(db.get_building_location(worldid)) 171 if isinstance(location_obj, Settlement): 172 # workaround: island can't be fetched from world, because it isn't fully constructed 173 island = WorldObject.get_object_by_id(db.get_settlement_island(location_obj.worldid)) 174 settlement = location_obj 175 else: # loc is island 176 island = location_obj 177 settlement = None 178 return (island, settlement)
179
180 - def get_buildings_in_range(self):
181 # TODO Think about moving this to the Settlement class 182 buildings = self.settlement.buildings 183 for building in buildings: 184 if building is self: 185 continue 186 if distances.distance_rect_rect(self.position, building.position) <= self.radius: 187 yield building
188
189 - def update_action_set_level(self, level=0):
190 """Updates this buildings action_set to a random actionset from the specified level 191 (if an action set exists in that level). 192 Its difference to get_random_action_set is that it just checks one level, and doesn't 193 search for an action set everywhere, which makes it a lot more effective if you're 194 just updating. 195 @param level: int level number""" 196 action_set = self.__class__.get_random_action_set(level, exact_level=True) 197 if action_set: 198 self._action_set_id = action_set # Set the new action_set 199 self.act(self._action, repeating=True)
200
201 - def level_upgrade(self, lvl):
202 """Upgrades building to another tier""" 203 self.level = lvl 204 self.update_action_set_level(lvl) 205 206 # any collectors (units) should also be upgraded, so that their 207 # graphics or properties can change 208 if self.has_component(CollectingComponent): 209 for collector in self.get_component(CollectingComponent).get_local_collectors(): 210 collector.level_upgrade(lvl)
211 212 @classmethod
213 - def get_initial_level(cls, player):
214 if hasattr(cls, 'default_level_on_build'): 215 return cls.default_level_on_build 216 return player.settler_level
217 218 @classmethod
219 - def getInstance(cls, session, x, y, action='idle', level=0, rotation=45, action_set_id=None, world_id=""):
220 """Get a Fife instance 221 @param x, y: The coordinates 222 @param action: The action, defaults to 'idle' 223 @param level: object level. Relevant for choosing an action set 224 @param rotation: rotation of the object. Any of [ 45 + 90*i for i in xrange(0, 4) ] 225 @param action_set_id: can be set if the action set is already known. If set, level isn't considered. 226 @return: tuple (fife_instance, action_set_id) 227 """ 228 assert isinstance(x, int) 229 assert isinstance(y, int) 230 #rotation = cls.check_build_rotation(session, rotation, x, y) 231 # TODO: replace this with new buildable api 232 # IDEA: save rotation in savegame 233 234 width, length = cls.size 235 if rotation == 135 or rotation == 315: 236 # if you look at a non-square builing from a 45 degree angle it looks 237 # different than from a 135 degree angle 238 # when you rotate it the width becomes the length and the length becomes the width 239 width, length = length, width 240 241 # the drawing origin is the center of it's area, minus 0.5 242 # the 0.5 isn't really necessary, but other code is aligned with the 0.5 shift 243 # this is at least for the gridoverlay, and the location of a build preview relative to the mouse 244 # it this is changed, it should also be changed for ground tiles (world/ground.py) and units 245 instance_coords = [x + width / 2 - 0.5, y + length / 2 - 0.5, 0] 246 247 instance = session.view.layers[cls.layer].createInstance( 248 cls._fife_object, 249 fife.ExactModelCoordinate(*instance_coords), 250 world_id) 251 252 if action_set_id is None: 253 action_set_id = cls.get_random_action_set(level=level) 254 fife.InstanceVisual.create(instance) 255 256 action_set = ActionSetLoader.get_set(action_set_id) 257 if action not in action_set: 258 if 'idle' in action_set: 259 action = 'idle' 260 elif 'idle_full' in action_set: 261 action = 'idle_full' 262 else: 263 # set first action 264 action = list(action_set.keys())[0] 265 instance.actRepeat("{}_{}".format(action, action_set_id), rotation) 266 return (instance, action_set_id)
267 268 @classmethod
269 - def have_resources(cls, inventory_holders, owner):
270 return Build.check_resources({}, cls.costs, owner, inventory_holders)[0]
271
272 - def init(self):
273 """init the building, called after the constructor is run and the 274 building is positioned (the settlement variable is assigned etc) 275 """ 276 pass
277
278 - def start(self):
279 """This function is called when the building is built, 280 to start production for example.""" 281 pass
282
283 - def __str__(self):
284 with disable_translations(): 285 return '{}(id={};worldid={})'.format(self.name, self.id, getattr(self, 'worldid', 'none'))
286
287 288 -class DefaultBuilding(BasicBuilding, BuildableSingle):
289 """Building with default properties, that does nothing.""" 290 pass
291