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

Source Code for Package horizons.world

  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 copy 
 23  import importlib 
 24  import json 
 25  import logging 
 26  from collections import deque 
 27  from functools import partial 
 28   
 29  import horizons.globals 
 30  from horizons.ai.aiplayer import AIPlayer 
 31  from horizons.ai.pirate import Pirate 
 32  from horizons.ai.trader import Trader 
 33  from horizons.command.unit import CreateUnit 
 34  from horizons.component.healthcomponent import HealthComponent 
 35  from horizons.component.selectablecomponent import SelectableComponent 
 36  from horizons.component.storagecomponent import StorageComponent 
 37  from horizons.constants import BUILDINGS, GAME, GROUND, MAP, PATHS, RES, UNITS 
 38  from horizons.entities import Entities 
 39  from horizons.messaging import LoadingProgress 
 40  from horizons.scheduler import Scheduler 
 41  from horizons.util.buildingindexer import BuildingIndexer 
 42  from horizons.util.color import Color 
 43  from horizons.util.savegameaccessor import SavegameAccessor 
 44  from horizons.util.shapes import Circle, Point, Rect 
 45  from horizons.util.worldobject import WorldObject 
 46  from horizons.world import worldutils 
 47  from horizons.world.buildingowner import BuildingOwner 
 48  from horizons.world.diplomacy import Diplomacy 
 49  from horizons.world.disaster.disastermanager import DisasterManager 
 50  from horizons.world.island import Island 
 51  from horizons.world.player import HumanPlayer 
 52  from horizons.world.units.weapon import Weapon 
