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

Source Code for Module horizons.world.settlement

  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 json 
 23  import logging 
 24  from collections import defaultdict 
 25   
 26  from horizons.component.componentholder import ComponentHolder 
 27  from horizons.component.storagecomponent import StorageComponent 
 28  from horizons.component.tradepostcomponent import TradePostComponent 
 29  from horizons.constants import BUILDINGS, TIER 
 30  from horizons.messaging import SettlementInventoryUpdated, UpgradePermissionsChanged 
 31  from horizons.scheduler import Scheduler 
 32  from horizons.util.changelistener import ChangeListener 
 33  from horizons.util.inventorychecker import InventoryChecker 
 34  from horizons.util.worldobject import WorldObject 
 35  from horizons.world.buildability.settlementcache import SettlementBuildabilityCache 
 36  from horizons.world.production.producer import GroundUnitProducer, Producer, ShipProducer 
 37  from horizons.world.resourcehandler import ResourceHandler 
38 39 40 -class Settlement(ComponentHolder, WorldObject, ChangeListener, ResourceHandler):
41 """The Settlement class describes a settlement and stores all the necessary information 42 like name, current inhabitants, lists of tiles and houses, etc belonging to the village.""" 43 44 log = logging.getLogger("world.settlement") 45 46 component_templates = ( 47 { 48 'StorageComponent': 49 {'PositiveSizedSlotStorage': 50 {'limit': 0} 51 } 52 }, 53 'TradePostComponent', 54 'SettlementNameComponent', 55 ) 56
57 - def __init__(self, session, owner):
58 """ 59 @param owner: Player object that owns the settlement 60 """ 61 super().__init__() 62 self.__init(session, owner, self.make_default_upgrade_permissions(), self.make_default_tax_settings())
63
64 - def __init(self, session, owner, upgrade_permissions, tax_settings):
65 from horizons.session import Session 66 assert isinstance(session, Session) 67 self.session = session 68 self.owner = owner 69 self.buildings = [] 70 self.ground_map = {} # this is the same as in island.py. it uses hard references to the tiles too 71 self.produced_res = defaultdict(int) # dictionary of all resources, produced at this settlement 72 self.buildings_by_id = defaultdict(list) 73 self.warehouse = None # this is set later in the same tick by the warehouse itself or load() here 74 self.upgrade_permissions = upgrade_permissions 75 self.tax_settings = tax_settings 76 Scheduler().add_new_object(self.__init_inventory_checker, self)
77
78 - def init_buildability_cache(self, terrain_cache):
79 self.buildability_cache = SettlementBuildabilityCache(terrain_cache, self.ground_map) 80 self.buildability_cache.modify_area(self.ground_map.keys())
81 82 @classmethod
84 upgrade_permissions = {} 85 for level in range(TIER.CURRENT_MAX): 86 upgrade_permissions[level] = True 87 upgrade_permissions[TIER.CURRENT_MAX] = False 88 return upgrade_permissions
89 90 @classmethod
92 tax_settings = {} 93 for level in range(TIER.CURRENT_MAX + 1): 94 tax_settings[level] = 1.0 95 return tax_settings
96
97 - def set_tax_setting(self, level, tax):
98 self.tax_settings[level] = tax
99
100 - def set_upgrade_permissions(self, level, allowed):
101 if self.upgrade_permissions[level] != allowed: 102 self.upgrade_permissions[level] = allowed 103 104 UpgradePermissionsChanged.broadcast(self)
105 106 @property
107 - def inhabitants(self):
108 """Returns number of inhabitants (sum of inhabitants of its buildings)""" 109 return sum([building.inhabitants for building in self.buildings])
110 111 @property
112 - def cumulative_running_costs(self):
113 """Return sum of running costs of all buildings""" 114 return sum([building.running_costs for building in self.buildings])
115 116 @property
117 - def cumulative_taxes(self):
118 """Return sum of all taxes paid in this settlement in 1 tax round""" 119 return sum([building.last_tax_payed for building in self.buildings if 120 hasattr(building, 'last_tax_payed')])
121
122 - def get_residentials_of_lvl_for_happiness(self, level, min_happiness=0, max_happiness=101):
123 is_valid_residential = lambda building: (hasattr(building, 'happiness') and 124 min_happiness <= building.happiness < max_happiness) and \ 125 (hasattr(building, 'level') and building.level == level) 126 return len(list(filter(is_valid_residential, self.buildings)))
127 128 @property
129 - def balance(self):
130 """Returns sum(income) - sum(expenses) for settlement""" 131 return self.cumulative_taxes + self.get_component(TradePostComponent).sell_income \ 132 - self.cumulative_running_costs - self.get_component(TradePostComponent).buy_expenses
133 134 @property
135 - def island(self):
136 """Returns the island this settlement is on""" 137 return self.session.world.get_island(self.warehouse.position.origin)
138
139 - def level_upgrade(self, lvl):
140 """Upgrades settlement to a new tier. 141 It only delegates the upgrade to its buildings.""" 142 for building in self.buildings: 143 building.level_upgrade(lvl)
144
145 - def save(self, db, islandid):
146 super().save(db) 147 148 db("INSERT INTO settlement (rowid, island, owner) VALUES(?, ?, ?)", 149 self.worldid, islandid, self.owner.worldid) 150 for res, amount in self.produced_res.items(): 151 db("INSERT INTO settlement_produced_res (settlement, res, amount) VALUES(?, ?, ?)", 152 self.worldid, res, amount) 153 for level in range(TIER.CURRENT_MAX + 1): 154 db("INSERT INTO settlement_level_properties (settlement, level, upgrading_allowed, tax_setting) VALUES(?, ?, ?, ?)", 155 self.worldid, level, self.upgrade_permissions[level], self.tax_settings[level]) 156 157 # dump ground data via json, it's orders of magnitude faster than sqlite 158 data = json.dumps(list(self.ground_map.keys())) 159 db("INSERT INTO settlement_tiles(rowid, data) VALUES(?, ?)", self.worldid, data)
160 161 @classmethod
162 - def load(cls, db, worldid, session, island):
163 self = cls.__new__(cls) 164 self.session = session 165 super(Settlement, self).load(db, worldid) 166 167 owner = db("SELECT owner FROM settlement WHERE rowid = ?", worldid)[0][0] 168 upgrade_permissions = {} 169 tax_settings = {} 170 for level, allowed, tax in db("SELECT level, upgrading_allowed, tax_setting FROM settlement_level_properties WHERE settlement = ?", worldid): 171 upgrade_permissions[level] = allowed 172 tax_settings[level] = tax 173 self.__init(session, WorldObject.get_object_by_id(owner), upgrade_permissions, tax_settings) 174 175 # load the settlement tile map 176 tile_data = db("SELECT data FROM settlement_tiles WHERE rowid = ?", worldid)[0][0] 177 coords_list = [tuple(raw_coords) for raw_coords in json.loads(tile_data)] # json saves tuples as list 178 for coords in coords_list: 179 tile = island.ground_map[coords] 180 self.ground_map[coords] = tile 181 tile.settlement = self 182 183 # load all buildings in this settlement 184 from horizons.world import load_building 185 for building_id, building_type in \ 186 db("SELECT rowid, type FROM building WHERE location = ?", worldid): 187 building = load_building(session, db, building_type, building_id) 188 if building_type == BUILDINGS.WAREHOUSE: 189 self.warehouse = building 190 191 for res, amount in db("SELECT res, amount FROM settlement_produced_res WHERE settlement = ?", worldid): 192 self.produced_res[res] = amount 193 194 return self
195
196 - def get_tiles_in_radius(self, location, radius, include_self):
197 """Returns tiles in radius of location. 198 This is a generator. 199 @param location: anything that supports get_radius_coordinates (usually Rect). 200 @param include_self: bool, whether to include the coordinates in location 201 """ 202 for coord in location.get_radius_coordinates(radius, include_self): 203 try: 204 yield self.ground_map[coord] 205 except KeyError: 206 pass
207
208 - def add_building(self, building, load=False):
209 """Adds a building to the settlement. 210 This does not set building.settlement, it must be set beforehand. 211 @see Island.add_building 212 """ 213 self.buildings.append(building) 214 if building.id in self.buildings_by_id: 215 self.buildings_by_id[building.id].append(building) 216 else: 217 self.buildings_by_id[building.id] = [building] 218 component = building.get_component(Producer) 219 if component and component.produces_resource: 220 finished = self.settlement_building_production_finished 221 building.get_component(Producer).add_production_finished_listener(finished) 222 if not load and not building.buildable_upon and self.buildability_cache: 223 self.buildability_cache.modify_area([coords for coords in building.position.tuple_iter()]) 224 if hasattr(self.owner, 'add_building'): 225 # notify interested players of added building 226 self.owner.add_building(building)
227
228 - def remove_building(self, building):
229 """Properly removes a building from the settlement""" 230 if building not in self.buildings: 231 self.log.debug("Building %s can not be removed from settlement", building.id) 232 return 233 self.buildings.remove(building) 234 self.buildings_by_id[building.id].remove(building) 235 component = building.get_component(Producer) 236 if component and component.produces_resource: 237 finished = self.settlement_building_production_finished 238 building.get_component(Producer).remove_production_finished_listener(finished) 239 if not building.buildable_upon and self.buildability_cache: 240 self.buildability_cache.add_area([coords for coords in building.position.tuple_iter()]) 241 if hasattr(self.owner, 'remove_building'): 242 # notify interested players of removed building 243 self.owner.remove_building(building)
244
245 - def count_buildings(self, id):
246 """Returns the number of buildings in the settlement that are of the given type.""" 247 return len(self.buildings_by_id.get(id, []))
248
249 - def settlement_building_production_finished(self, building, produced_res):
250 """Callback function for registering the production of resources.""" 251 for res, amount in produced_res.items(): 252 self.produced_res[res] += amount
253
254 - def __init_inventory_checker(self):
255 """Check for changed inventories every 4 ticks.""" 256 storage = self.get_component(StorageComponent) 257 self.__inventory_checker = InventoryChecker(SettlementInventoryUpdated, storage, 4)
258
259 - def end(self):
260 self.buildability_cache = None 261 self.session = None 262 self.owner = None 263 self.buildings = None 264 self.ground_map = None 265 self.produced_res = None 266 self.buildings_by_id = None 267 self.warehouse = None 268 if hasattr(self, '__inventory_checker'): 269 self.__inventory_checker.remove()
270