Пример #1
0
	def finish_loading(self, db):
		""" This is called separately because most objects are loaded after the player. """

		# load the ships

		for ship_id, state_id in db("SELECT rowid, state FROM ai_ship WHERE owner = ?", self.worldid):
			ship = WorldObject.get_object_by_id(ship_id)
			self.ships[ship] = self.shipStates[state_id]

		# load unit manager
		self.unit_manager = UnitManager.load(db, self)

		# load combat manager
		self.combat_manager = CombatManager.load(db, self)

		# load strategy manager
		self.strategy_manager = StrategyManager.load(db, self)

		# load BehaviorManager
		self.behavior_manager = BehaviorManager.load(db, self)

		# load the land managers
		for (worldid,) in db("SELECT rowid FROM ai_land_manager WHERE owner = ?", self.worldid):
			land_manager = LandManager.load(db, self, worldid)
			self.islands[land_manager.island.worldid] = land_manager

		# load the settlement managers and settlement foundation missions
		for land_manager in self.islands.itervalues():
			db_result = db("SELECT rowid FROM ai_settlement_manager WHERE land_manager = ?", land_manager.worldid)
			if db_result:
				settlement_manager = SettlementManager.load(db, self, db_result[0][0])
				self.settlement_managers.append(settlement_manager)
				self._settlement_manager_by_settlement_id[settlement_manager.settlement.worldid] = settlement_manager

				# load the foundation ship preparing missions
				db_result = db("SELECT rowid FROM ai_mission_prepare_foundation_ship WHERE settlement_manager = ?",
					settlement_manager.worldid)
				for (mission_id,) in db_result:
					self.missions.add(PrepareFoundationShip.load(db, mission_id, self.report_success, self.report_failure))
			else:
				mission_id = db("SELECT rowid FROM ai_mission_found_settlement WHERE land_manager = ?", land_manager.worldid)[0][0]
				self.missions.add(FoundSettlement.load(db, mission_id, self.report_success, self.report_failure))

		for settlement_manager in self.settlement_managers:
			# load the domestic trade missions
			db_result = db("SELECT rowid FROM ai_mission_domestic_trade WHERE source_settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(DomesticTrade.load(db, mission_id, self.report_success, self.report_failure))

			# load the special domestic trade missions
			db_result = db("SELECT rowid FROM ai_mission_special_domestic_trade WHERE source_settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(SpecialDomesticTrade.load(db, mission_id, self.report_success, self.report_failure))

			# load the international trade missions
			db_result = db("SELECT rowid FROM ai_mission_international_trade WHERE settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(InternationalTrade.load(db, mission_id, self.report_success, self.report_failure))
Пример #2
0
	def finish_loading(self, db):
		""" This is called separately because most objects are loaded after the player. """

		# load the ships

		for ship_id, state_id in db("SELECT rowid, state FROM ai_ship WHERE owner = ?", self.worldid):
			ship = WorldObject.get_object_by_id(ship_id)
			self.ships[ship] = self.shipStates[state_id]

		# load unit manager
		self.unit_manager = UnitManager.load(db, self)

		# load combat manager
		self.combat_manager = CombatManager.load(db, self)

		# load strategy manager
		self.strategy_manager = StrategyManager.load(db, self)

		# load BehaviorManager
		self.behavior_manager = BehaviorManager.load(db, self)

		# load the land managers
		for (worldid,) in db("SELECT rowid FROM ai_land_manager WHERE owner = ?", self.worldid):
			land_manager = LandManager.load(db, self, worldid)
			self.islands[land_manager.island.worldid] = land_manager

		# load the settlement managers and settlement foundation missions
		for land_manager in self.islands.itervalues():
			db_result = db("SELECT rowid FROM ai_settlement_manager WHERE land_manager = ?", land_manager.worldid)
			if db_result:
				settlement_manager = SettlementManager.load(db, self, db_result[0][0])
				self.settlement_managers.append(settlement_manager)
				self._settlement_manager_by_settlement_id[settlement_manager.settlement.worldid] = settlement_manager

				# load the foundation ship preparing missions
				db_result = db("SELECT rowid FROM ai_mission_prepare_foundation_ship WHERE settlement_manager = ?",
					settlement_manager.worldid)
				for (mission_id,) in db_result:
					self.missions.add(PrepareFoundationShip.load(db, mission_id, self.report_success, self.report_failure))
			else:
				mission_id = db("SELECT rowid FROM ai_mission_found_settlement WHERE land_manager = ?", land_manager.worldid)[0][0]
				self.missions.add(FoundSettlement.load(db, mission_id, self.report_success, self.report_failure))

		for settlement_manager in self.settlement_managers:
			# load the domestic trade missions
			db_result = db("SELECT rowid FROM ai_mission_domestic_trade WHERE source_settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(DomesticTrade.load(db, mission_id, self.report_success, self.report_failure))

			# load the special domestic trade missions
			db_result = db("SELECT rowid FROM ai_mission_special_domestic_trade WHERE source_settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(SpecialDomesticTrade.load(db, mission_id, self.report_success, self.report_failure))

			# load the international trade missions
			db_result = db("SELECT rowid FROM ai_mission_international_trade WHERE settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(InternationalTrade.load(db, mission_id, self.report_success, self.report_failure))
Пример #3
0
	def __init(self):
		self._enabled = True  # whether this player is enabled (currently disabled at the end of the game)
		self.world = self.session.world
		self.islands = {}
		self.settlement_managers = []
		self._settlement_manager_by_settlement_id = {}
		self.missions = set()
		self.fishers = []
		self.settlement_founder = SettlementFounder(self)
		self.unit_builder = UnitBuilder(self)
		self.unit_manager = UnitManager(self)
		self.combat_manager = CombatManager(self)
		self.strategy_manager = StrategyManager(self)
		self.behavior_manager = BehaviorManager(self)
		self.settlement_expansions = []  # [(coords, settlement)]
		self.goals = [DoNothingGoal(self)]
		self.special_domestic_trade_manager = SpecialDomesticTradeManager(self)
		self.international_trade_manager = InternationalTradeManager(self)
Пример #4
0
class AIPlayer(GenericAI):
    """This is the AI that builds settlements."""

    shipStates = Enum.get_extended(
        GenericAI.shipStates,
        'on_a_mission',
    )

    log = logging.getLogger("ai.aiplayer")
    tick_interval = 32
    tick_long_interval = 128

    def __init__(self, session, id, name, color, clientid, difficulty_level,
                 **kwargs):
        super(AIPlayer, self).__init__(session, id, name, color, clientid,
                                       difficulty_level, **kwargs)
        self.need_more_ships = False
        self.need_more_combat_ships = True
        self.need_feeder_island = False
        self.personality_manager = PersonalityManager(self)
        self.__init()
        Scheduler().add_new_object(Callback(self.finish_init), self, run_in=0)

    def start(self):
        """ Start the AI tick process. Try to space out their ticks evenly. """
        ai_players = 0
        position = None
        for player in self.world.players:
            if isinstance(player, AIPlayer):
                if player is self:
                    position = ai_players
                ai_players += 1
        run_in = self.tick_interval * position // ai_players + 1
        Scheduler().add_new_object(Callback(self.tick), self, run_in=run_in)
        run_in = self.tick_long_interval * position // ai_players + 1
        Scheduler().add_new_object(Callback(self.tick_long),
                                   self,
                                   run_in=run_in)

    def finish_init(self):
        # initialize the things that couldn't be initialized before because of the loading order
        self.refresh_ships()
        self.start()

    def refresh_ships(self):
        """ called when a new ship is added to the fleet """
        for ship in self.world.ships:
            if ship.owner == self and ship.has_component(
                    SelectableComponent) and ship not in self.ships:
                self.log.info('%s Added %s to the fleet', self, ship)
                self.ships[ship] = self.shipStates.idle
                if isinstance(ship, MovingWeaponHolder):
                    ship.stance = NoneStance
                if isinstance(ship, FightingShip):
                    self.combat_manager.add_new_unit(ship)
        self.need_more_ships = False
        self.need_more_combat_ships = False

    def __init(self):
        self._enabled = True  # whether this player is enabled (currently disabled at the end of the game)
        self.world = self.session.world
        self.islands = {}
        self.settlement_managers = []
        self._settlement_manager_by_settlement_id = {}
        self.missions = set()
        self.fishers = []
        self.settlement_founder = SettlementFounder(self)
        self.unit_builder = UnitBuilder(self)
        self.unit_manager = UnitManager(self)
        self.combat_manager = CombatManager(self)
        self.strategy_manager = StrategyManager(self)
        self.behavior_manager = BehaviorManager(self)
        self.settlement_expansions = []  # [(coords, settlement)]
        self.goals = [DoNothingGoal(self)]
        self.special_domestic_trade_manager = SpecialDomesticTradeManager(self)
        self.international_trade_manager = InternationalTradeManager(self)
        SettlementRangeChanged.subscribe(self._on_settlement_range_changed)
        NewDisaster.subscribe(self.notify_new_disaster)
        MineEmpty.subscribe(self.notify_mine_empty)

    def get_random_profile(self, token):
        return BehaviorProfileManager.get_random_player_profile(self, token)

    def start_mission(self, mission):
        self.ships[mission.ship] = self.shipStates.on_a_mission
        self.missions.add(mission)
        mission.start()

    def report_success(self, mission, msg):
        if not self._enabled:
            return

        self.missions.remove(mission)
        if mission.ship and mission.ship in self.ships:
            self.ships[mission.ship] = self.shipStates.idle
        if isinstance(mission, FoundSettlement):
            settlement_manager = SettlementManager(self, mission.land_manager)
            self.settlement_managers.append(settlement_manager)
            self._settlement_manager_by_settlement_id[
                settlement_manager.settlement.worldid] = settlement_manager
            self.add_building(settlement_manager.settlement.warehouse)
            if settlement_manager.feeder_island:
                self.need_feeder_island = False
        elif isinstance(mission, PrepareFoundationShip):
            self.settlement_founder.tick()

    def report_failure(self, mission, msg):
        if not self._enabled:
            return

        self.missions.remove(mission)
        if mission.ship and mission.ship in self.ships:
            self.ships[mission.ship] = self.shipStates.idle
        if isinstance(mission, FoundSettlement):
            del self.islands[mission.land_manager.island.worldid]

    def save(self, db):
        super(AIPlayer, self).save(db)

        # save the player
        db("UPDATE player SET client_id = 'AIPlayer' WHERE rowid = ?",
           self.worldid)

        current_callback = Callback(self.tick)
        calls = Scheduler().get_classinst_calls(self, current_callback)
        assert len(calls) == 1, "got %s calls for saving %s: %s" % (
            len(calls), current_callback, calls)
        remaining_ticks = max(calls.values()[0], 1)

        current_callback_long = Callback(self.tick_long)
        calls = Scheduler().get_classinst_calls(self, current_callback_long)
        assert len(calls) == 1, "got %s calls for saving %s: %s" % (
            len(calls), current_callback_long, calls)
        remaining_ticks_long = max(calls.values()[0], 1)

        db(
            "INSERT INTO ai_player(rowid, need_more_ships, need_more_combat_ships, need_feeder_island, remaining_ticks, remaining_ticks_long) VALUES(?, ?, ?, ?, ?, ?)",
            self.worldid, self.need_more_ships, self.need_more_combat_ships,
            self.need_feeder_island, remaining_ticks, remaining_ticks_long)

        # save the ships
        for ship, state in self.ships.iteritems():
            db("INSERT INTO ai_ship(rowid, owner, state) VALUES(?, ?, ?)",
               ship.worldid, self.worldid, state.index)

        # save the land managers
        for land_manager in self.islands.itervalues():
            land_manager.save(db)

        # save the settlement managers
        for settlement_manager in self.settlement_managers:
            settlement_manager.save(db)

        # save the missions
        for mission in self.missions:
            mission.save(db)

        # save the personality manager
        self.personality_manager.save(db)

        # save the unit manager
        self.unit_manager.save(db)

        # save the combat manager
        self.combat_manager.save(db)

        # save the strategy manager
        self.strategy_manager.save(db)

        # save the behavior manager
        self.behavior_manager.save(db)

    def _load(self, db, worldid):
        super(AIPlayer, self)._load(db, worldid)
        self.personality_manager = PersonalityManager.load(db, self)
        self.__init()

        self.need_more_ships, self.need_more_combat_ships, self.need_feeder_island, remaining_ticks, remaining_ticks_long = \
         db("SELECT need_more_ships, need_more_combat_ships, need_feeder_island, remaining_ticks, remaining_ticks_long FROM ai_player WHERE rowid = ?", worldid)[0]
        Scheduler().add_new_object(Callback(self.tick),
                                   self,
                                   run_in=remaining_ticks)
        Scheduler().add_new_object(Callback(self.tick_long),
                                   self,
                                   run_in=remaining_ticks_long)

    def finish_loading(self, db):
        """ This is called separately because most objects are loaded after the player. """

        # load the ships

        for ship_id, state_id in db(
                "SELECT rowid, state FROM ai_ship WHERE owner = ?",
                self.worldid):
            ship = WorldObject.get_object_by_id(ship_id)
            self.ships[ship] = self.shipStates[state_id]

        # load unit manager
        self.unit_manager = UnitManager.load(db, self)

        # load combat manager
        self.combat_manager = CombatManager.load(db, self)

        # load strategy manager
        self.strategy_manager = StrategyManager.load(db, self)

        # load BehaviorManager
        self.behavior_manager = BehaviorManager.load(db, self)

        # load the land managers
        for (worldid, ) in db(
                "SELECT rowid FROM ai_land_manager WHERE owner = ?",
                self.worldid):
            land_manager = LandManager.load(db, self, worldid)
            self.islands[land_manager.island.worldid] = land_manager

        # load the settlement managers and settlement foundation missions
        for land_manager in self.islands.itervalues():
            db_result = db(
                "SELECT rowid FROM ai_settlement_manager WHERE land_manager = ?",
                land_manager.worldid)
            if db_result:
                settlement_manager = SettlementManager.load(
                    db, self, db_result[0][0])
                self.settlement_managers.append(settlement_manager)
                self._settlement_manager_by_settlement_id[
                    settlement_manager.settlement.worldid] = settlement_manager

                # load the foundation ship preparing missions
                db_result = db(
                    "SELECT rowid FROM ai_mission_prepare_foundation_ship WHERE settlement_manager = ?",
                    settlement_manager.worldid)
                for (mission_id, ) in db_result:
                    self.missions.add(
                        PrepareFoundationShip.load(db, mission_id,
                                                   self.report_success,
                                                   self.report_failure))
            else:
                mission_id = db(
                    "SELECT rowid FROM ai_mission_found_settlement WHERE land_manager = ?",
                    land_manager.worldid)[0][0]
                self.missions.add(
                    FoundSettlement.load(db, mission_id, self.report_success,
                                         self.report_failure))

        for settlement_manager in self.settlement_managers:
            # load the domestic trade missions
            db_result = db(
                "SELECT rowid FROM ai_mission_domestic_trade WHERE source_settlement_manager = ?",
                settlement_manager.worldid)
            for (mission_id, ) in db_result:
                self.missions.add(
                    DomesticTrade.load(db, mission_id, self.report_success,
                                       self.report_failure))

            # load the special domestic trade missions
            db_result = db(
                "SELECT rowid FROM ai_mission_special_domestic_trade WHERE source_settlement_manager = ?",
                settlement_manager.worldid)
            for (mission_id, ) in db_result:
                self.missions.add(
                    SpecialDomesticTrade.load(db, mission_id,
                                              self.report_success,
                                              self.report_failure))

            # load the international trade missions
            db_result = db(
                "SELECT rowid FROM ai_mission_international_trade WHERE settlement_manager = ?",
                settlement_manager.worldid)
            for (mission_id, ) in db_result:
                self.missions.add(
                    InternationalTrade.load(db, mission_id,
                                            self.report_success,
                                            self.report_failure))

    def tick(self):
        Scheduler().add_new_object(Callback(self.tick),
                                   self,
                                   run_in=self.tick_interval)
        self.settlement_founder.tick()
        self.handle_enemy_expansions()
        self.handle_settlements()
        self.special_domestic_trade_manager.tick()
        self.international_trade_manager.tick()
        self.unit_manager.tick()
        self.combat_manager.tick()

    def tick_long(self):
        """
		Same as above but used for reasoning that is not required to be called as often (such as diplomacy, strategy etc.)
		"""
        Scheduler().add_new_object(Callback(self.tick_long),
                                   self,
                                   run_in=self.tick_long_interval)
        self.strategy_manager.tick()

    def handle_settlements(self):
        goals = []
        for goal in self.goals:
            if goal.can_be_activated:
                goal.update()
                goals.append(goal)
        for settlement_manager in self.settlement_managers:
            settlement_manager.tick(goals)
        goals.sort(reverse=True)

        settlements_blocked = set()  # set([settlement_manager_id, ...])
        for goal in goals:
            if not goal.active:
                continue
            if isinstance(
                    goal, SettlementGoal
            ) and goal.settlement_manager.worldid in settlements_blocked:
                continue  # can't build anything in this settlement
            result = goal.execute()
            if result == GOAL_RESULT.SKIP:
                self.log.info('%s, skipped goal %s', self, goal)
            elif result == GOAL_RESULT.BLOCK_SETTLEMENT_RESOURCE_USAGE:
                self.log.info(
                    '%s blocked further settlement resource usage by goal %s',
                    self, goal)
                settlements_blocked.add(goal.settlement_manager.worldid)
                goal.settlement_manager.need_materials = True
            else:
                self.log.info(
                    '%s all further goals during this tick blocked by goal %s',
                    self, goal)
                break  # built something; stop because otherwise the AI could look too fast

        self.log.info('%s had %d active goals', self,
                      sum(goal.active for goal in goals))
        for goal in goals:
            if goal.active:
                self.log.info('%s %s', self, goal)

        # refresh taxes and upgrade permissions
        for settlement_manager in self.settlement_managers:
            settlement_manager.refresh_taxes_and_upgrade_permissions()

    def request_ship(self):
        self.log.info('%s received request for more ships', self)
        self.need_more_ships = True

    def request_combat_ship(self):
        self.log.info('%s received request for combat ships', self)
        self.need_more_combat_ships = True

    def add_building(self, building):
        assert self._enabled
        # if the settlement id is not present then this is a new settlement that has to be handled separately
        if building.settlement.worldid in self._settlement_manager_by_settlement_id:
            self._settlement_manager_by_settlement_id[
                building.settlement.worldid].add_building(building)

    def remove_building(self, building):
        if not self._enabled:
            return

        self._settlement_manager_by_settlement_id[
            building.settlement.worldid].remove_building(building)

    def remove_unit(self, unit):
        if not self._enabled:
            return

        if unit in self.ships:
            del self.ships[unit]
        self.combat_manager.remove_unit(unit)
        self.unit_manager.remove_unit(unit)

    def count_buildings(self, building_id):
        return sum(
            settlement_manager.settlement.count_buildings(building_id)
            for settlement_manager in self.settlement_managers)

    def notify_mine_empty(self, message):
        """The Mine calls this function to let the player know that the mine is empty."""
        settlement = message.mine.settlement
        if settlement.owner is self:
            self._settlement_manager_by_settlement_id[
                settlement.worldid].production_builder.handle_mine_empty(
                    message.mine)

    def notify_new_disaster(self, message):
        settlement = message.building.settlement
        if settlement.owner is self:
            Scheduler().add_new_object(Callback(
                self._settlement_manager_by_settlement_id[
                    settlement.worldid].handle_disaster, message),
                                       self,
                                       run_in=0)

    def _on_settlement_range_changed(self, message):
        """Stores the ownership changes in a list for later processing."""
        our_new_coords_list = []
        settlement = message.sender

        for tile in message.changed_tiles:
            coords = (tile.x, tile.y)
            if settlement.owner is self:
                our_new_coords_list.append(coords)
            else:
                self.settlement_expansions.append((coords, settlement))

        if our_new_coords_list and settlement.worldid in self._settlement_manager_by_settlement_id:
            self._settlement_manager_by_settlement_id[
                settlement.
                worldid].production_builder.road_connectivity_cache.modify_area(
                    our_new_coords_list)

    def handle_enemy_expansions(self):
        if not self.settlement_expansions:
            return  # no changes in land ownership

        change_lists = defaultdict(lambda: [])
        for coords, settlement in self.settlement_expansions:
            if settlement.island.worldid not in self.islands:
                continue  # we don't have a settlement there and have no current plans to create one
            change_lists[settlement.island.worldid].append(coords)
        self.settlement_expansions = []
        if not change_lists:
            return  # no changes in land ownership on islands we care about

        for island_id, changed_coords in change_lists.iteritems():
            affects_us = False
            land_manager = self.islands[island_id]
            for coords in changed_coords:
                if coords in land_manager.production or coords in land_manager.village:
                    affects_us = True
                    break
            if not affects_us:
                continue  # we weren't using that land anyway

            settlement_manager = None
            for potential_settlement_manager in self.settlement_managers:
                if potential_settlement_manager.settlement.island.worldid == island_id:
                    settlement_manager = potential_settlement_manager
                    break

            if settlement_manager is None:
                self.handle_enemy_settling_on_our_chosen_island(island_id)
                # we are on the way to found a settlement on that island
            else:
                # we already have a settlement there
                settlement_manager.handle_lost_area(changed_coords)

    def handle_enemy_settling_on_our_chosen_island(self, island_id):
        mission = None
        for a_mission in self.missions:
            if isinstance(
                    a_mission, FoundSettlement
            ) and a_mission.land_manager.island.worldid == island_id:
                mission = a_mission
                break
        assert mission
        mission.cancel()
        self.settlement_founder.tick()

    @classmethod
    def load_abstract_buildings(cls, db):
        AbstractBuilding.load_all(db)

    @classmethod
    def clear_caches(cls):
        BasicBuilder.clear_cache()
        AbstractFarm.clear_cache()

    def __str__(self):
        return 'AI(%s/%s)' % (
            self.name if hasattr(self, 'name') else 'unknown',
            self.worldid if hasattr(self, 'worldid') else 'none')

    def early_end(self):
        """Called to speed up session destruction."""
        assert self._enabled
        self._enabled = False
        SettlementRangeChanged.unsubscribe(self._on_settlement_range_changed)
        NewDisaster.unsubscribe(self.notify_new_disaster)
        MineEmpty.unsubscribe(self.notify_mine_empty)

    def end(self):
        assert not self._enabled
        self.personality_manager = None
        self.world = None
        self.islands = None
        self.settlement_managers = None
        self._settlement_manager_by_settlement_id = None
        self.missions = None
        self.fishers = None
        self.settlement_founder = None
        self.unit_builder = None
        self.unit_manager = None
        self.behavior_manager = None
        self.combat_manager = None
        self.settlement_expansions = None
        self.goals = None
        self.special_domestic_trade_manager = None
        self.international_trade_manager = None
        self.strategy_manager.end()
        self.strategy_manager = None
        super(AIPlayer, self).end()
Пример #5
0
class AIPlayer(GenericAI):
	"""This is the AI that builds settlements."""

	shipStates = Enum.get_extended(GenericAI.shipStates, 'on_a_mission',)

	log = logging.getLogger("ai.aiplayer")
	tick_interval = 32
	tick_long_interval = 128

	def __init__(self, session, id, name, color, clientid, difficulty_level, **kwargs):
		super(AIPlayer, self).__init__(session, id, name, color, clientid, difficulty_level, **kwargs)
		self.need_more_ships = False
		self.need_more_combat_ships = True
		self.need_feeder_island = False
		self.personality_manager = PersonalityManager(self)
		self.__init()
		Scheduler().add_new_object(Callback(self.finish_init), self, run_in=0)

	def start(self):
		""" Start the AI tick process. Try to space out their ticks evenly. """
		ai_players = 0
		position = None
		for player in self.world.players:
			if isinstance(player, AIPlayer):
				if player is self:
					position = ai_players
				ai_players += 1
		run_in = self.tick_interval * position // ai_players + 1
		Scheduler().add_new_object(Callback(self.tick), self, run_in=run_in)
		run_in = self.tick_long_interval * position // ai_players + 1
		Scheduler().add_new_object(Callback(self.tick_long), self, run_in=run_in)

	def finish_init(self):
		# initialize the things that couldn't be initialized before because of the loading order
		self.refresh_ships()
		self.start()

	def refresh_ships(self):
		""" called when a new ship is added to the fleet """
		for ship in self.world.ships:
			if ship.owner == self and ship.has_component(SelectableComponent) and ship not in self.ships:
				self.log.info('%s Added %s to the fleet', self, ship)
				self.ships[ship] = self.shipStates.idle
				if isinstance(ship, MovingWeaponHolder):
					ship.stance = NoneStance
				if isinstance(ship, FightingShip):
					self.combat_manager.add_new_unit(ship)
		self.need_more_ships = False
		self.need_more_combat_ships = False

	def __init(self):
		self._enabled = True  # whether this player is enabled (currently disabled at the end of the game)
		self.world = self.session.world
		self.islands = {}
		self.settlement_managers = []
		self._settlement_manager_by_settlement_id = {}
		self.missions = set()
		self.fishers = []
		self.settlement_founder = SettlementFounder(self)
		self.unit_builder = UnitBuilder(self)
		self.unit_manager = UnitManager(self)
		self.combat_manager = CombatManager(self)
		self.strategy_manager = StrategyManager(self)
		self.behavior_manager = BehaviorManager(self)
		self.settlement_expansions = []  # [(coords, settlement)]
		self.goals = [DoNothingGoal(self)]
		self.special_domestic_trade_manager = SpecialDomesticTradeManager(self)
		self.international_trade_manager = InternationalTradeManager(self)
		SettlementRangeChanged.subscribe(self._on_settlement_range_changed)
		NewDisaster.subscribe(self.notify_new_disaster)
		MineEmpty.subscribe(self.notify_mine_empty)

	def get_random_profile(self, token):
		return BehaviorProfileManager.get_random_player_profile(self, token)

	def start_mission(self, mission):
		self.ships[mission.ship] = self.shipStates.on_a_mission
		self.missions.add(mission)
		mission.start()

	def report_success(self, mission, msg):
		if not self._enabled:
			return

		self.missions.remove(mission)
		if mission.ship and mission.ship in self.ships:
			self.ships[mission.ship] = self.shipStates.idle
		if isinstance(mission, FoundSettlement):
			settlement_manager = SettlementManager(self, mission.land_manager)
			self.settlement_managers.append(settlement_manager)
			self._settlement_manager_by_settlement_id[settlement_manager.settlement.worldid] = settlement_manager
			self.add_building(settlement_manager.settlement.warehouse)
			if settlement_manager.feeder_island:
				self.need_feeder_island = False
		elif isinstance(mission, PrepareFoundationShip):
			self.settlement_founder.tick()

	def report_failure(self, mission, msg):
		if not self._enabled:
			return

		self.missions.remove(mission)
		if mission.ship and mission.ship in self.ships:
			self.ships[mission.ship] = self.shipStates.idle
		if isinstance(mission, FoundSettlement):
			del self.islands[mission.land_manager.island.worldid]

	def save(self, db):
		super(AIPlayer, self).save(db)

		# save the player
		db("UPDATE player SET client_id = 'AIPlayer' WHERE rowid = ?", self.worldid)

		current_callback = Callback(self.tick)
		calls = Scheduler().get_classinst_calls(self, current_callback)
		assert len(calls) == 1, "got {0!s} calls for saving {1!s}: {2!s}".format(len(calls), current_callback, calls)
		remaining_ticks = max(calls.values()[0], 1)

		current_callback_long = Callback(self.tick_long)
		calls = Scheduler().get_classinst_calls(self, current_callback_long)
		assert len(calls) == 1, "got {0!s} calls for saving {1!s}: {2!s}".format(len(calls), current_callback_long, calls)
		remaining_ticks_long = max(calls.values()[0], 1)

		db("INSERT INTO ai_player(rowid, need_more_ships, need_more_combat_ships, need_feeder_island, remaining_ticks, remaining_ticks_long) VALUES(?, ?, ?, ?, ?, ?)",
			self.worldid, self.need_more_ships, self.need_more_combat_ships, self.need_feeder_island, remaining_ticks, remaining_ticks_long)

		# save the ships
		for ship, state in self.ships.iteritems():
			db("INSERT INTO ai_ship(rowid, owner, state) VALUES(?, ?, ?)", ship.worldid, self.worldid, state.index)

		# save the land managers
		for land_manager in self.islands.itervalues():
			land_manager.save(db)

		# save the settlement managers
		for settlement_manager in self.settlement_managers:
			settlement_manager.save(db)

		# save the missions
		for mission in self.missions:
			mission.save(db)

		# save the personality manager
		self.personality_manager.save(db)

		# save the unit manager
		self.unit_manager.save(db)

		# save the combat manager
		self.combat_manager.save(db)

		# save the strategy manager
		self.strategy_manager.save(db)

		# save the behavior manager
		self.behavior_manager.save(db)

	def _load(self, db, worldid):
		super(AIPlayer, self)._load(db, worldid)
		self.personality_manager = PersonalityManager.load(db, self)
		self.__init()

		self.need_more_ships, self.need_more_combat_ships, self.need_feeder_island, remaining_ticks, remaining_ticks_long = \
			db("SELECT need_more_ships, need_more_combat_ships, need_feeder_island, remaining_ticks, remaining_ticks_long FROM ai_player WHERE rowid = ?", worldid)[0]
		Scheduler().add_new_object(Callback(self.tick), self, run_in=remaining_ticks)
		Scheduler().add_new_object(Callback(self.tick_long), self, run_in=remaining_ticks_long)

	def finish_loading(self, db):
		""" This is called separately because most objects are loaded after the player. """

		# load the ships

		for ship_id, state_id in db("SELECT rowid, state FROM ai_ship WHERE owner = ?", self.worldid):
			ship = WorldObject.get_object_by_id(ship_id)
			self.ships[ship] = self.shipStates[state_id]

		# load unit manager
		self.unit_manager = UnitManager.load(db, self)

		# load combat manager
		self.combat_manager = CombatManager.load(db, self)

		# load strategy manager
		self.strategy_manager = StrategyManager.load(db, self)

		# load BehaviorManager
		self.behavior_manager = BehaviorManager.load(db, self)

		# load the land managers
		for (worldid,) in db("SELECT rowid FROM ai_land_manager WHERE owner = ?", self.worldid):
			land_manager = LandManager.load(db, self, worldid)
			self.islands[land_manager.island.worldid] = land_manager

		# load the settlement managers and settlement foundation missions
		for land_manager in self.islands.itervalues():
			db_result = db("SELECT rowid FROM ai_settlement_manager WHERE land_manager = ?", land_manager.worldid)
			if db_result:
				settlement_manager = SettlementManager.load(db, self, db_result[0][0])
				self.settlement_managers.append(settlement_manager)
				self._settlement_manager_by_settlement_id[settlement_manager.settlement.worldid] = settlement_manager

				# load the foundation ship preparing missions
				db_result = db("SELECT rowid FROM ai_mission_prepare_foundation_ship WHERE settlement_manager = ?",
					settlement_manager.worldid)
				for (mission_id,) in db_result:
					self.missions.add(PrepareFoundationShip.load(db, mission_id, self.report_success, self.report_failure))
			else:
				mission_id = db("SELECT rowid FROM ai_mission_found_settlement WHERE land_manager = ?", land_manager.worldid)[0][0]
				self.missions.add(FoundSettlement.load(db, mission_id, self.report_success, self.report_failure))

		for settlement_manager in self.settlement_managers:
			# load the domestic trade missions
			db_result = db("SELECT rowid FROM ai_mission_domestic_trade WHERE source_settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(DomesticTrade.load(db, mission_id, self.report_success, self.report_failure))

			# load the special domestic trade missions
			db_result = db("SELECT rowid FROM ai_mission_special_domestic_trade WHERE source_settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(SpecialDomesticTrade.load(db, mission_id, self.report_success, self.report_failure))

			# load the international trade missions
			db_result = db("SELECT rowid FROM ai_mission_international_trade WHERE settlement_manager = ?", settlement_manager.worldid)
			for (mission_id,) in db_result:
				self.missions.add(InternationalTrade.load(db, mission_id, self.report_success, self.report_failure))

	def tick(self):
		Scheduler().add_new_object(Callback(self.tick), self, run_in=self.tick_interval)
		self.settlement_founder.tick()
		self.handle_enemy_expansions()
		self.handle_settlements()
		self.special_domestic_trade_manager.tick()
		self.international_trade_manager.tick()
		self.unit_manager.tick()
		self.combat_manager.tick()

	def tick_long(self):
		"""
		Same as above but used for reasoning that is not required to be called as often (such as diplomacy, strategy etc.)
		"""
		Scheduler().add_new_object(Callback(self.tick_long), self, run_in=self.tick_long_interval)
		self.strategy_manager.tick()

	def handle_settlements(self):
		goals = []
		for goal in self.goals:
			if goal.can_be_activated:
				goal.update()
				goals.append(goal)
		for settlement_manager in self.settlement_managers:
			settlement_manager.tick(goals)
		goals.sort(reverse=True)

		settlements_blocked = set()  # set([settlement_manager_id, ...])
		for goal in goals:
			if not goal.active:
				continue
			if isinstance(goal, SettlementGoal) and goal.settlement_manager.worldid in settlements_blocked:
				continue  # can't build anything in this settlement
			result = goal.execute()
			if result == GOAL_RESULT.SKIP:
				self.log.info('%s, skipped goal %s', self, goal)
			elif result == GOAL_RESULT.BLOCK_SETTLEMENT_RESOURCE_USAGE:
				self.log.info('%s blocked further settlement resource usage by goal %s', self, goal)
				settlements_blocked.add(goal.settlement_manager.worldid)
				goal.settlement_manager.need_materials = True
			else:
				self.log.info('%s all further goals during this tick blocked by goal %s', self, goal)
				break  # built something; stop because otherwise the AI could look too fast

		self.log.info('%s had %d active goals', self, sum(goal.active for goal in goals))
		for goal in goals:
			if goal.active:
				self.log.info('%s %s', self, goal)

		# refresh taxes and upgrade permissions
		for settlement_manager in self.settlement_managers:
			settlement_manager.refresh_taxes_and_upgrade_permissions()

	def request_ship(self):
		self.log.info('%s received request for more ships', self)
		self.need_more_ships = True

	def request_combat_ship(self):
		self.log.info('%s received request for combat ships', self)
		self.need_more_combat_ships = True

	def add_building(self, building):
		assert self._enabled
		# if the settlement id is not present then this is a new settlement that has to be handled separately
		if building.settlement.worldid in self._settlement_manager_by_settlement_id:
			self._settlement_manager_by_settlement_id[building.settlement.worldid].add_building(building)

	def remove_building(self, building):
		if not self._enabled:
			return

		self._settlement_manager_by_settlement_id[building.settlement.worldid].remove_building(building)

	def remove_unit(self, unit):
		if not self._enabled:
			return

		if unit in self.ships:
			del self.ships[unit]
		self.combat_manager.remove_unit(unit)
		self.unit_manager.remove_unit(unit)

	def count_buildings(self, building_id):
		return sum(settlement_manager.settlement.count_buildings(building_id) for settlement_manager in self.settlement_managers)

	def notify_mine_empty(self, message):
		"""The Mine calls this function to let the player know that the mine is empty."""
		settlement = message.mine.settlement
		if settlement.owner is self:
			self._settlement_manager_by_settlement_id[settlement.worldid].production_builder.handle_mine_empty(message.mine)

	def notify_new_disaster(self, message):
		settlement = message.building.settlement
		if settlement.owner is self:
			Scheduler().add_new_object(Callback(self._settlement_manager_by_settlement_id[settlement.worldid].handle_disaster, message), self, run_in=0)

	def _on_settlement_range_changed(self, message):
		"""Stores the ownership changes in a list for later processing."""
		our_new_coords_list = []
		settlement = message.sender

		for tile in message.changed_tiles:
			coords = (tile.x, tile.y)
			if settlement.owner is self:
				our_new_coords_list.append(coords)
			else:
				self.settlement_expansions.append((coords, settlement))

		if our_new_coords_list and settlement.worldid in self._settlement_manager_by_settlement_id:
			self._settlement_manager_by_settlement_id[settlement.worldid].production_builder.road_connectivity_cache.modify_area(our_new_coords_list)

	def handle_enemy_expansions(self):
		if not self.settlement_expansions:
			return  # no changes in land ownership

		change_lists = defaultdict(list)
		for coords, settlement in self.settlement_expansions:
			if settlement.island.worldid not in self.islands:
				continue  # we don't have a settlement there and have no current plans to create one
			change_lists[settlement.island.worldid].append(coords)
		self.settlement_expansions = []
		if not change_lists:
			return  # no changes in land ownership on islands we care about

		for island_id, changed_coords in change_lists.iteritems():
			affects_us = False
			land_manager = self.islands[island_id]
			for coords in changed_coords:
				if coords in land_manager.production or coords in land_manager.village:
					affects_us = True
					break
			if not affects_us:
				continue  # we weren't using that land anyway

			settlement_manager = None
			for potential_settlement_manager in self.settlement_managers:
				if potential_settlement_manager.settlement.island.worldid == island_id:
					settlement_manager = potential_settlement_manager
					break

			if settlement_manager is None:
				self.handle_enemy_settling_on_our_chosen_island(island_id)
				# we are on the way to found a settlement on that island
			else:
				# we already have a settlement there
				settlement_manager.handle_lost_area(changed_coords)

	def handle_enemy_settling_on_our_chosen_island(self, island_id):
		mission = None
		for a_mission in self.missions:
			if isinstance(a_mission, FoundSettlement) and a_mission.land_manager.island.worldid == island_id:
				mission = a_mission
				break
		assert mission
		mission.cancel()
		self.settlement_founder.tick()

	@classmethod
	def load_abstract_buildings(cls, db):
		AbstractBuilding.load_all(db)

	@classmethod
	def clear_caches(cls):
		BasicBuilder.clear_cache()
		AbstractFarm.clear_cache()

	def __str__(self):
		return 'AI({0!s}/{1!s})'.format(getattr(self, 'name', 'unknown'),
			getattr(self, 'worldid', 'none'))

	def early_end(self):
		"""Called to speed up session destruction."""
		assert self._enabled
		self._enabled = False
		SettlementRangeChanged.unsubscribe(self._on_settlement_range_changed)
		NewDisaster.unsubscribe(self.notify_new_disaster)
		MineEmpty.unsubscribe(self.notify_mine_empty)

	def end(self):
		assert not self._enabled
		self.personality_manager = None
		self.world = None
		self.islands = None
		self.settlement_managers = None
		self._settlement_manager_by_settlement_id = None
		self.missions = None
		self.fishers = None
		self.settlement_founder = None
		self.unit_builder = None
		self.unit_manager = None
		self.behavior_manager = None
		self.combat_manager = None
		self.settlement_expansions = None
		self.goals = None
		self.special_domestic_trade_manager = None
		self.international_trade_manager = None
		self.strategy_manager.end()
		self.strategy_manager = None
		super(AIPlayer, self).end()