53 54 55 -class World(BuildingOwner, WorldObject):
56 """The World class represents an Unknown Horizons map with all its units, grounds, buildings, etc. 57 58 It inherits from BuildingOwner, among other things, so it has building management capabilities. 59 There is always one big reference per building, which is stored in either the world, the island, 60 or the settlement. 61 62 The main components of the world are: 63 * players - a list of all the session's players - Player instances 64 * islands - a list of all the map's islands - Island instances 65 * grounds - a list of all the map's groundtiles 66 * ground_map - a dictionary that binds tuples of coordinates with a reference to the tile: 67 { (x, y): tileref, ...} 68 This is important for pathfinding and quick tile fetching. 69 * island_map - a dictionary that binds tuples of coordinates with a reference to the island 70 * ships - a list of all the ships ingame - horizons.world.units.ship.Ship instances 71 * ship_map - same as ground_map, but for ships 72 * session - reference to horizons.session.Session instance of the current game 73 * trader - The world's ingame free trader player instance (can control multiple ships) 74 * pirate - The world's ingame pirate player instance 75 TUTORIAL: You should now check out the _init() function. 76 """ 77 log = logging.getLogger("world") 78
79 - def __init__(self, session):
80 """ 81 @type session: horizons.session.Session 82 @param session: instance of session the world belongs to. 83 """ 84 self.inited = False 85 self.session = session 86 87 # create playerlist 88 self.players = [] 89 self.player = None # player sitting in front of this machine 90 self.trader = None 91 self.pirate = None 92 93 # create shiplist, which is currently used for saving ships 94 # and having at least one reference to them 95 self.ships = [] 96 self.ground_units = [] 97 98 self.islands = [] 99 100 super().__init__(worldid=GAME.WORLD_WORLDID)
101
102 - def end(self):
103 # destructor-like thing. 104 super().end() 105 106 # let the AI players know that the end is near to speed up destruction 107 for player in self.players: 108 if hasattr(player, 'early_end'): 109 player.early_end() 110 111 for ship in self.ships[:]: 112 ship.remove() 113 for island in self.islands: 114 island.end() 115 for player in self.players: 116 player.end() # end players after game entities, since they usually depend on players 117 118 self.session = None 119 self.properties = None 120 self.players = None 121 self.player = None 122 self.ground_map = None 123 self.fake_tile_map = None 124 self.full_map = None 125 self.island_map = None 126 self.water = None 127 self.ships = None 128 self.ship_map = None 129 self.fish_indexer = None 130 self.ground_units = None 131 132 if self.pirate is not None: 133 self.pirate.end() 134 self.pirate = None 135 136 if self.trader is not None: 137 self.trader.end() 138 self.trader = None 139 140 self.islands = None 141 self.diplomacy = None
142
143 - def _init(self, savegame_db, force_player_id=None, disasters_enabled=True):
144 """ 145 @param savegame_db: Dbreader with loaded savegame database 146 @param force_player_id: the worldid of the selected human player or default if None (debug option) 147 """ 148 """ 149 All essential and non-essential parts of the world are set up here, you don't need to 150 know everything that happens. 151 """ 152 # load properties 153 self.properties = {} 154 for (name, value) in savegame_db("SELECT name, value FROM map_properties"): 155 self.properties[name] = json.loads(value) 156 if 'disasters_enabled' not in self.properties: 157 # set on first init 158 self.properties['disasters_enabled'] = disasters_enabled 159 160 self._load_players(savegame_db, force_player_id) 161 162 # all static data 163 LoadingProgress.broadcast(self, 'world_load_map') 164 self.load_raw_map(savegame_db) 165 166 # load world buildings (e.g. fish) 167 LoadingProgress.broadcast(self, 'world_load_buildings') 168 buildings = savegame_db("SELECT rowid, type FROM building WHERE location = ?", self.worldid) 169 for (building_worldid, building_typeid) in buildings: 170 load_building(self.session, savegame_db, building_typeid, building_worldid) 171 172 # use a dict because it's directly supported by the pathfinding algo 173 LoadingProgress.broadcast(self, 'world_init_water') 174 self.water = {tile: 1.0 for tile in self.ground_map} 175 self._init_water_bodies() 176 self.sea_number = self.water_body[(self.min_x, self.min_y)] 177 for island in self.islands: 178 island.terrain_cache.create_sea_cache() 179 180 # assemble list of water and coastline for ship, that can drive through shallow water 181 # NOTE: this is rather a temporary fix to make the fisher be able to move 182 # since there are tile between coastline and deep sea, all non-constructible tiles 183 # are added to this list as well, which will contain a few too many 184 self.water_and_coastline = copy.copy(self.water) 185 for island in self.islands: 186 for coord, tile in island.ground_map.items(): 187 if 'coastline' in tile.classes or 'constructible' not in tile.classes: 188 self.water_and_coastline[coord] = 1.0 189 self._init_shallow_water_bodies() 190 self.shallow_sea_number = self.shallow_water_body[(self.min_x, self.min_y)] 191 192 # create ship position list. entries: ship_map[(x, y)] = ship 193 self.ship_map = {} 194 self.ground_unit_map = {} 195 196 if self.session.is_game_loaded(): 197 # there are 0 or 1 trader AIs so this is safe 198 trader_data = savegame_db("SELECT rowid FROM player WHERE is_trader = 1") 199 if trader_data: 200 self.trader = Trader.load(self.session, savegame_db, trader_data[0][0]) 201 # there are 0 or 1 pirate AIs so this is safe 202 pirate_data = savegame_db("SELECT rowid FROM player WHERE is_pirate = 1") 203 if pirate_data: 204 self.pirate = Pirate.load(self.session, savegame_db, pirate_data[0][0]) 205 206 # load all units (we do it here cause all buildings are loaded by now) 207 LoadingProgress.broadcast(self, 'world_load_units') 208 for (worldid, typeid) in savegame_db("SELECT rowid, type FROM unit ORDER BY rowid"): 209 Entities.units[typeid].load(self.session, savegame_db, worldid) 210 211 if self.session.is_game_loaded(): 212 # let trader and pirate command their ships. we have to do this here 213 # because ships have to be initialized for this, and they have 214 # to exist before ships are loaded. 215 if self.trader: 216 self.trader.load_ship_states(savegame_db) 217 if self.pirate: 218 self.pirate.finish_loading(savegame_db) 219 220 # load the AI stuff only when we have AI players 221 LoadingProgress.broadcast(self, 'world_setup_ai') 222 if any(isinstance(player, AIPlayer) for player in self.players): 223 AIPlayer.load_abstract_buildings(self.session.db) # TODO: find a better place for this 224 225 # load the AI players 226 # this has to be done here because otherwise the ships and other objects won't exist 227 for player in self.players: 228 if not isinstance(player, HumanPlayer): 229 player.finish_loading(savegame_db) 230 231 LoadingProgress.broadcast(self, 'world_load_stuff') 232 self._load_combat(savegame_db) 233 self._load_diplomacy(savegame_db) 234 self._load_disasters(savegame_db) 235 236 self.inited = True 237 """TUTORIAL: 238 To dig deeper, you should now continue to horizons/world/island.py, 239 to check out how buildings and settlements are added to the map."""
240
241 - def _load_combat(self, savegame_db):
242 # load ongoing attacks 243 if self.session.is_game_loaded(): 244 Weapon.load_attacks(self.session, savegame_db)
245
246 - def _load_diplomacy(self, savegame_db):
247 self.diplomacy = Diplomacy() 248 if self.session.is_game_loaded(): 249 self.diplomacy.load(self, savegame_db)
250
251 - def _load_disasters(self, savegame_db):
252 # disasters are only enabled if they are explicitly set to be enabled 253 disasters_disabled = not self.properties.get('disasters_enabled') 254 self.disaster_manager = DisasterManager(self.session, disabled=disasters_disabled) 255 if self.session.is_game_loaded(): 256 self.disaster_manager.load(savegame_db)
257
258 - def load_raw_map(self, savegame_db, preview=False):
259 self.map_name = savegame_db.map_name 260 261 # Load islands. 262 for (islandid,) in savegame_db("SELECT DISTINCT island_id + 1001 FROM ground"): 263 island = Island(savegame_db, islandid, self.session, preview=preview) 264 self.islands.append(island) 265 266 # Calculate map dimensions. 267 self.min_x, self.min_y, self.max_x, self.max_y = 0, 0, 0, 0 268 for island in self.islands: 269 self.min_x = min(island.position.left, self.min_x) 270 self.min_y = min(island.position.top, self.min_y) 271 self.max_x = max(island.position.right, self.max_x) 272 self.max_y = max(island.position.bottom, self.max_y) 273 self.min_x -= savegame_db.map_padding 274 self.min_y -= savegame_db.map_padding 275 self.max_x += savegame_db.map_padding 276 self.max_y += savegame_db.map_padding 277 278 self.map_dimensions = Rect.init_from_borders(self.min_x, self.min_y, self.max_x, self.max_y) 279 280 # Add water. 281 self.log.debug("Filling world with water...") 282 self.ground_map = {} 283 284 # big sea water tile class 285 if not preview: 286 default_grounds = Entities.grounds[self.properties.get('default_ground', '{:d}-straight'.format(GROUND.WATER[0]))] 287 288 fake_tile_class = Entities.grounds['-1-special'] 289 fake_tile_size = 10 290 for x in range(self.min_x - MAP.BORDER, self.max_x + MAP.BORDER, fake_tile_size): 291 for y in range(self.min_y - MAP.BORDER, self.max_y + MAP.BORDER, fake_tile_size): 292 fake_tile_x = x - 1 293 fake_tile_y = y + fake_tile_size - 1 294 if not preview: 295 # we don't need no references, we don't need no mem control 296 default_grounds(self.session, fake_tile_x, fake_tile_y) 297 for x_offset in range(fake_tile_size): 298 if self.min_x <= x + x_offset < self.max_x: 299 for y_offset in range(fake_tile_size): 300 if self.min_y <= y + y_offset < self.max_y: 301 self.ground_map[(x + x_offset, y + y_offset)] = fake_tile_class(self.session, fake_tile_x, fake_tile_y) 302 self.fake_tile_map = copy.copy(self.ground_map) 303 304 # Remove parts that are occupied by islands, create the island map and the full map. 305 self.island_map = {} 306 self.full_map = copy.copy(self.ground_map) 307 for island in self.islands: 308 for coords in island.ground_map: 309 if coords in self.ground_map: 310 self.full_map[coords] = island.ground_map[coords] 311 del self.ground_map[coords] 312 self.island_map[coords] = island
313
314 - def _load_players(self, savegame_db, force_player_id):
315 human_players = [] 316 for player_worldid, client_id in savegame_db("SELECT rowid, client_id FROM player WHERE is_trader = 0 and is_pirate = 0 ORDER BY rowid"): 317 player = None 318 # check if player is an ai 319 ai_data = self.session.db("SELECT class_package, class_name FROM ai WHERE client_id = ?", client_id) 320 if ai_data: 321 class_package, class_name = ai_data[0] 322 # import ai class and call load on it 323 module = importlib.import_module('horizons.ai.' + class_package) 324 ai_class = getattr(module, class_name) 325 player = ai_class.load(self.session, savegame_db, player_worldid) 326 else: # no ai 327 player = HumanPlayer.load(self.session, savegame_db, player_worldid) 328 self.players.append(player) 329 330 if client_id == horizons.globals.fife.get_uh_setting("ClientID"): 331 self.player = player 332 elif client_id is not None and not ai_data: 333 # possible human player candidate with different client id 334 human_players.append(player) 335 self.owner_highlight_active = False 336 self.health_visible_for_all_health_instances = False 337 338 if self.player is None: 339 # we have no human player. 340 # check if there is only one player with an id (i.e. human player) 341 # this would be the case if the savegame originates from a different installation. 342 # if there's more than one of this kind, we can't be sure what to select. 343 # TODO: create interface for selecting player, if we want this 344 if len(human_players) == 1: 345 # exactly one player, we can quite safely use this one 346 self.player = human_players[0] 347 elif not human_players and self.players: 348 # the first player should be the human-ai hybrid 349 self.player = self.players[0] 350 351 # set the human player to the forced value (debug option) 352 self.set_forced_player(force_player_id) 353 354 if self.player is None and self.session.is_game_loaded(): 355 self.log.warning('WARNING: Cannot autoselect a player because there ' 356 'are no or multiple candidates.')
357 358 @classmethod
359 - def _recognize_water_bodies(cls, map_dict):
360 """This function runs the flood fill algorithm on the water to make it easy 361 to recognize different water bodies.""" 362 moves = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] 363 364 n = 0 365 for coords, num in map_dict.items(): 366 if num is not None: 367 continue 368 369 map_dict[coords] = n 370 queue = deque([coords]) 371 while queue: 372 x, y = queue.popleft() 373 for dx, dy in moves: 374 coords2 = (x + dx, y + dy) 375 if coords2 in map_dict and map_dict[coords2] is None: 376 map_dict[coords2] = n 377 queue.append(coords2) 378 n += 1
379
380 - def _init_water_bodies(self):
381 """This function runs the flood fill algorithm on the water to make it easy 382 to recognize different water bodies.""" 383 self.water_body = dict.fromkeys(self.water) 384 self._recognize_water_bodies(self.water_body)
385
387 """This function runs the flood fill algorithm on the water and the coast to 388 make it easy to recognise different water bodies for fishers.""" 389 self.shallow_water_body = dict.fromkeys(self.water_and_coastline) 390 self._recognize_water_bodies(self.shallow_water_body)
391
392 - def init_fish_indexer(self):
393 radius = Entities.buildings[BUILDINGS.FISHER].radius 394 buildings = self.provider_buildings.provider_by_resources[RES.FISH] 395 self.fish_indexer = BuildingIndexer(radius, self.full_map, buildings=buildings)
396
397 - def init_new_world(self, trader_enabled, pirate_enabled, natural_resource_multiplier):
398 """ 399 This should be called if a new map is loaded (not a savegame, a fresh 400 map). In other words, when it is loaded for the first time. 401 402 NOTE: commands for creating the world objects are executed directly, 403 bypassing the manager. 404 This is necessary because else the commands would be transmitted 405 over the wire in network games. 406 407 @return: the coordinates of the players first ship 408 """ 409 410 # workaround: the creation of all the objects causes a lot of logging output we don't need. 411 # therefore, reset the levels for now 412 loggers_to_silence = {'world.production': None} 413 for logger_name in loggers_to_silence: 414 logger = logging.getLogger(logger_name) 415 loggers_to_silence[logger_name] = logger.getEffectiveLevel() 416 logger.setLevel(logging.WARN) 417 418 # add a random number of environmental objects 419 if natural_resource_multiplier != 0: 420 self._add_nature_objects(natural_resource_multiplier) 421 422 # reset loggers, see above 423 for logger_name, level in loggers_to_silence.items(): 424 logging.getLogger(logger_name).setLevel(level) 425 426 # add free trader 427 if trader_enabled: 428 self.trader = Trader(self.session, 99999, "Free Trader", Color()) 429 430 ret_coords = None 431 for player in self.players: 432 # Adding ships for the players 433 # hack to place the ship on the development map 434 point = self.get_random_possible_ship_position() 435 # Execute command directly, not via manager, because else it would be transmitted over the 436 # network to other players. Those however will do the same thing anyways. 437 ship = CreateUnit(player.worldid, UNITS.PLAYER_SHIP, point.x, point.y)(issuer=self.session.world.player) 438 # give ship basic resources 439 for res, amount in self.session.db("SELECT resource, amount FROM start_resources"): 440 ship.get_component(StorageComponent).inventory.alter(res, amount) 441 if player is self.player: 442 ret_coords = point.to_tuple() 443 444 # HACK: Store starting ship as first unit group, and select it 445 def _preselect_player_ship(player_ship): 446 sel_comp = player_ship.get_component(SelectableComponent) 447 sel_comp.select(reset_cam=True) 448 self.session.selected_instances = {player_ship} 449 self.session.ingame_gui.handle_selection_group(1, True) 450 sel_comp.show_menu()
451 select_ship = partial(_preselect_player_ship, ship) 452 Scheduler().add_new_object(select_ship, ship, run_in=0) 453 454 # load the AI stuff only when we have AI players 455 if any(isinstance(player, AIPlayer) for player in self.players): 456 AIPlayer.load_abstract_buildings(self.session.db) # TODO: find a better place for this 457 458 # add a pirate ship 459 if pirate_enabled: 460 self.pirate = Pirate(self.session, 99998, "Captain Blackbeard", Color()) 461 462 assert ret_coords is not None, "Return coords are None. No players loaded?" 463 return ret_coords
464
465 - def _add_nature_objects(self, natural_resource_multiplier):
466 worldutils.add_nature_objects(self, natural_resource_multiplier)
467
468 - def set_forced_player(self, force_player_id):
469 if force_player_id is not None: 470 for player in self.players: 471 if player.worldid == force_player_id: 472 self.player = player 473 break
474
475 - def get_random_possible_ground_unit_position(self):
476 """Returns a random position upon an island. 477 @return: Point""" 478 return worldutils.get_random_possible_ground_unit_position(self)
479
480 - def get_random_possible_ship_position(self):
481 """Returns a random position in water that is not at the border of the world. 482 @return: Point""" 483 return worldutils.get_random_possible_ship_position(self)
484
485 - def get_random_possible_coastal_ship_position(self):
486 """Returns a random position in water that is not at the border of the world 487 but on the coast of an island. 488 @return: Point""" 489 return worldutils.get_random_possible_coastal_ship_position(self)
490 491 #----------------------------------------------------------------------
492 - def get_tiles_in_radius(self, position, radius, shuffle=False):
493 """Returns all tiles in the radius around the point. 494 This is a generator; make sure you use it appropriately. 495 @param position: Point instance 496 @return List of tiles in radius. 497 """ 498 for point in self.get_points_in_radius(position, radius, shuffle): 499 yield self.get_tile(point)
500
501 - def get_points_in_radius(self, position, radius, shuffle=False):
502 """Returns all points in the radius around the point. 503 This is a generator; make sure you use it appropriately. 504 @param position: Point instance 505 @return List of points in radius. 506 """ 507 assert isinstance(position, Point) 508 points = Circle(position, radius) 509 if shuffle: 510 points = list(points) 511 self.session.random.shuffle(points) 512 for point in points: 513 if self.map_dimensions.contains_without_border(point): 514 # don't yield if point is not in map, those points don't exist 515 yield point
516
517 - def setup_player(self, id, name, color, clientid, local, is_ai, difficulty_level):
518 """Sets up a new Player instance and adds her to the active world. 519 Only used for new games. Loading old players is done in _init(). 520 @param local: bool, whether the player is the one sitting on front of this machine.""" 521 inv = self.session.db.get_player_start_res() 522 player = None 523 if is_ai: # a human controlled AI player 524 player = AIPlayer(self.session, id, name, color, clientid, difficulty_level) 525 else: 526 player = HumanPlayer(self.session, id, name, color, clientid, difficulty_level) 527 player.initialize(inv) # Componentholder init 528 if local: 529 self.player = player 530 self.players.append(player)
531
532 - def get_tile(self, point):
533 """Returns the ground at x, y. 534 @param point: coords as Point 535 @return: instance of Ground at x, y 536 """ 537 return self.full_map.get((point.x, point.y))
538 539 @property
540 - def settlements(self):
541 """Returns all settlements on world""" 542 settlements = [] 543 for i in self.islands: 544 settlements.extend(i.settlements) 545 return settlements
546
547 - def get_island(self, point):
548 """Returns the island for that coordinate. If none is found, returns None. 549 @param point: instance of Point""" 550 # NOTE: keep code synchronized with duplicated code below 551 return self.island_map.get(point.to_tuple())
552
553 - def get_island_tuple(self, tup):
554 """Overloaded from above""" 555 return self.island_map.get(tup)
556
557 - def get_islands_in_radius(self, point, radius):
558 """Returns all islands in a certain radius around a point. 559 @return set of islands in radius""" 560 islands = set() 561 for island in self.islands: 562 for tile in island.get_surrounding_tiles(point, radius=radius, 563 include_corners=False): 564 islands.add(island) 565 break 566 return islands
567
568 - def get_warehouses(self, position=None, radius=None, owner=None, include_tradeable=False):
569 """Returns all warehouses on the map, optionally only those in range 570 around the specified position. 571 @param position: Point or Rect instance. 572 @param radius: int radius to use. 573 @param owner: Player instance, list only warehouses belonging to this player. 574 @param include_tradeable also list the warehouses the owner can trade with 575 @return: List of warehouses. 576 """ 577 warehouses = [] 578 islands = [] 579 if radius is not None and position is not None: 580 islands = self.get_islands_in_radius(position, radius) 581 else: 582 islands = self.islands 583 584 for island in islands: 585 for settlement in island.settlements: 586 warehouse = settlement.warehouse 587 if (radius is None or position is None or 588 warehouse.position.distance(position) <= radius) and \ 589 (owner is None or warehouse.owner == owner or 590 (include_tradeable and self.diplomacy.can_trade(warehouse.owner, owner))): 591 warehouses.append(warehouse) 592 return warehouses
593
594 - def get_ships(self, position=None, radius=None):
595 """Returns all ships on the map, optionally only those in range 596 around the specified position. 597 @param position: Point or Rect instance. 598 @param radius: int radius to use. 599 @return: List of ships. 600 """ 601 if position is not None and radius is not None: 602 circle = Circle(position, radius) 603 return [ship for ship in self.ships if circle.contains(ship.position)] 604 else: 605 return self.ships
606
607 - def get_ground_units(self, position=None, radius=None):
608 """@see get_ships""" 609 if position is not None and radius is not None: 610 circle = Circle(position, radius) 611 return [unit for unit in self.ground_units if circle.contains(unit.position)] 612 else: 613 return self.ground_units
614
615 - def get_buildings(self, position=None, radius=None):
616 """@see get_ships""" 617 buildings = [] 618 if position is not None and radius is not None: 619 circle = Circle(position, radius) 620 for island in self.islands: 621 for building in island.buildings: 622 if circle.contains(building.position.center): 623 buildings.append(building) 624 return buildings 625 else: 626 return [b for b in island.buildings for island in self.islands]
627
628 - def get_all_buildings(self):
629 """Yields all buildings independent of owner""" 630 for island in self.islands: 631 for b in island.buildings: 632 yield b 633 for s in island.settlements: 634 for b in s.buildings: 635 yield b
636
637 - def get_health_instances(self, position=None, radius=None):
638 """Returns all instances that have health""" 639 instances = [] 640 for instance in self.get_ships(position, radius) + \ 641 self.get_ground_units(position, radius): 642 if instance.has_component(HealthComponent): 643 instances.append(instance) 644 return instances
645
646 - def save(self, db):
647 """Saves the current game to the specified db. 648 @param db: DbReader object of the db the game is saved to.""" 649 super().save(db) 650 if isinstance(self.map_name, list): 651 db("INSERT INTO metadata VALUES(?, ?)", 'random_island_sequence', ' '.join(self.map_name)) 652 else: 653 # the map name has to be simplified because the absolute paths won't be transferable between machines 654 simplified_name = self.map_name 655 if self.map_name.startswith(PATHS.USER_MAPS_DIR): 656 simplified_name = 'USER_MAPS_DIR:' + simplified_name[len(PATHS.USER_MAPS_DIR):] 657 db("INSERT INTO metadata VALUES(?, ?)", 'map_name', simplified_name) 658 659 for island in self.islands: 660 island.save(db) 661 for player in self.players: 662 player.save(db) 663 if self.trader is not None: 664 self.trader.save(db) 665 if self.pirate is not None: 666 self.pirate.save(db) 667 for unit in self.ships + self.ground_units: 668 unit.save(db) 669 self.diplomacy.save(db) 670 Weapon.save_attacks(db) 671 self.disaster_manager.save(db)
672
673 - def get_checkup_hash(self):
674 """Returns a collection of important game state values. Used to check if two mp games have diverged. 675 Not designed to be reliable.""" 676 # NOTE: don't include float values, they are represented differently in python 2.6 and 2.7 677 # and will differ at some insignificant place. Also make sure to handle them correctly in the game logic. 678 data = { 679 'rngvalue': self.session.random.random(), 680 'settlements': [], 681 'ships': [], 682 } 683 for island in self.islands: 684 # dicts usually aren't hashable, this makes them 685 # since defaultdicts appear, we discard values that can be autogenerated 686 # (those are assumed to default to something evaluating False) 687 dict_hash = lambda d: sorted(i for i in d.items() if i[1]) 688 for settlement in island.settlements: 689 storage_dict = settlement.get_component(StorageComponent).inventory._storage 690 entry = { 691 'owner': str(settlement.owner.worldid), 692 'inhabitants': str(settlement.inhabitants), 693 'cumulative_running_costs': str(settlement.cumulative_running_costs), 694 'cumulative_taxes': str(settlement.cumulative_taxes), 695 'inventory': str(dict_hash(storage_dict)) 696 } 697 data['settlements'].append(entry) 698 for ship in self.ships: 699 entry = { 700 'owner': str(ship.owner.worldid), 701 'position': ship.position.to_tuple(), 702 } 703 data['ships'].append(entry) 704 return data
705
706 - def toggle_owner_highlight(self):
707 renderer = self.session.view.renderer['InstanceRenderer'] 708 # Toggle flag that tracks highlight status. 709 self.owner_highlight_active = not self.owner_highlight_active 710 if self.owner_highlight_active: #show 711 for player in self.players: 712 red = player.color.r 713 green = player.color.g 714 blue = player.color.b 715 for settlement in player.settlements: 716 for tile in settlement.ground_map.values(): 717 renderer.addColored(tile._instance, red, green, blue) 718 else: 719 # "Hide": Do nothing after removing color highlights. 720 renderer.removeAllColored()
721
722 - def toggle_translucency(self):
723 """Make certain building types translucent""" 724 worldutils.toggle_translucency(self)
725
726 - def toggle_health_for_all_health_instances(self):
727 worldutils.toggle_health_for_all_health_instances(self)
728
729 730 -def load_building(session, db, typeid, worldid):
731 """Loads a saved building. Don't load buildings yourself in the game code.""" 732 return Entities.buildings[typeid].load(session, db, worldid)
733
734 735 -def load_raw_world(map_file):
736 WorldObject.reset() 737 world = World(session=None) 738 world.inited = True 739 world.load_raw_map(SavegameAccessor(map_file, True), preview=True) 740 return world
741