Exemplo n.º 1
0
    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:
Exemplo n.º 2
0
    def __init(self, origin, filename, preview=False):
        """
		Load the actual island from a file
		@param origin: Point
		@param filename: String, filename of island db or random map id
		@param preview: flag, map preview mode
		"""
        self.file = filename
        self.origin = origin
        db = self._get_island_db()

        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, action_id, rotation) in db(
            "SELECT x, y, ground_id, action_id, rotation FROM ground"
        ):  # Load grounds
            if not preview:  # actual game, need actual tiles
                ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y)
                ground.act(action_id, rotation)
            else:
                ground = Point(self.origin.x + rel_x, self.origin.y + rel_y)
                ground.classes = tuple()
                ground.settlement = None
                # 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

        # 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)

        if not preview:  # this isn't needed for previews, but it is in actual games
            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:
Exemplo n.º 3
0
	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:
Exemplo n.º 4
0
	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:
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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
Exemplo n.º 7
0
class Island(BuildingOwner, WorldObject):
    """The Island class represents an island. It contains a list of all things on the map
	that belong to the island. This comprises ground tiles as well as buildings,
	nature objects (which are buildings) and units.
	All those objects also have a reference to the island, making it easy to determine to which island the instance belongs.
	An Island instance is created during 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 ground tiles 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 :)
	NOTE: The components work a bit different, but this code here is mostly not component oriented.

	To continue hacking, check out the __init() function now.
	"""

    log = logging.getLogger("world.island")

    def __init__(self, db, islandid, session, preview=False):
        """
		@param db: db instance with island table
		@param islandid: id of island in that table
		@param session: reference to Session instance
		@param preview: flag, map preview mode
		"""
        super(Island, self).__init__(worldid=islandid)

        if False:
            from horizons.session import Session

            assert isinstance(session, Session)
        self.session = session

        x, y, filename = db("SELECT x, y, file FROM island WHERE rowid = ? - 1000", islandid)[0]
        self.__init(Point(x, y), filename, preview=preview)

        if not preview:
            # create building indexers
            from horizons.world.units.animal import WildAnimal

            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 = Settlement.load(db, settlement_id, self.session, self)
            self.settlements.append(settlement)

        if not preview:
            # 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 _get_island_db(self):
        # check if filename is a random map
        if random_map.is_random_island_id_string(self.file):
            # it's a random map id, create this map and load it
            return random_map.create_random_island(self.file)
        return DbReader(self.file)  # Create a new DbReader instance to load the maps file.

    def __init(self, origin, filename, preview=False):
        """
		Load the actual island from a file
		@param origin: Point
		@param filename: String, filename of island db or random map id
		@param preview: flag, map preview mode
		"""
        self.file = filename
        self.origin = origin
        db = self._get_island_db()

        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, action_id, rotation) in db(
            "SELECT x, y, ground_id, action_id, rotation FROM ground"
        ):  # Load grounds
            if not preview:  # actual game, need actual tiles
                ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y)
                ground.act(action_id, rotation)
            else:
                ground = Point(self.origin.x + rel_x, self.origin.y + rel_y)
                ground.classes = tuple()
                ground.settlement = None
                # 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

        # 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)

        if not preview:  # this isn't needed for previews, but it is in actual games
            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:
		The next step will be an overview of the component system, which you will need
		to understand in order to see how our actual game object (buildings, units) work. Please proceed to horizons/world/componentholder.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 save_map(self, db):
        """Saves the ground into the given database (used for saving maps, not saved games)."""
        db(
            "CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL, action_id TEXT NOT NULL, rotation INTEGER NOT NULL)"
        )
        db("CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)")
        source_db = self._get_island_db()
        db("BEGIN")
        db.execute_many(
            "INSERT INTO ground VALUES(?, ?, ?, ?, ?)",
            source_db("SELECT x, y, ground_id, action_id, rotation FROM ground"),
        )
        db("COMMIT")

    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 add_settlement(self, position, radius, player, load=False):
        """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 warehouse
		@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)
        settlement.initialize()
        self.add_existing_settlement(position, radius, settlement, load)
        # 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,
        )

        NewSettlement.broadcast(self, settlement)

        return settlement

    def add_existing_settlement(self, position, radius, settlement, load=False):
        """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
		@param load: whether it has been called during load"""
        if settlement not in self.settlements:
            self.settlements.append(settlement)
        if not load:
            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:
		"""
        settlement_tiles_changed = []
        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
                    Minimap.update(coord)
                    self._register_change(coord[0], coord[1])
                    settlement_tiles_changed.append(tile)

                    # 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
                # found a new building, that is now in settlement radius
                # 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)

        if settlement_tiles_changed:
            SettlementRangeChanged.broadcast(settlement, settlement_tiles_changed)

    def add_building(self, building, player, load=False):
        """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
		@param load: boolean, whether it has been called during loading"""
        building = super(Island, self).add_building(building, player, load=load)
        if not load:
            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)
        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())
            if not load:
                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, where, radius=1):
        """Returns tiles around point with specified radius.
		@param point: instance of Point, or object with get_surrounding()"""
        if hasattr(where, "get_surrounding"):
            coords = where.get_surrounding(include_corners=False)
        else:  # assume Point
            coords = Circle(where, radius).tuple_iter()
        for position in coords:
            tile = self.get_tile_tuple(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
                    animal = Entities.units[UNITS.WILD_ANIMAL_CLASS](self, x=point.x, y=point.y, session=self.session)
                    animal.initialize()
                    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

        def calc_cache(size_x, size_y):
            d = {}
            for (x, y) in self.ground_map:
                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:
                    d[(x, y)] = self.last_change_id
            return d

        class LazyDict(dict):
            def __getitem__(self, x):
                try:
                    return super(LazyDict, self).__getitem__(x)
                except KeyError:
                    val = self[x] = calc_cache(*x)
                    return val

        self.last_changed = LazyDict()

    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

    def end(self):
        # NOTE: killing animals before buildings is an optimisation, else they would
        # keep searching for new trees every time a tree is torn down.
        for wild_animal in (wild_animal for wild_animal in self.wild_animals):
            wild_animal.remove()
        super(Island, self).end()
        for settlement in self.settlements:
            settlement.end()
        self.wild_animals = None
        self.ground_map = None
        self.path_nodes = None
        self.building_indexers = None
Exemplo n.º 8
0
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