def testRect(self): r1 = Rect(Point(0,0), 1, 1) r2 = Rect(0, 0, 1 ,1) r3 = Rect(Point(2, 2), 1, 1) self.assertEqual(r1, r2) self.assertTrue(r1 == r2) self.assertFalse(r1.contains(Point(-1,-1))) self.assertTrue(r2.contains(Point(0,0))) self.assertTrue(r2.contains(Point(1,1))) self.assertTrue(r1.intersects(r2)) self.assertFalse(r1.intersects(r3))
class Island(WorldObject): """The Island class represents an Island by keeping a list of all instances on the map, that belong to the island. The island variable is also set on every instance that belongs to an island, making it easy to determine to which island the instance belongs, when selected. An Island instance is created at map creation, when all tiles are added to the map. @param origin: Point instance - Position of the (0, 0) ground tile. @param filename: file from which the island is loaded. Each island holds some important attributes: * grounds - All grounds that belong to the island are referenced here. * grounds_map - a dictionary that binds tuples of coordinates with a reference to the tile: { (x, y): tileref, ...} This is important for pathfinding and quick tile fetching. * buildings - a list of all Building instances that are present on the island. * settlements - a list of all Settlement instances that are present on the island. * path_nodes - a special dictionary used by the pather to save paths. TUTORIAL: Why do we use a separate __init() function, and do not use the __init__() function? Simple, if we load the game, the class is not loaded as new instance, so the __init__ function is not called. Rather the load function is called. So everything that new classes and loaded classes share to initialize, comes into the __init() function. This is the common way of doing this in Unknown Horizons, so better get used to it :) To continue hacking, check out the __init() function now. """ log = logging.getLogger("world.island") def __init__(self, db, islandid, session): """ @param db: db instance with island table @param islandid: id of island in that table @param session: reference to Session instance """ self.session = session # an island is always loaded from db, so __init__() basically is load() super(Island, self).load(db, islandid) x, y, filename = db("SELECT x, y, file FROM island WHERE rowid = ? - 1000", islandid)[0] self.__init(Point(x, y), filename) # load settlements for (settlement_id,) in db("SELECT rowid FROM settlement WHERE island = ?", islandid): Settlement.load(db, settlement_id, self.session, self) # load buildings from horizons.world import load_building for (building_worldid, building_typeid) in \ db("SELECT rowid, type FROM building WHERE location = ?", islandid): load_building(self.session, db, building_typeid, building_worldid) def __init(self, origin, filename): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id """ self.file = filename self.origin = origin # check if filename is a random map if random_map.is_random_island_id_string(filename): # it's a random map id, create this map and load it db = random_map.create_random_island(filename) else: db = DbReader(filename) # Create a new DbReader instance to load the maps file. p_x, p_y, width, height = db("select (min(x) + ?), (min(y) + ?), (1 + max(x) - min(x)), (1 + max(y) - min(y)) from ground", self.origin.x, self.origin.y)[0] # rect for quick checking if a tile isn't on this island # NOTE: it contains tiles, that are not on the island! self.rect = Rect(Point(p_x, p_y), width, height) self.ground_map = {} for (rel_x, rel_y, ground_id) in db("select x, y, ground_id from ground"): # Load grounds ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) # These are important for pathfinding and building to check if the ground tile # is blocked in any way. self.ground_map[(ground.x, ground.y)] = ground self.settlements = [] self.buildings = [] self.provider_buildings = ProviderHandler() self.wild_animals = [] self.path_nodes = IslandPathNodes(self) # repopulate wild animals every 2 mins if they die out. Scheduler().add_new_object(self.check_wild_animal_population, self, Scheduler().get_ticks(120), -1) """TUTORIAL: To continue hacking, you should now take off to the real fun stuff and check out horizons/world/building/__init__.py. """ def save(self, db): db("INSERT INTO island (rowid, x, y, file) VALUES (? - 1000, ?, ?, ?)", self.worldid, self.origin.x, self.origin.y, self.file) for building in self.buildings: building.save(db) for settlement in self.settlements: settlement.save(db, self.worldid) for animal in self.wild_animals: animal.save(db) def get_coordinates(self): """Returns list of coordinates, that are on the island.""" return self.ground_map.keys() def get_tile(self, point): """Returns whether a tile is on island or not. @param point: Point contains position of the tile. @return: tile instance if tile is on island, else None.""" try: return self.ground_map[(point.x, point.y)] except KeyError: return None def get_tile_tuple(self, tup): """Overloaded get_tile, takes a tuple as argument""" try: return self.ground_map[tup] except KeyError: return None def get_tiles_tuple(self, tuples): """Same as get_tile, but takes a list of tuples. @param tuples: iterable of tuples @return: list of tiles""" for tup in tuples: if tup in self.ground_map: yield self.ground_map[tup] def get_building(self, point): """Returns the building at the point @param point: position of the tile to look on @return: Building class instance or None if none is found. """ try: return self.ground_map[point.to_tuple()].object except KeyError: return None def get_settlement(self, point): """Look for a settlement on a specific tile @param point: Point to look on @return: Settlement at point, or None""" try: return self.get_tile(point).settlement # some tiles might be none, so we have to catch that error here except AttributeError: return None def get_settlements(self, rect, player = None): """Returns the list of settlements for the coordinates describing a rect. @param rect: Area to search for settlements @return: list of Settlement instances at that position.""" settlements = set() if self.rect.intersects(rect): for point in rect: try: if player is None or self.get_tile(point).settlement.owner == player: settlements.add( self.get_tile(point).settlement ) except AttributeError: # some tiles don't have settlements, we don't explicitly check for them cause # its faster this way. pass settlements.discard(None) # None values might have been added, we don't want them return list(settlements) def add_settlement(self, position, radius, player): """Adds a settlement to the island at the position x, y with radius as area of influence. @param position: Rect describing the position of the new branch office @param radius: int radius of the area of influence. @param player: int id of the player that owns the settlement""" settlement = Settlement(self.session, player, self) self.add_existing_settlement(position, radius, settlement) # TODO: Move this to command, this message should not appear while loading self.session.ingame_gui.message_widget.add(position.center().x, \ position.center().y, \ 'NEW_SETTLEMENT', \ {'player':player.name}, \ self.session.world.player == player) self.session.world.notify_new_settlement() return settlement def add_existing_settlement(self, position, radius, settlement): """Same as add_settlement, but uses settlement from parameter. May also be called for extension of an existing settlement by a new building. (this is useful for loading, where every loaded building extends the radius of its settlement). @param position: Rect""" if settlement not in self.settlements: self.settlements.append(settlement) self.assign_settlement(position, radius, settlement) self.session.campaign_eventhandler.check_events(CONDITIONS.settlements_num_greater) return settlement def assign_settlement(self, position, radius, settlement): """Assigns the settlement property to tiles within the circle defined by \ position and radius. @param position: Rect @param radius: @param settlement: """ for coord in position.get_radius_coordinates(radius, include_self=True): tile = self.get_tile_tuple(coord) if tile is not None: if tile.settlement == settlement: continue if tile.settlement is None: tile.settlement = settlement self.session.ingame_gui.minimap.update(coord) settlement.ground_map[coord] = tile # only add tiles used for coloring ranges if ( 'constructible' in tile.classes or 'coastline' in tile.classes ): settlement.tilequadtree.add_tile(tile) building = tile.object # assign buildings on tiles to settlement if building is not None and building.settlement is None: building.settlement = settlement building.owner = settlement.owner settlement.buildings.append(building) #TODO: inherit resources etc def add_building(self, building, player): """Adds a building to the island at the position x, y with player as the owner. @param building: Building class instance of the building that is to be added. @param player: int id of the player that owns the settlement""" if building.extends_settlement: for building.settlement in self.get_settlements(building.position, player): self.assign_settlement(building.position, building.radius, building.settlement) break # Set all tiles in the buildings position(rect) for point in building.position: tile = self.get_tile(point) tile.blocked = True # Set tile blocked tile.object = building # Set tile's object to the building self.path_nodes.reset_tile_walkability(point.to_tuple()) self.buildings.append(building) if building.settlement is not None: building.settlement.buildings.append(building) building.init() return building def remove_building(self, building): assert building.island == self # Reset the tiles this building was covering for point in building.position: tile = self.get_tile(point) tile.blocked = False tile.object = None self.path_nodes.reset_tile_walkability(point.to_tuple()) if building.settlement is not None: building.settlement.buildings.remove(building) assert(building not in building.settlement.buildings) # Remove this building from the buildings list self.buildings.remove(building) assert building not in self.buildings def get_surrounding_tiles(self, point, radius = 1): """Returns tiles around point with specified radius. @param point: instance of Point""" for position in Circle(point, radius): tile = self.get_tile(position) if tile is not None: yield tile def get_tiles_in_radius(self, location, radius, include_self): """Returns tiles in radius of location. This is a generator. @param location: anything that supports get_radius_coordinates (usually Rect). @param include_self: bool, whether to include the coordinates in location """ for coord in location.get_radius_coordinates(radius, include_self): try: yield self.ground_map[coord] except KeyError: pass @decorators.make_constants() def get_providers_in_range(self, circle, res=None, reslist=None, player=None): """Returns all instances of provider within the specified circle. NOTE: Specifing the res parameter is usually a huge speed gain. @param circle: instance of Circle @param res: optional; only return providers that provide res. conflicts with reslist @param reslist: optionally; list of res to search providers for. conflicts with res @param player: Player instance, only buildings belonging to this player @return: list of providers""" assert not (bool(res) and bool(reslist)) if res is not None: provider_list = self.provider_buildings.provider_by_resources[res] elif reslist: provider_list = set() for _res in reslist: provider_list = provider_list.union(self.provider_buildings.provider_by_resources[_res]) else: # worst case: search all provider buildings provider_list = self.provider_buildings possible_providers = [] for provider in provider_list: if (player is None or player == provider.owner) and \ provider.position.distance_to_circle(circle) == 0: possible_providers.append(provider) return possible_providers def __iter__(self): for i in self.get_coordinates(): yield i def check_wild_animal_population(self): """Creates a wild animal if they died out.""" self.log.debug("Checking wild animal population: %s", len(self.wild_animals)) if len(self.wild_animals) == 0: # find a tree where we can place it for building in self.buildings: if building.id == BUILDINGS.TREE_CLASS: point = building.position.origin Entities.units[UNITS.WILD_ANIMAL_CLASS](self, x=point.x, y=point.y, session=self.session) return
class Island(BuildingOwner, WorldObject): """The Island class represents an Island by keeping a list of all instances on the map, that belong to the island. The island variable is also set on every instance that belongs to an island, making it easy to determine to which island the instance belongs, when selected. An Island instance is created at map creation, when all tiles are added to the map. @param origin: Point instance - Position of the (0, 0) ground tile. @param filename: file from which the island is loaded. Each island holds some important attributes: * grounds - All grounds that belong to the island are referenced here. * grounds_map - a dictionary that binds tuples of coordinates with a reference to the tile: { (x, y): tileref, ...} This is important for pathfinding and quick tile fetching. * buildings - a list of all Building instances that are present on the island. * settlements - a list of all Settlement instances that are present on the island. * path_nodes - a special dictionary used by the pather to save paths. TUTORIAL: Why do we use a separate __init() function, and do not use the __init__() function? Simple, if we load the game, the class is not loaded as new instance, so the __init__ function is not called. Rather the load function is called. So everything that new classes and loaded classes share to initialize, comes into the __init() function. This is the common way of doing this in Unknown Horizons, so better get used to it :) To continue hacking, check out the __init() function now. """ log = logging.getLogger("world.island") def __init__(self, db, islandid, session): """ @param db: db instance with island table @param islandid: id of island in that table @param session: reference to Session instance """ super(Island, self).__init__(worldid=islandid) self.session = session x, y, filename = db( "SELECT x, y, file FROM island WHERE rowid = ? - 1000", islandid)[0] self.__init(Point(x, y), filename) # create building indexers self.building_indexers = {} self.building_indexers[BUILDINGS.TREE_CLASS] = BuildingIndexer( WildAnimal.walking_range, self, self.session.random) # load settlements for (settlement_id, ) in db( "SELECT rowid FROM settlement WHERE island = ?", islandid): Settlement.load(db, settlement_id, self.session) # load buildings from horizons.world import load_building for (building_worldid, building_typeid) in \ db("SELECT rowid, type FROM building WHERE location = ?", islandid): load_building(self.session, db, building_typeid, building_worldid) def __init(self, origin, filename): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id """ self.file = filename self.origin = origin # check if filename is a random map if random_map.is_random_island_id_string(filename): # it's a random map id, create this map and load it db = random_map.create_random_island(filename) else: db = DbReader( filename ) # Create a new DbReader instance to load the maps file. p_x, p_y, width, height = db( "SELECT (MIN(x) + ?), (MIN(y) + ?), (1 + MAX(x) - MIN(x)), (1 + MAX(y) - MIN(y)) FROM ground", self.origin.x, self.origin.y)[0] # rect for quick checking if a tile isn't on this island # NOTE: it contains tiles, that are not on the island! self.rect = Rect(Point(p_x, p_y), width, height) self.ground_map = {} for (rel_x, rel_y, ground_id ) in db("SELECT x, y, ground_id FROM ground"): # Load grounds ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) # These are important for pathfinding and building to check if the ground tile # is blocked in any way. self.ground_map[(ground.x, ground.y)] = ground self.settlements = [] self.wild_animals = [] self.path_nodes = IslandPathNodes(self) # repopulate wild animals every 2 mins if they die out. Scheduler().add_new_object(self.check_wild_animal_population, self, Scheduler().get_ticks(120), -1) """TUTORIAL: To continue hacking, you should now take off to the real fun stuff and check out horizons/world/building/__init__.py. """ def save(self, db): super(Island, self).save(db) db("INSERT INTO island (rowid, x, y, file) VALUES (? - 1000, ?, ?, ?)", self.worldid, self.origin.x, self.origin.y, self.file) for settlement in self.settlements: settlement.save(db, self.worldid) for animal in self.wild_animals: animal.save(db) def get_coordinates(self): """Returns list of coordinates, that are on the island.""" return self.ground_map.keys() def get_tile(self, point): """Returns whether a tile is on island or not. @param point: Point contains position of the tile. @return: tile instance if tile is on island, else None.""" try: return self.ground_map[(point.x, point.y)] except KeyError: return None def get_tile_tuple(self, tup): """Overloaded get_tile, takes a tuple as argument""" try: return self.ground_map[tup] except KeyError: return None def get_tiles_tuple(self, tuples): """Same as get_tile, but takes a list of tuples. @param tuples: iterable of tuples @return: list of tiles""" for tup in tuples: if tup in self.ground_map: yield self.ground_map[tup] def get_building(self, point): """Returns the building at the point @param point: position of the tile to look on @return: Building class instance or None if none is found. """ try: return self.ground_map[point.to_tuple()].object except KeyError: return None def get_settlement(self, point): """Look for a settlement on a specific tile @param point: Point to look on @return: Settlement at point, or None""" try: return self.get_tile(point).settlement # some tiles might be none, so we have to catch that error here except AttributeError: return None def get_settlements(self, rect, player=None): """Returns the list of settlements for the coordinates describing a rect. @param rect: Area to search for settlements @return: list of Settlement instances at that position.""" settlements = set() if self.rect.intersects(rect): for point in rect: try: if player is None or self.get_tile( point).settlement.owner == player: settlements.add(self.get_tile(point).settlement) except AttributeError: # some tiles don't have settlements, we don't explicitly check for them cause # its faster this way. pass settlements.discard( None) # None values might have been added, we don't want them return list(settlements) def add_settlement(self, position, radius, player): """Adds a settlement to the island at the position x, y with radius as area of influence. @param position: Rect describing the position of the new branch office @param radius: int radius of the area of influence. @param player: int id of the player that owns the settlement""" settlement = Settlement(self.session, player) self.add_existing_settlement(position, radius, settlement) # TODO: Move this to command, this message should not appear while loading self.session.ingame_gui.message_widget.add(position.center().x, \ position.center().y, \ 'NEW_SETTLEMENT', \ {'player':player.name}, \ self.session.world.player == player) self.session.world.notify_new_settlement() return settlement def add_existing_settlement(self, position, radius, settlement): """Same as add_settlement, but uses settlement from parameter. May also be called for extension of an existing settlement by a new building. (this is useful for loading, where every loaded building extends the radius of its settlement). @param position: Rect""" if settlement not in self.settlements: self.settlements.append(settlement) self.assign_settlement(position, radius, settlement) self.session.scenario_eventhandler.check_events( CONDITIONS.settlements_num_greater) return settlement def assign_settlement(self, position, radius, settlement): """Assigns the settlement property to tiles within the circle defined by \ position and radius. @param position: Rect @param radius: @param settlement: """ for coord in position.get_radius_coordinates(radius, include_self=True): tile = self.get_tile_tuple(coord) if tile is not None: if tile.settlement == settlement: continue if tile.settlement is None: tile.settlement = settlement settlement.ground_map[coord] = tile self.session.ingame_gui.minimap.update(coord) building = tile.object # assign buildings on tiles to settlement if building is not None and building.settlement is None and \ building.island == self: # don't steal from other islands building.settlement = settlement building.owner = settlement.owner settlement.add_building(building) #TODO: inherit resources etc def add_building(self, building, player): """Adds a building to the island at the position x, y with player as the owner. @param building: Building class instance of the building that is to be added. @param player: int id of the player that owns the settlement""" building = super(Island, self).add_building(building, player) for building.settlement in self.get_settlements( building.position, player): self.assign_settlement(building.position, building.radius, building.settlement) break if building.settlement is not None: building.settlement.add_building(building) building.init() if building.id in self.building_indexers: self.building_indexers[building.id].add(building) # Reset the tiles this building was covering for point in building.position: self.path_nodes.reset_tile_walkability(point.to_tuple()) return building def remove_building(self, building): # removal code (before super call) if building.settlement is not None: building.settlement.remove_building(building) assert (building not in building.settlement.buildings) super(Island, self).remove_building(building) if building.id in self.building_indexers: self.building_indexers[building.id].remove(building) # Reset the tiles this building was covering (after building has been completely removed) for point in building.position: self.path_nodes.reset_tile_walkability(point.to_tuple()) def get_building_index(self, resource_id): if resource_id == RES.WILDANIMALFOOD_ID: return self.building_indexers[BUILDINGS.TREE_CLASS] return None def get_surrounding_tiles(self, point, radius=1): """Returns tiles around point with specified radius. @param point: instance of Point""" for position in Circle(point, radius): tile = self.get_tile(position) if tile is not None: yield tile def get_tiles_in_radius(self, location, radius, include_self): """Returns tiles in radius of location. This is a generator. @param location: anything that supports get_radius_coordinates (usually Rect). @param include_self: bool, whether to include the coordinates in location """ for coord in location.get_radius_coordinates(radius, include_self): try: yield self.ground_map[coord] except KeyError: pass def __iter__(self): return self.ground_map.iterkeys() def check_wild_animal_population(self): """Creates a wild animal if they died out.""" self.log.debug("Checking wild animal population: %s", len(self.wild_animals)) if len(self.wild_animals) == 0: # find a tree where we can place it for building in self.buildings: if building.id == BUILDINGS.TREE_CLASS: point = building.position.origin Entities.units[UNITS.WILD_ANIMAL_CLASS]( self, x=point.x, y=point.y, session=self.session) return
class Island(BuildingOwner, WorldObject): """The Island class represents an Island by keeping a list of all instances on the map, that belong to the island. The island variable is also set on every instance that belongs to an island, making it easy to determine to which island the instance belongs, when selected. An Island instance is created at map creation, when all tiles are added to the map. @param origin: Point instance - Position of the (0, 0) ground tile. @param filename: file from which the island is loaded. Each island holds some important attributes: * grounds - All grounds that belong to the island are referenced here. * grounds_map - a dictionary that binds tuples of coordinates with a reference to the tile: { (x, y): tileref, ...} This is important for pathfinding and quick tile fetching. * position - a Rect that borders the island with the smallest possible area * buildings - a list of all Building instances that are present on the island. * settlements - a list of all Settlement instances that are present on the island. * path_nodes - a special dictionary used by the pather to save paths. TUTORIAL: Why do we use a separate __init() function, and do not use the __init__() function? Simple, if we load the game, the class is not loaded as new instance, so the __init__ function is not called. Rather the load function is called. So everything that new classes and loaded classes share to initialize, comes into the __init() function. This is the common way of doing this in Unknown Horizons, so better get used to it :) To continue hacking, check out the __init() function now. """ log = logging.getLogger("world.island") def __init__(self, db, islandid, session): """ @param db: db instance with island table @param islandid: id of island in that table @param session: reference to Session instance """ super(Island, self).__init__(worldid=islandid) self.session = session x, y, filename = db("SELECT x, y, file FROM island WHERE rowid = ? - 1000", islandid)[0] self.__init(Point(x, y), filename) # create building indexers self.building_indexers = {} self.building_indexers[BUILDINGS.TREE_CLASS] = BuildingIndexer(WildAnimal.walking_range, self, self.session.random) # load settlements for (settlement_id,) in db("SELECT rowid FROM settlement WHERE island = ?", islandid): Settlement.load(db, settlement_id, self.session) # load buildings from horizons.world import load_building for (building_worldid, building_typeid) in \ db("SELECT rowid, type FROM building WHERE location = ?", islandid): load_building(self.session, db, building_typeid, building_worldid) def __init(self, origin, filename): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id """ self.file = filename self.origin = origin # check if filename is a random map if random_map.is_random_island_id_string(filename): # it's a random map id, create this map and load it db = random_map.create_random_island(filename) else: db = DbReader(filename) # Create a new DbReader instance to load the maps file. p_x, p_y, width, height = db("SELECT (MIN(x) + ?), (MIN(y) + ?), (1 + MAX(x) - MIN(x)), (1 + MAX(y) - MIN(y)) FROM ground", self.origin.x, self.origin.y)[0] # rect for quick checking if a tile isn't on this island # NOTE: it contains tiles, that are not on the island! self.rect = Rect(Point(p_x, p_y), width, height) self.ground_map = {} for (rel_x, rel_y, ground_id) in db("SELECT x, y, ground_id FROM ground"): # Load grounds ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) # These are important for pathfinding and building to check if the ground tile # is blocked in any way. self.ground_map[(ground.x, ground.y)] = ground self._init_cache() self.settlements = [] self.wild_animals = [] self.num_trees = 0 self.path_nodes = IslandPathNodes(self) # define the rectangle with the smallest area that contains every island tile its position min_x = min(zip(*self.ground_map.keys())[0]) max_x = max(zip(*self.ground_map.keys())[0]) min_y = min(zip(*self.ground_map.keys())[1]) max_y = max(zip(*self.ground_map.keys())[1]) self.position = Rect.init_from_borders(min_x, min_y, max_x, max_y) # repopulate wild animals every 2 mins if they die out. Scheduler().add_new_object(self.check_wild_animal_population, self, Scheduler().get_ticks(120), -1) """TUTORIAL: To continue hacking, you should now take off to the real fun stuff and check out horizons/world/building/__init__.py. """ def save(self, db): super(Island, self).save(db) db("INSERT INTO island (rowid, x, y, file) VALUES (? - 1000, ?, ?, ?)", self.worldid, self.origin.x, self.origin.y, self.file) for settlement in self.settlements: settlement.save(db, self.worldid) for animal in self.wild_animals: animal.save(db) def get_coordinates(self): """Returns list of coordinates, that are on the island.""" return self.ground_map.keys() def get_tile(self, point): """Returns whether a tile is on island or not. @param point: Point contains position of the tile. @return: tile instance if tile is on island, else None.""" try: return self.ground_map[(point.x, point.y)] except KeyError: return None def get_tile_tuple(self, tup): """Overloaded get_tile, takes a tuple as argument""" try: return self.ground_map[tup] except KeyError: return None def get_tiles_tuple(self, tuples): """Same as get_tile, but takes a list of tuples. @param tuples: iterable of tuples @return: list of tiles""" for tup in tuples: if tup in self.ground_map: yield self.ground_map[tup] def get_building(self, point): """Returns the building at the point @param point: position of the tile to look on @return: Building class instance or None if none is found. """ try: return self.ground_map[point.to_tuple()].object except KeyError: return None def get_settlement(self, point): """Look for a settlement on a specific tile @param point: Point to look on @return: Settlement at point, or None""" try: return self.get_tile(point).settlement # some tiles might be none, so we have to catch that error here except AttributeError: return None def get_settlements(self, rect, player = None): """Returns the list of settlements for the coordinates describing a rect. @param rect: Area to search for settlements @return: list of Settlement instances at that position.""" settlements = set() if self.rect.intersects(rect): for point in rect: try: if player is None or self.get_tile(point).settlement.owner == player: settlements.add( self.get_tile(point).settlement ) except AttributeError: # some tiles don't have settlements, we don't explicitly check for them cause # its faster this way. pass settlements.discard(None) # None values might have been added, we don't want them return list(settlements) def add_settlement(self, position, radius, player): """Adds a settlement to the island at the position x, y with radius as area of influence. @param position: Rect describing the position of the new branch office @param radius: int radius of the area of influence. @param player: int id of the player that owns the settlement""" settlement = Settlement(self.session, player) self.add_existing_settlement(position, radius, settlement) # TODO: Move this to command, this message should not appear while loading self.session.ingame_gui.message_widget.add(position.center().x, \ position.center().y, \ 'NEW_SETTLEMENT', \ {'player':player.name}, \ self.session.world.player == player) self.session.world.notify_new_settlement() return settlement def add_existing_settlement(self, position, radius, settlement): """Same as add_settlement, but uses settlement from parameter. May also be called for extension of an existing settlement by a new building. (this is useful for loading, where every loaded building extends the radius of its settlement). @param position: Rect""" if settlement not in self.settlements: self.settlements.append(settlement) self.assign_settlement(position, radius, settlement) self.session.scenario_eventhandler.check_events(CONDITIONS.settlements_num_greater) return settlement def assign_settlement(self, position, radius, settlement): """Assigns the settlement property to tiles within the circle defined by \ position and radius. @param position: Rect @param radius: @param settlement: """ for coord in position.get_radius_coordinates(radius, include_self=True): tile = self.get_tile_tuple(coord) if tile is not None: if tile.settlement == settlement: continue if tile.settlement is None: tile.settlement = settlement settlement.ground_map[coord] = tile self.session.ingame_gui.minimap.update(coord) self._register_change(coord[0], coord[1]) # notify all AI players when land ownership changes for player in self.session.world.players: if hasattr(player, 'on_settlement_expansion'): player.on_settlement_expansion(settlement, coord) building = tile.object # assign buildings on tiles to settlement if building is not None and building.settlement is None and \ building.island == self: # don't steal from other islands building.settlement = settlement building.owner = settlement.owner settlement.add_building(building) #TODO: inherit resources etc def add_building(self, building, player): """Adds a building to the island at the position x, y with player as the owner. @param building: Building class instance of the building that is to be added. @param player: int id of the player that owns the settlement""" building = super(Island, self).add_building(building, player) for building.settlement in self.get_settlements(building.position, player): self.assign_settlement(building.position, building.radius, building.settlement) break if building.settlement is not None: building.settlement.add_building(building) building.init() if building.id in self.building_indexers: self.building_indexers[building.id].add(building) # Reset the tiles this building was covering for point in building.position: self.path_nodes.reset_tile_walkability(point.to_tuple()) self._register_change(point.x, point.y) # keep track of the number of trees for animal population control if building.id == BUILDINGS.TREE_CLASS: self.num_trees += 1 return building def remove_building(self, building): # removal code (before super call) if building.settlement is not None: building.settlement.remove_building(building) assert(building not in building.settlement.buildings) super(Island, self).remove_building(building) if building.id in self.building_indexers: self.building_indexers[building.id].remove(building) # Reset the tiles this building was covering (after building has been completely removed) for point in building.position: self.path_nodes.reset_tile_walkability(point.to_tuple()) self._register_change(point.x, point.y) # keep track of the number of trees for animal population control if building.id == BUILDINGS.TREE_CLASS: self.num_trees -= 1 def get_building_index(self, resource_id): if resource_id == RES.WILDANIMALFOOD_ID: return self.building_indexers[BUILDINGS.TREE_CLASS] return None def get_surrounding_tiles(self, point, radius = 1): """Returns tiles around point with specified radius. @param point: instance of Point""" for position in Circle(point, radius): tile = self.get_tile(position) if tile is not None: yield tile def get_tiles_in_radius(self, location, radius, include_self): """Returns tiles in radius of location. This is a generator. @param location: anything that supports get_radius_coordinates (usually Rect). @param include_self: bool, whether to include the coordinates in location """ for coord in location.get_radius_coordinates(radius, include_self): try: yield self.ground_map[coord] except KeyError: pass def __iter__(self): return self.ground_map.iterkeys() def check_wild_animal_population(self): """Creates a wild animal if they died out.""" self.log.debug("Checking wild animal population: %s", len(self.wild_animals)) if len(self.wild_animals) == 0: # find a tree where we can place it for building in self.buildings: if building.id == BUILDINGS.TREE_CLASS: point = building.position.origin Entities.units[UNITS.WILD_ANIMAL_CLASS](self, x=point.x, y=point.y, session=self.session) return # we might not find a tree, but if that's the case, wild animals would die out anyway again, # so do nothing in this case. def _init_cache(self): """ initialises the cache that knows when the last time the buildability of a rectangle may have changed on this island """ self.last_change_id = -1 self.building_sizes = set() db_result = self.session.db("SELECT DISTINCT size_x, size_y FROM building WHERE button_name IS NOT NULL") for size_x, size_y in db_result: self.building_sizes.add((size_x, size_y)) self.building_sizes.add((size_y, size_x)) self.last_changed = {} for size in self.building_sizes: self.last_changed[size] = {} for (x, y) in self.ground_map: for size_x, size_y in self.building_sizes: all_on_island = True for dx in xrange(size_x): for dy in xrange(size_y): if (x + dx, y + dy) not in self.ground_map: all_on_island = False break if not all_on_island: break if all_on_island: self.last_changed[(size_x, size_y)][(x, y)] = self.last_change_id def _register_change(self, x, y): """ registers the possible buildability change of a rectangle on this island """ self.last_change_id += 1 for (area_size_x, area_size_y), building_areas in self.last_changed.iteritems(): for dx in xrange(area_size_x): for dy in xrange(area_size_y): coords = (x - dx, y - dy) # building area with origin at coords affected if coords in building_areas: building_areas[coords] = self.last_change_id