class PirateRoutine(FleetMission):
    """
	Never ending mission of:
	1. Start moving to random places.
	2. Chase nearby ships along the way.
	3. Go back home
	"""

    missionStates = Enum.get_extended(FleetMission.missionStates,
                                      'sailing_to_target', 'chasing_ship',
                                      'going_home')

    # range at which the ship is considered "caught"
    caught_range = 5

    def __init__(self, success_callback, failure_callback, ships):
        super(PirateRoutine, self).__init__(success_callback, failure_callback,
                                            ships)
        self.target_point = self.owner.session.world.get_random_possible_ship_position(
        )

    def _setup_state_callbacks(self):
        self.combatIntermissions = {
            self.missionStates.sailing_to_target:
            (self.sail_to_target, self.flee_home),
            self.missionStates.chasing_ship: (self.chase_ship, self.flee_home),
            self.missionStates.going_home: (self.go_home, self.flee_home),
            self.missionStates.fleeing_home: (self.flee_home, self.flee_home),
        }

        self._state_fleet_callbacks = {
            self.missionStates.sailing_to_target:
            Callback(self.go_home),
            self.missionStates.chasing_ship:
            Callback(self.chase_ship),
            self.missionStates.going_home:
            Callback(self.report_success, "Pirate routine ended successfully"),
            self.missionStates.fleeing_home:
            Callback(self.report_failure,
                     "Mission was a failure, ships fled home successfully"),
        }

    def save(self, db):
        super(PirateRoutine, self).save(db)
        db(
            "INSERT INTO ai_mission_pirate_routine (rowid, target_point_x, target_point_y) VALUES(?, ?, ?)",
            self.worldid, self.target_point.x, self.target_point.y)

    def _load(self, worldid, owner, db, success_callback, failure_callback):
        super(PirateRoutine, self)._load(db, worldid, success_callback,
                                         failure_callback, owner)
        db_result = db(
            "SELECT target_point_x, target_point_y FROM ai_mission_pirate_routine WHERE rowid = ?",
            worldid)[0]

        self.target_point = Point(*db_result)

    def start(self):
        self.sail_to_target()

    def sail_to_target(self):
        self.log.debug(
            "Pirate %s, Mission %s, 1/2 set off to random point at %s",
            self.owner.name, self.__class__.__name__, self.target_point)
        try:
            self.fleet.move(
                self.target_point, self._state_fleet_callbacks[
                    self.missionStates.sailing_to_target])
            self.state = self.missionStates.sailing_to_target
        except MoveNotPossible:
            self.report_failure("Move was not possible when moving to target")

    def go_home(self):
        self.log.debug("Pirate %s, Mission %s, 2/2 going home at point %s",
                       self.owner.name, self.__class__.__name__,
                       self.owner.home_point)
        try:
            self.fleet.move(
                self.owner.home_point,
                self._state_fleet_callbacks[self.missionStates.going_home])
            self.state = self.missionStates.going_home
        except MoveNotPossible:
            self.report_failure(
                "Pirate: %s, Mission: %s, Pirate ship couldn't go home." %
                (self.owner.name, self.__class__.__name__))

    def chase_ship(self):
        pass

    def flee_home(self):
        # check if fleet still exists
        if self.fleet.size() > 0:
            try:
                self.fleet.move(
                    self.owner.home_point, self._state_fleet_callbacks[
                        self.missionStates.fleeing_home])
                self.state = self.missionStates.fleeing_home
            except MoveNotPossible:
                self.report_failure(
                    "Pirate: %s, Mission: %s, Pirate ship couldn't flee home after combat"
                    % (self.owner.name, self.__class__.__name__))
        else:
            self.report_failure("Combat was lost, all ships were wiped out")

    @classmethod
    def create(cls, success_callback, failure_callback, ships):
        return PirateRoutine(success_callback, failure_callback, ships)
class SurpriseAttack(FleetMission):
    """
	This is a basic attack mission.
	1. Send fleet to a Point (or Circle) A
	2. Break diplomacy with enemy player P if he is not hostile,
	3. Begin combat phase
	4. Return home (point B).
	"""

    missionStates = Enum.get_extended(FleetMission.missionStates,
                                      'sailing_to_target', 'in_combat',
                                      'breaking_diplomacy', 'going_back')

    def __init__(self, success_callback, failure_callback, ships, target_point,
                 return_point, enemy_player):
        super(SurpriseAttack, self).__init__(success_callback,
                                             failure_callback, ships)
        self.__init(target_point, return_point, enemy_player)

    def __init(self, target_point, return_point, enemy_player):
        self.target_point = target_point
        self.return_point = return_point
        self.enemy_player = enemy_player

        self.combatIntermissions = {
            self.missionStates.sailing_to_target:
            (self.sail_to_target, self.flee_home),
            self.missionStates.in_combat: (self.go_back, self.flee_home),
            self.missionStates.going_back: (self.go_back, self.flee_home),
            self.missionStates.breaking_diplomacy:
            (self.break_diplomacy, self.flee_home),
            self.missionStates.fleeing_home: (self.flee_home, self.flee_home),
        }

        # Fleet callbacks corresponding to given state
        self._state_fleet_callbacks = {
            self.missionStates.sailing_to_target:
            Callback(self.break_diplomacy),
            self.missionStates.going_back:
            Callback(self.report_success, "Ships arrived at return point"),
            self.missionStates.fleeing_home:
            Callback(self.report_failure,
                     "Combat was lost, ships fled home successfully"),
        }

    def save(self, db):
        super(SurpriseAttack, self).save(db)
        db(
            "INSERT INTO ai_mission_surprise_attack (rowid, enemy_player_id, target_point_x, target_point_y, target_point_radius, "
            "return_point_x, return_point_y) VALUES(?, ?, ?, ?, ?, ?, ?)",
            self.worldid, self.enemy_player.worldid,
            self.target_point.center.x, self.target_point.center.y,
            self.target_point.radius, self.return_point.x, self.return_point.y)

    def _load(self, worldid, owner, db, success_callback, failure_callback):
        super(SurpriseAttack, self)._load(db, worldid, success_callback,
                                          failure_callback, owner)
        db_result = db(
            "SELECT enemy_player_id, target_point_x, target_point_y, target_point_radius, return_point_x, return_point_y "
            "FROM ai_mission_surprise_attack WHERE rowid = ?", worldid)[0]
        enemy_player_id, target_point_x, target_point_y, target_point_radius, return_point_x, return_point_y = db_result

        target_point = Circle(Point(target_point_x, target_point_y),
                              target_point_radius)
        return_point = Point(return_point_x, return_point_y)
        enemy_player = WorldObject.get_object_by_id(enemy_player_id)
        self.__init(target_point, return_point, enemy_player)

    def start(self):
        self.sail_to_target()

    def sail_to_target(self):
        self.log.debug(
            "Player %s, Mission %s, 1/4 set off from point %s to point %s",
            self.owner.name, self.__class__.__name__, self.return_point,
            self.target_point)
        try:
            self.fleet.move(
                self.target_point, self._state_fleet_callbacks[
                    self.missionStates.sailing_to_target])
            self.state = self.missionStates.sailing_to_target
        except MoveNotPossible:
            self.report_failure("Move was not possible when moving to target")

    def break_diplomacy(self):
        self.state = self.missionStates.breaking_diplomacy
        self.log.debug(
            "Player %s, Mission %s, 2/4 breaking diplomacy with Player %s",
            self.owner.name, self.__class__.__name__, self.enemy_player.name)
        if not self.session.world.diplomacy.are_enemies(
                self.owner, self.enemy_player):
            AddEnemyPair(self.owner, self.enemy_player).execute(self.session)
        self.in_combat()

    def in_combat(self):
        self.combat_phase = True
        self.log.debug("Player %s, Mission %s, 3/4 in combat", self.owner.name,
                       self.__class__.__name__)
        self.state = self.missionStates.in_combat

    def go_back(self):
        self.log.debug(
            "Player %s, Mission %s, 4/4 going back after combat to point %s",
            self.owner.name, self.__class__.__name__, self.return_point)
        try:
            self.fleet.move(
                self.return_point,
                self._state_fleet_callbacks[self.missionStates.going_back])
            self.state = self.missionStates.going_back
        except MoveNotPossible:
            self.report_failure("Move was not possible when going back")

    def flee_home(self):
        if self.fleet.size() > 0:
            try:
                self.fleet.move(
                    self.return_point, self._state_fleet_callbacks[
                        self.missionStates.fleeing_home])
                self.state = self.missionStates.fleeing_home
            except MoveNotPossible:
                self.report_failure(
                    "Combat was lost, ships couldn't flee home")
        else:
            self.report_failure("Combat was lost, all ships were wiped out")

    @classmethod
    def create(cls, success_callback, failure_callback, fleet, target_point,
               return_point, enemy_player):
        return SurpriseAttack(success_callback, failure_callback, fleet,
                              target_point, return_point, enemy_player)
Example #3
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()
class PirateCombatManager(CombatManager):
    """
	Pirate player requires slightly different handling of combat, thus it gets his own CombatManager.
	Pirate player is able to use standard BehaviorComponents in it's BehaviorManager.
	"""
    log = logging.getLogger("ai.aiplayer.piratecombatmanager")

    shipStates = Enum.get_extended(CombatManager.shipStates, 'chasing_ship',
                                   'going_home')

    def __init__(self, owner):
        super().__init__(owner)

    def handle_mission_combat(self, mission):
        """
		Routine for handling combat in mission that requests for it.
		"""
        filters = self.unit_manager.filtering_rules
        fleet = mission.fleet

        ship_group = fleet.get_ships()
        ship_group = self.unit_manager.filter_ships(
            ship_group, (filters.ship_state(self.ships, self.shipStates.idle)))

        if not ship_group:
            mission.abort_mission()

        ships_around = self.unit_manager.find_ships_near_group(
            ship_group, self.combat_range)
        ships_around = self.unit_manager.filter_ships(ships_around,
                                                      (filters.hostile(), ))
        fighting_ships = self.unit_manager.filter_ships(
            ships_around, (filters.fighting(), ))
        working_ships = self.unit_manager.filter_ships(ships_around,
                                                       (filters.working(), ))

        environment = {'ship_group': ship_group}

        # begin combat if it's still unresolved
        if fighting_ships:
            environment['enemies'] = fighting_ships
            environment['power_balance'] = UnitManager.calculate_power_balance(
                ship_group, fighting_ships)
            self.log.debug("Player: %s vs Player: %s -> power_balance:%s",
                           self.owner.name, fighting_ships[0].owner.name,
                           environment['power_balance'])
            self.owner.behavior_manager.request_action(
                BehaviorManager.action_types.offensive,
                'fighting_ships_in_sight', **environment)
        elif working_ships:
            environment['enemies'] = working_ships
            self.owner.behavior_manager.request_action(
                BehaviorManager.action_types.offensive,
                'working_ships_in_sight', **environment)
        else:
            # no one else is around to fight -> continue mission
            mission.continue_mission()

    def handle_uncertain_combat(self, mission):
        """
		Handles fleets that may way to be in combat.
		"""
        filters = self.unit_manager.filtering_rules

        # test first whether requesting for combat is of any use (any ships nearby)
        ship_group = mission.fleet.get_ships()
        ship_group = self.unit_manager.filter_ships(
            ship_group, (filters.ship_state(self.ships, self.shipStates.idle)))
        ships_around = self.unit_manager.find_ships_near_group(
            ship_group, self.combat_range)
        ships_around = self.unit_manager.filter_ships(ships_around,
                                                      (filters.hostile()))
        fighting_ships = self.unit_manager.filter_ships(
            ships_around, (filters.fighting(), ))
        working_ships = self.unit_manager.filter_ships(ships_around,
                                                       (filters.working(), ))

        if fighting_ships:
            environment = {'enemies': fighting_ships}
            if self.owner.strategy_manager.request_to_pause_mission(
                    mission, **environment):
                self.handle_mission_combat(mission)
        elif working_ships:
            environment = {'enemies': working_ships}
            if self.owner.strategy_manager.request_to_pause_mission(
                    mission, **environment):
                self.handle_mission_combat(mission)

    def handle_casual_combat(self):
        """
		Combat with idle ships (not assigned to a mission)
		"""
        filters = self.unit_manager.filtering_rules

        rules = (filters.not_in_fleet, filters.pirate,
                 filters.ship_state(self.ships, self.shipStates.idle))
        for ship in self.unit_manager.get_ships(rules):
            # Turn into one-ship group, since reasoning is based around groups of ships
            ship_group = [
                ship,
            ]

            ships_around = self.unit_manager.find_ships_near_group(
                ship_group, self.combat_range)
            fighting_ships = self.unit_manager.filter_ships(
                ships_around, (filters.fighting(), ))
            working_ships = self.unit_manager.filter_ships(
                ships_around, (filters.working(), ))
            environment = {'ship_group': ship_group}
            if fighting_ships:
                environment['enemies'] = fighting_ships
                environment[
                    'power_balance'] = UnitManager.calculate_power_balance(
                        ship_group, fighting_ships)
                self.log.debug("Player: %s vs Player: %s -> power_balance:%s",
                               self.owner.name, fighting_ships[0].owner.name,
                               environment['power_balance'])
                self.owner.behavior_manager.request_action(
                    BehaviorManager.action_types.offensive,
                    'fighting_ships_in_sight', **environment)
            elif working_ships:
                environment['enemies'] = working_ships
                self.owner.behavior_manager.request_action(
                    BehaviorManager.action_types.offensive,
                    'working_ships_in_sight', **environment)
            else:
                self.owner.behavior_manager.request_action(
                    BehaviorManager.action_types.idle, 'no_one_in_sight',
                    **environment)
Example #5
0
class Trader(GenericAI):
    """A trader represents the free trader that travels around the map with its trading ship(s) and
	sells resources to players and buys resources from them. This is a very simple form of AI, as it
	doesn't do any more then drive to a place on water or a warehouse randomly and then buys and
	sells resources. A game should not have more then one free trader (it could though)
	@param id: int - player id, every Player needs a unique id, as the free trader is a Player instance, it also does.
	@param name: Traders name, also needed for the Player class.
	@param color: util.Color instance with the traders banner color, also needed for the Player class"""

    shipStates = Enum.get_extended(GenericAI.shipStates, 'moving_to_warehouse',
                                   'reached_warehouse')

    log = logging.getLogger("ai.trader")
    regular_player = False

    def __init__(self, session, id, name, color, **kwargs):
        super(Trader, self).__init__(session, id, name, color, **kwargs)
        self.__init()
        map_size = self.session.world.map_dimensions.width
        while map_size > 0:
            self.create_ship()
            map_size -= TRADER.TILES_PER_TRADER

    def create_ship(self):
        """Create a ship and place it randomly"""
        self.log.debug("Trader %s: creating new ship", self.worldid)
        point = self.session.world.get_random_possible_ship_position()
        ship = CreateUnit(self.worldid, UNITS.TRADER_SHIP, point.x,
                          point.y)(issuer=self)
        self.ships[ship] = self.shipStates.reached_warehouse
        Scheduler().add_new_object(Callback(self.ship_idle, ship),
                                   self,
                                   run_in=0)

    def __init(self):
        self.warehouse = {
        }  # { ship.worldid : warehouse }. stores the warehouse the ship is currently heading to
        self.allured_by_signal_fire = {
        }  # bool, used to get away from a signal fire (and not be allured again immediately)

        NewSettlement.subscribe(self._on_new_settlement)

    def _on_new_settlement(self, msg):
        # make sure there's a trader ship for SETTLEMENTS_PER_SHIP settlements
        if len(self.session.world.settlements
               ) > self.get_ship_count() * TRADER.SETTLEMENTS_PER_SHIP:
            self.create_ship()

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

        # mark self as a trader
        db("UPDATE player SET is_trader = 1 WHERE rowid = ?", self.worldid)

        for ship in self.ships:
            # prepare values
            ship_state = self.ships[ship]

            remaining_ticks = None
            # get current callback in scheduler, according to ship state, to retrieve
            # the number of ticks, when the call will actually be done
            current_callback = None
            if ship_state == self.shipStates.reached_warehouse:
                current_callback = Callback(self.ship_idle, ship)
            if current_callback is not None:
                # current state has a callback
                calls = Scheduler().get_classinst_calls(self, current_callback)
                assert len(
                    calls) == 1, "got {} calls for saving {}: {}".format(
                        len(calls), current_callback, calls)
                remaining_ticks = max(list(calls.values())[0], 1)

            targeted_warehouse = None if ship.worldid not in self.warehouse else self.warehouse[
                ship.worldid].worldid

            # put them in the database
            db(
                "INSERT INTO trader_ships(rowid, state, remaining_ticks, targeted_warehouse) \
			   VALUES(?, ?, ?, ?)", ship.worldid, ship_state.index, remaining_ticks,
                targeted_warehouse)

    def _load(self, db, worldid):
        super(Trader, self)._load(db, worldid)
        self.__init()

    def load_ship_states(self, db):
        # load ships one by one from db (ship instances themselves are loaded already, but
        # we have to use them here)
        for ship_id, state_id, remaining_ticks, targeted_warehouse in \
          db("SELECT rowid, state, remaining_ticks, targeted_warehouse FROM trader_ships"):
            state = self.shipStates[state_id]
            ship = WorldObject.get_object_by_id(ship_id)

            self.ships[ship] = state

            if state == self.shipStates.moving_random:
                ship.add_move_callback(Callback(self.ship_idle, ship))
            elif state == self.shipStates.moving_to_warehouse:
                ship.add_move_callback(Callback(self.reached_warehouse, ship))
                assert targeted_warehouse is not None
                self.warehouse[ship.worldid] = WorldObject.get_object_by_id(
                    targeted_warehouse)
            elif state == self.shipStates.reached_warehouse:
                assert remaining_ticks is not None
                Scheduler().add_new_object(Callback(self.ship_idle, ship),
                                           self, remaining_ticks)

    def get_ship_count(self):
        """Returns number of ships"""
        return len(self.ships)

    def send_ship_random(self, ship):
        """Sends a ship to a random position on the map.
		@param ship: Ship instance that is to be used"""
        super(Trader, self).send_ship_random(ship)
        ship.add_conditional_callback(
            Callback(self._check_for_signal_fire_in_ship_range, ship),
            callback=Callback(self._ship_found_signal_fire, ship))

    def _check_for_signal_fire_in_ship_range(self, ship):
        """Returns the signal fire instance, if there is one in the ships range, else False"""
        if ship in self.allured_by_signal_fire and self.allured_by_signal_fire[
                ship]:
            return False  # don't visit signal fire again
        for tile in self.session.world.get_tiles_in_radius(
                ship.position, ship.radius):
            try:
                if tile.object.id == BUILDINGS.SIGNAL_FIRE:
                    return tile.object
            except AttributeError:
                pass  # tile has no object or object has no id
        return False

    def _ship_found_signal_fire(self, ship):
        signal_fire = self._check_for_signal_fire_in_ship_range(ship)
        self.log.debug("Trader %s ship %s found signal fire %s", self.worldid,
                       ship.worldid, signal_fire)
        # search a warehouse in the range of the signal fire and move to it
        warehouses = self.session.world.get_warehouses()
        for house in warehouses:
            if house.position.distance(signal_fire.position) <= signal_fire.radius and \
               house.owner == signal_fire.owner:
                self.log.debug("Trader %s moving to house %s", self.worldid,
                               house)
                self.allured_by_signal_fire[ship] = True

                # HACK: remove allured flag in a few ticks
                def rem_allured(self, ship):
                    self.allured_by_signal_fire[ship] = False

                Scheduler().add_new_object(Callback(rem_allured, self, ship),
                                           self,
                                           Scheduler().get_ticks(60))
                self.send_ship_random_warehouse(ship, house)
                return
        self.log.debug("Trader can't find warehouse in range of signal fire")

    def send_ship_random_warehouse(self, ship, warehouse=None):
        """Sends a ship to a random warehouse on the map
		@param ship: Ship instance that is to be used
		@param warehouse: warehouse instance to move to. Random one is selected on None."""
        self.log.debug("Trader %s ship %s moving to warehouse (random=%s)",
                       self.worldid, ship.worldid, (warehouse is None))
        #TODO maybe this kind of list should be saved somewhere, as this is pretty performance intense
        warehouses = self.session.world.get_warehouses()
        # Remove all warehouses that are not safe to visit
        warehouses = list(filter(self.is_warehouse_safe, warehouses))
        if not warehouses:  # there aren't any warehouses, move randomly
            self.send_ship_random(ship)
        else:  # select a warehouse
            if warehouse is None:
                self.warehouse[ship.worldid] = self.session.random.choice(
                    warehouses)
            else:
                self.warehouse[ship.worldid] = warehouse
            try:  # try to find a possible position near the warehouse
                ship.move(
                    Circle(self.warehouse[ship.worldid].position.center,
                           ship.radius), Callback(self.reached_warehouse,
                                                  ship))
                self.ships[ship] = self.shipStates.moving_to_warehouse
            except MoveNotPossible:
                self.send_ship_random(ship)

    def is_warehouse_safe(self, warehouse):
        """Checkes whether a warehouse is safe to visit"""
        return not isinstance(
            self.session.world.disaster_manager.get_disaster(
                warehouse.settlement), BlackDeathDisaster)

    def reached_warehouse(self, ship):
        """Actions that need to be taken when reaching a warehouse:
		Sell demanded res, buy offered res, simulate load/unload, continue route.
		@param ship: ship instance"""
        self.log.debug("Trader %s ship %s: reached warehouse", self.worldid,
                       ship.worldid)
        settlement = self.warehouse[ship.worldid].settlement
        # NOTE: must be sorted for mp games (same order everywhere)
        trade_comp = settlement.get_component(TradePostComponent)
        for res in sorted(trade_comp.buy_list.keys(
        )):  # check for resources that the settlement wants to buy
            # select a random amount to sell
            amount = self.session.random.randint(TRADER.SELL_AMOUNT_MIN,
                                                 TRADER.SELL_AMOUNT_MAX)
            # try to sell all, else try smaller pieces
            for try_amount in range(amount, 0, -1):
                price = int(
                    self.session.db.get_res_value(res) *
                    TRADER.PRICE_MODIFIER_SELL * try_amount)
                trade_successful = trade_comp.buy(res, try_amount, price,
                                                  self.worldid)
                self.log.debug(
                    "Trader %s: offered sell %s tons of res %s, success: %s",
                    self.worldid, try_amount, res, trade_successful)
                if trade_successful:
                    break

        # NOTE: must be sorted for mp games (same order everywhere)
        for res in sorted(trade_comp.sell_list.keys()):
            # select a random amount to buy from the settlement
            amount = self.session.random.randint(TRADER.BUY_AMOUNT_MIN,
                                                 TRADER.BUY_AMOUNT_MAX)
            # try to buy all, else try smaller pieces
            for try_amount in range(amount, 0, -1):
                price = int(
                    self.session.db.get_res_value(res) *
                    TRADER.PRICE_MODIFIER_BUY * try_amount)
                trade_successful = trade_comp.sell(res, try_amount, price,
                                                   self.worldid)
                self.log.debug(
                    "Trader %s: offered buy %s tons of res %s, success: %s",
                    self.worldid, try_amount, res, trade_successful)
                if trade_successful:
                    break

        del self.warehouse[ship.worldid]
        # wait a few seconds before going on to simulate loading/unloading process
        Scheduler().add_new_object(
            Callback(self.ship_idle, ship), self,
            Scheduler().get_ticks(TRADER.TRADING_DURATION))
        self.ships[ship] = self.shipStates.reached_warehouse

    def ship_idle(self, ship):
        """Called if a ship is idle. Sends ship to either a random place or warehouse.
		Probability for 'random warehouse' in percent: TRADER.BUSINESS_SENSE.
		@param ship: ship instance"""
        if self.session.random.randint(0, 100) < TRADER.BUSINESS_SENSE:
            # delay one tick, to allow old movement calls to completely finish
            self.log.debug(
                "Trader %s ship %s: idle, moving to random warehouse",
                self.worldid, ship.worldid)
            Scheduler().add_new_object(Callback(
                self.send_ship_random_warehouse, ship),
                                       self,
                                       run_in=0)
        else:
            self.log.debug(
                "Trader %s ship %s: idle, moving to random location",
                self.worldid, ship.worldid)
            Scheduler().add_new_object(Callback(self.send_ship_random, ship),
                                       self,
                                       run_in=0)

    def end(self):
        super(Trader, self).end()
        NewSettlement.unsubscribe(self._on_new_settlement)
Example #6
0
class Pirate(GenericAI):
    """A pirate ship moving randomly around. If another ship comes into the reach
	of it, it will be followed for a short time."""

    # TODO: Move on_a_mission to GenericAI
    shipStates = Enum.get_extended(GenericAI.shipStates, 'on_a_mission',
                                   'chasing_ship', 'going_home')

    log = logging.getLogger("ai.pirate")
    regular_player = False

    caught_ship_radius = 5
    home_radius = 2

    ship_count = 1

    tick_interval = 32
    tick_long_interval = 128

    def __init__(self, session, id, name, color, **kwargs):
        super(Pirate, self).__init__(session, id, name, color, **kwargs)

        # choose a random water tile on the coast and call it home
        self.home_point = self.session.world.get_random_possible_coastal_ship_position(
        )
        # random sea tile if costal tile not found. Empty map?
        if not self.home_point:
            self.home_point = self.session.world.get_random_possible_ship_position(
            )
        self.log.debug("Pirate: home at (%d, %d), radius %d",
                       self.home_point.x, self.home_point.y, self.home_radius)
        self.__init()

        # create a ship and place it randomly (temporary hack)
        for i in range(self.ship_count):
            self.create_ship_at_random_position()

        Scheduler().add_new_object(Callback(self.tick), self, 1, -1,
                                   self.tick_interval)
        Scheduler().add_new_object(Callback(self.tick_long), self, 1, -1,
                                   self.tick_long_interval)

    def __init(self):
        self.world = self.session.world
        self.unit_manager = UnitManager(self)
        self.combat_manager = PirateCombatManager(self)
        self.strategy_manager = PirateStrategyManager(self)
        self.behavior_manager = BehaviorManager(self)

    @staticmethod
    def get_nearest_player_ship(base_ship):
        lowest_distance = None
        nearest_ship = None
        for ship in base_ship.find_nearby_ships():
            if isinstance(
                    ship,
                (PirateShip,
                 TradeShip)) or not ship.has_component(SelectableComponent):
                continue  # don't attack these ships
            distance = base_ship.position.distance(ship.position)
            if lowest_distance is None or distance < lowest_distance:
                lowest_distance = distance
                nearest_ship = ship
        return nearest_ship

    def tick(self):
        self.combat_manager.tick()

        # Temporary function for pirates respawn
        self.maintain_ship_count()

    def tick_long(self):
        self.strategy_manager.tick()

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

    def create_ship_at_random_position(self):
        point = self.session.world.get_random_possible_ship_position()
        ship = CreateUnit(self.worldid, UNITS.PIRATE_SHIP, point.x,
                          point.y)(issuer=self.session.world.player)
        self.ships[ship] = self.shipStates.idle
        self.combat_manager.add_new_unit(ship)

    def maintain_ship_count(self):
        if len(list(self.ships.keys())) < self.ship_count:
            self.create_ship_at_random_position()

    def save(self, db):
        super(Pirate, self).save(db)
        db("UPDATE player SET is_pirate = 1 WHERE rowid = ?", self.worldid)
        db("INSERT INTO pirate_home_point(x, y) VALUES(?, ?)",
           self.home_point.x, self.home_point.y)

        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(list(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(list(calls.values())[0], 1)

        db(
            "INSERT INTO ai_pirate(rowid, remaining_ticks, remaining_ticks_long) VALUES(?, ?, ?)",
            self.worldid, remaining_ticks, remaining_ticks_long)

        for ship in self.ships:
            ship_state = self.ships[ship]
            db("INSERT INTO pirate_ships(rowid, state) VALUES(?, ?)",
               ship.worldid, ship_state.index)

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

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

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

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

    def _load(self, db, worldid):
        super(Pirate, self)._load(db, worldid)
        self.__init()

        remaining_ticks, = db(
            "SELECT remaining_ticks FROM ai_pirate WHERE rowid = ?",
            worldid)[0]
        Scheduler().add_new_object(Callback(self.tick), self, remaining_ticks,
                                   -1, self.tick_interval)

        remaining_ticks_long, = db(
            "SELECT remaining_ticks_long FROM ai_pirate WHERE rowid = ?",
            worldid)[0]
        Scheduler().add_new_object(Callback(self.tick_long), self,
                                   remaining_ticks_long, -1,
                                   self.tick_interval)

        home = db("SELECT x, y FROM pirate_home_point")[0]
        self.home_point = Point(home[0], home[1])

        self.log.debug("Pirate: home at (%d, %d), radius %d",
                       self.home_point.x, self.home_point.y, self.home_radius)

    def finish_loading(self, db):
        # load ships one by one from db (ship instances themselves are loaded already, but
        # we have to use them here)

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

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

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

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

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

    def remove_unit(self, unit):
        """Called when a ship which is owned by the pirate is removed or killed."""
        del self.ships[unit]
        self.combat_manager.remove_unit(unit)
        self.unit_manager.remove_unit(unit)

    def end(self):
        self.strategy_manager.end()
        super(Pirate, self).end()
Example #7
0
class ChaseShipsAndAttack(FleetMission):
	"""
	This is one of the basic attack missions.
	1. Sail to given ship on the map until the fleet is in close range
	2. Begin combat phase
	3. go to 1 if ship is still alive

	This mission may work the best for 2 ships fleet
	"""

	missionStates = Enum.get_extended(FleetMission.missionStates, 'sailing_to_target', 'in_combat')
	target_range = 5

	def __init__(self, success_callback, failure_callback, ships, target_ship):
		super(ChaseShipsAndAttack, self).__init__(success_callback, failure_callback, ships)
		self.__init(target_ship)

	def __init(self, target_ship):
		self.target_ship = target_ship

		self.combatIntermissions = {
			self.missionStates.sailing_to_target: (self.sail_to_target, self.flee_home),
			self.missionStates.in_combat: (self.check_ship_alive, self.flee_home),
			self.missionStates.fleeing_home: (self.flee_home, self.flee_home),
		}

		self._state_fleet_callbacks = {
			self.missionStates.sailing_to_target: Callback(self.was_reached),
			self.missionStates.fleeing_home: Callback(self.report_failure, "Combat was lost, ships fled home successfully"),
		}

		ShipDestroyed.subscribe(self._on_ship_destroyed)

	def save(self, db):
		super(ChaseShipsAndAttack, self).save(db)
		db("INSERT INTO ai_mission_chase_ships_and_attack (rowid, target_ship_id) VALUES(?, ?)", self.worldid, self.target_ship.worldid)

	def _load(self, worldid, owner, db, success_callback, failure_callback):
		super(ChaseShipsAndAttack, self)._load(db, worldid, success_callback, failure_callback, owner)
		(target_ship_id,) = db("SELECT target_ship_id FROM ai_mission_chase_ships_and_attack WHERE rowid = ?", worldid)[0]

		target_ship = WorldObject.get_object_by_id(target_ship_id)
		self.__init(target_ship)

	def start(self):
		self.sail_to_target()

	def sail_to_target(self):
		self.log.debug("Player %s, Mission %s, 1/2 set off to ship %s at %s", self.owner.name, self.__class__.__name__,
			self.target_ship.get_component(NamedComponent).name, self.target_ship.position)
		try:
			self.fleet.move(Circle(self.target_ship.position, self.target_range), self._state_fleet_callbacks[self.missionStates.sailing_to_target])
			self.state = self.missionStates.sailing_to_target
		except MoveNotPossible:
			self.report_failure("Move was not possible when moving to target")

	def was_reached(self):
		if self.target_ship.in_ship_map:
			if any((ship.position.distance(self.target_ship.position) <= self.target_range + 1 for ship in self.fleet.get_ships())):
				# target ship reached: execute combat
				self.state = self.missionStates.in_combat
				self.in_combat()
			else:
				# target ship was not reached: sail again
				self.state = self.missionStates.sailing_to_target
				self.sail_to_target()
		else:
			# ship was destroyed
			self.report_success("Ship was destroyed")

	def check_ship_alive(self):
		if self.target_ship.in_ship_map:
			self.was_reached()
		else:
			self.report_success("Target ship was eliminated")

	def in_combat(self):
		if not self.session.world.diplomacy.are_enemies(self.owner, self.target_ship.owner):
			self.report_failure("Target ship was not hostile. Aborting mission.")
			return
		self.combat_phase = True
		self.log.debug("Player %s, Mission %s, 2/2 in combat", self.owner.name, self.__class__.__name__)
		self.state = self.missionStates.in_combat

	def flee_home(self):
		# check if fleet still exists
		if self.fleet.size() > 0:
			try:
				home_settlement = self.owner.settlements[0]
				return_point = self.unit_manager.get_warehouse_area(home_settlement, 10)
				self.fleet.move(return_point, self._state_fleet_callbacks[self.missionStates.fleeing_home])
				self.state = self.missionStates.fleeing_home
			except MoveNotPossible:
				self.report_failure("Combat was lost, ships couldn't flee home")
		else:
			self.report_failure("Combat was lost, all ships were wiped out")

	@classmethod
	def create(cls, success_callback, failure_callback, fleet, target_ship):
		return ChaseShipsAndAttack(success_callback, failure_callback, fleet, target_ship)

	def _on_ship_destroyed(self, msg):
		if msg.sender is self.target_ship:
			self.check_ship_alive()
			assert not self.target_ship.in_ship_map
			ShipDestroyed.unsubscribe(self._on_ship_destroyed)

	def end(self):
		if self.target_ship.in_ship_map:
			ShipDestroyed.unsubscribe(self._on_ship_destroyed)
		super(ChaseShipsAndAttack, self).end()
Example #8
0
class ScoutingMission(FleetMission):
	"""
	This is an example of a scouting mission.
	Send ship from point A to point B, and then to point A again.
	1. Send fleet to a point on the map
	2. Fleet returns to starting position of ships[0] (first ship)
	"""
	missionStates = Enum.get_extended(FleetMission.missionStates, 'sailing_to_target', 'going_back')

	def __init__(self, success_callback, failure_callback, ships, target_point):
		super(ScoutingMission, self).__init__(success_callback, failure_callback, ships)
		self.__init(target_point, ships[0].position.copy())

	def __init(self, target_point, starting_point):
		self.target_point = target_point
		self.starting_point = starting_point

		self.combatIntermissions = {
			self.missionStates.sailing_to_target: (self.sail_to_target, self.flee_home),
			self.missionStates.going_back: (self.go_back, self.flee_home),
			self.missionStates.fleeing_home: (self.flee_home, self.flee_home),
		}

		self._state_fleet_callbacks = {
			self.missionStates.sailing_to_target: Callback(self.go_back),
			self.missionStates.going_back: Callback(self.report_success, "Ships arrived at the target"),
			self.missionStates.fleeing_home: Callback(self.report_failure, "Combat was lost, ships fled home successfully"),
		}

	def start(self):
		self.sail_to_target()

	def save(self, db):
		super(ScoutingMission, self).save(db)
		db("INSERT INTO ai_scouting_mission (rowid, starting_point_x, starting_point_y, target_point_x, target_point_y) VALUES(?, ?, ?, ?, ?)",
			self.worldid, self.starting_point.x, self.starting_point.y, self.target_point.x, self.target_point.y)

	def _load(self, worldid, owner, db, success_callback, failure_callback):
		super(ScoutingMission, self)._load(db, worldid, success_callback, failure_callback, owner)
		db_result = db("SELECT target_point_x, target_point_y, starting_point_x, starting_point_y FROM ai_scouting_mission WHERE rowid = ?", worldid)[0]
		self.__init(Point(*db_result[:2]), Point(*db_result[2:]))

	def go_back(self):
		"""
		Going back home after successfully reaching the target point.
		"""
		try:
			self.fleet.move(self.starting_point, self._state_fleet_callbacks[self.missionStates.going_back])
			self.state = self.missionStates.going_back
		except MoveNotPossible:
			self.report_failure("Move was not possible when going back")

	def flee_home(self):
		"""
		Fleeing home after severe casualties.
		"""
		if self.fleet.size() > 0:
			try:
				self.fleet.move(self.starting_point, self._state_fleet_callbacks[self.missionStates.fleeing_home])
				self.state = self.missionStates.fleeing_home
			except MoveNotPossible:
				self.report_failure("Combat was lost, ships couldn't flee home")
		else:
			self.report_failure("Combat was lost, all ships were wiped out")

	def sail_to_target(self):
		if not self.target_point:
			self.target_point = self.owner.session.world.get_random_possible_ship_position()
		try:
			self.fleet.move(self.target_point, self._state_fleet_callbacks[self.missionStates.sailing_to_target])
			self.state = self.missionStates.sailing_to_target
		except MoveNotPossible:
			self.report_failure("Move was not possible when moving to target")

	@classmethod
	def create(cls, success_callback, failure_callback, ships, target_point=None):
		return ScoutingMission(success_callback, failure_callback, ships, target_point)
Example #9
0
class Pirate(AIPlayer):
    """A pirate ship moving randomly around. If another ship comes into the reach
	of it, it will be followed for a short time."""

    shipStates = Enum.get_extended(AIPlayer.shipStates, 'chasing_ship',
                                   'going_home')

    log = logging.getLogger("ai.pirate")

    caught_ship_radius = 5
    home_radius = 2
    sight_radius = 15

    def __init__(self, session, id, name, color, **kwargs):
        super(Pirate, self).__init__(session, id, name, color, **kwargs)

        # choose a random water tile on the coast and call it home
        self.home_point = self.session.world.get_random_possible_coastal_ship_position(
        )
        self.log.debug(
            "Pirate: home at (%d, %d), radius %d" %
            (self.home_point.x, self.home_point.y, self.home_radius))

        # create a ship and place it randomly (temporary hack)
        point = self.session.world.get_random_possible_ship_position()
        ship = CreateUnit(self.worldid, UNITS.PIRATE_SHIP_CLASS, point.x,
                          point.y)(issuer=self.session.world.player)
        self.ships[ship] = self.shipStates.idle
        self.calculate_visibility_points(ship)

        for ship in self.ships.keys():
            Scheduler().add_new_object(Callback(self.send_ship, ship), self)
            Scheduler().add_new_object(Callback(self.lookout, ship), self, 8,
                                       -1)

    @staticmethod
    def get_nearest_player_ship(base_ship):
        lowest_distance = None
        nearest_ship = None
        for ship in base_ship.find_nearby_ships():
            if isinstance(ship, (PirateShip, TradeShip)):
                continue  # don't attack these ships
            distance = base_ship.position.distance_to_point(ship.position)
            if lowest_distance is None or distance < lowest_distance:
                lowest_distance = distance
                nearest_ship = ship
        return nearest_ship

    def lookout(self, pirate_ship):
        if self.ships[pirate_ship] != self.shipStates.going_home:
            ship = self.get_nearest_player_ship(pirate_ship)
            if ship:
                self.log.debug("Pirate: Scout found ship: %s" % ship.name)
                self.send_ship(pirate_ship)
            else:
                self.predict_player_position(pirate_ship)

    def save(self, db):
        super(Pirate, self).save(db)
        db("UPDATE player SET is_pirate = 1 WHERE rowid = ?", self.worldid)
        db("INSERT INTO pirate_home_point(x, y) VALUES(?, ?)",
           self.home_point.x, self.home_point.y)

        for ship in self.ships:
            # prepare values
            ship_state = self.ships[ship]
            current_callback = Callback(self.lookout, ship)
            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)

            db(
                "INSERT INTO pirate_ships(rowid, state, remaining_ticks) VALUES(?, ?, ?)",
                ship.worldid, ship_state.index, remaining_ticks)

    def _load(self, db, worldid):
        super(Pirate, self)._load(db, worldid)
        home = db("SELECT x, y FROM pirate_home_point")[0]
        self.home_point = Point(home[0], home[1])
        self.log.debug(
            "Pirate: home at (%d, %d), radius %d" %
            (self.home_point.x, self.home_point.y, self.home_radius))

    def load_ship_states(self, db):
        # load ships one by one from db (ship instances themselves are loaded already, but
        # we have to use them here)
        for ship_id, state_id, remaining_ticks in \
          db("SELECT rowid, state, remaining_ticks FROM pirate_ships"):
            state = self.shipStates[state_id]
            ship = WorldObject.get_object_by_id(ship_id)
            self.ships[ship] = state
            assert remaining_ticks is not None
            Scheduler().add_new_object(Callback(self.lookout, ship), self,
                                       remaining_ticks, -1, 8)
            ship.add_move_callback(Callback(self.ship_idle, ship))
            self.calculate_visibility_points(ship)

    def send_ship(self, pirate_ship):
        self.log.debug(
            'Pirate %s: send_ship(%s) start transition: %s' %
            (self.worldid, pirate_ship.name, self.ships[pirate_ship]))
        done = False

        #transition the pirate ship state to 'idle' once it is inside home circumference
        if pirate_ship.position.distance_to_point(self.home_point) <= self.home_radius and \
         self.ships[pirate_ship] == self.shipStates.going_home:
            self.ships[pirate_ship] = self.shipStates.idle
            self.log.debug(
                'Pirate %s: send_ship(%s) new state: %s' %
                (self.worldid, pirate_ship.name, self.ships[pirate_ship]))

        if self.ships[pirate_ship] != self.shipStates.going_home:
            if self._chase_closest_ship(pirate_ship):
                done = True

        if not done:
            ship = self.get_nearest_player_ship(pirate_ship)
            if self.ships[pirate_ship] == self.shipStates.chasing_ship and (ship is None or \
              ship.position.distance_to_point(pirate_ship.position) <= self.caught_ship_radius):
                # caught the ship, go home
                for point in self.session.world.get_points_in_radius(
                        self.home_point, self.home_radius, shuffle=True):
                    try:
                        pirate_ship.move(point,
                                         Callback(self.send_ship, pirate_ship))
                    except MoveNotPossible:
                        continue
                    self.log.debug(
                        'Pirate %s: send_ship(%s) going home (%d, %d)' %
                        (self.worldid, pirate_ship.name, point.x, point.y))
                    self.ships[pirate_ship] = self.shipStates.going_home
                    done = True
                    break

        #visit those points which are most frequented by player
        if not done and self.ships[pirate_ship] != self.shipStates.going_home:
            self.predict_player_position(pirate_ship)

        self.log.debug(
            'Pirate %s: send_ship(%s) new state: %s' %
            (self.worldid, pirate_ship.name, self.ships[pirate_ship]))

    def _chase_closest_ship(self, pirate_ship):
        ship = self.get_nearest_player_ship(pirate_ship)
        if ship:
            # The player is in sight here
            # Note down all the point's visibility the player belongs to
            for point in self.visibility_points:
                if ship.position.distance_to_point(
                        point[0]) <= self.sight_radius:
                    point[
                        1] += 1  # point[1] represents the chance of seeing a player
            if ship.position.distance_to_point(
                    pirate_ship.position) <= self.caught_ship_radius:
                self.ships[pirate_ship] = self.shipStates.chasing_ship
                return False  # already caught it

            # move ship there:
            for point in self.session.world.get_points_in_radius(
                    ship.position, self.caught_ship_radius - 1):
                try:
                    pirate_ship.move(point,
                                     Callback(self.send_ship, pirate_ship))
                except MoveNotPossible:
                    continue
                self.log.debug(
                    'Pirate %s: chasing %s (next point %d, %d)' %
                    (self.worldid, pirate_ship.name, point.x, point.y))
                self.ships[pirate_ship] = self.shipStates.chasing_ship
                return True
        return False

    def predict_player_position(self, pirate_ship):
        """Moves the pirate_ship to one of it's visibility points where 
		   the player has spent the maximum time"""

        self.ships[pirate_ship] = self.shipStates.moving_random
        self.log.debug(
            'Pirate %s: send_ship(%s) new state: %s' %
            (self.worldid, pirate_ship.name, self.ships[pirate_ship]))
        # See if we have reached the destination
        if pirate_ship.position.distance_to_point(
                self.visibility_points[0][0]) <= self.home_radius:
            # Every unsuccessful try halves the chances of ship being on that point next time
            self.visibility_points[0][1] = self.visibility_points[0][1] / 2

            self.visibility_points.sort(key=self._sort_preference)
            max_point = self.visibility_points[0]
            if max_point[1] is 0:  # choose a random location in this case
                # TODO: If this seems inefficient in larger maps, change to 'shift elements by 1'
                index = self.session.random.randint(
                    0,
                    len(self.visibility_points) - 1)
                max_point = self.visibility_points[index]
                self.visibility_points[index] = self.visibility_points[0]
                self.visibility_points[0] = max_point
            self.visible_player = [True] * len(self.visibility_points)
            self.log.debug(
                'Pirate %s: ship %s, Frequencies of visibility_points:' %
                (self.worldid, pirate_ship.name))
            for point in self.visibility_points:
                self.log.debug('(%d, %d) -> %d' %
                               (point[0].x, point[0].y, point[1]))
        # Consider the points that are being visited while visiting the destination point, and reduce
        # the frequency values for them
        elif len(self.visible_player) is len(self.visibility_points):
            for point in self.visibility_points:
                index = self.visibility_points.index(point)
                if self.visible_player[index] and pirate_ship.position.distance_to_point\
                 (self.visibility_points[index][0]) <= self.home_radius:
                    self.visibility_points[index][
                        1] = self.visibility_points[index][1] / 2
                    self.visible_player[index] = False
        try:
            pirate_ship.move(self.visibility_points[0][00])
        except MoveNotPossible:
            self.notify_unit_path_blocked(pirate_ship)
            return

    def _sort_preference(self, a):
        return -a[1]

    def calculate_visibility_points(self, pirate_ship):
        """Finds out the points from which the entire map will be accessible.
		Should be called only once either during initialization or loading"""
        rect = self.session.world.map_dimensions.get_corners()
        x_min, x_max, y_min, y_max = rect[0][0], rect[1][0], rect[0][0], rect[
            2][1]

        # Necessary points (with tolerable overlapping) which cover the entire map
        # Each element contains two values: it's co-ordinate, and frequency of player ship's visit
        self.visibility_points = []
        y = y_min + self.sight_radius
        while y <= y_max:
            x = x_min + self.sight_radius
            while x <= x_max:
                self.visibility_points.append([Point(x, y), 0])
                x += self.sight_radius  # should be 2*self.sight_radius for no overlap
            y += self.sight_radius

        self.log.debug("Pirate %s: %s, visibility points" %
                       (self.worldid, pirate_ship.name))
        copy_points = list(self.visibility_points)
        for point in copy_points:
            if not self.session.world.get_tile(point[0]).is_water:
                # Make a position compromise to obtain a water tile on the point
                for x in (3, -3, 2, -2, 1, -1, 0):
                    for y in (3, -3, 2, -2, 1, -1, 0):
                        new_point = Point(point[0].x + x, point[0].y + y)
                        if self.session.world.get_tile(new_point).is_water:
                            self.visibility_points.remove(
                                point)  #remove old point
                            point[0] = new_point
                            self.visibility_points.append([point[0],
                                                           0])  #add new point
                            break
                    if self.session.world.get_tile(point[0]).is_water: break
                if not self.session.world.get_tile(point[0]).is_water:
                    self.visibility_points.remove(point)
                    continue
            self.log.debug("(%d, %d)" % (point[0].x, point[0].y))
        # A list needed to reduce visibility frequencies of a point if player is not sighted on that point
        self.visible_player = []
Example #10
0
class Trader(AIPlayer):
	"""A trader represents the free trader that travels around the map with his trading ship(s) and
	sells resources to players and buys resources from them. This is a very simple form of AI, as it
	doesn't do any more then drive to a place on water or a branchoffice randomly and then buys and
	sells resources. A game should not have more then one free trader (it could though)
	@param id: int - player id, every Player needs a unique id, as the freetrader is a Player instance, he also does.
	@param name: Traders name, also needed for the Player class.
	@param color: util.Color instance with the traders banner color, also needed for the Player class"""

	shipStates = Enum.get_extended(AIPlayer.shipStates, 'moving_to_branch', 'reached_branch')

	log = logging.getLogger("ai.trader")

	def __init__(self, session, id, name, color, **kwargs):
		super(Trader, self).__init__(session, id, name, color, **kwargs)
		self.__init()
		self.create_ship()

	def create_ship(self):
		"""Create a ship and place it randomly"""
		self.log.debug("Trader %s: creating new ship", self.worldid)
		point = self.session.world.get_random_possible_ship_position()
		ship = CreateUnit(self.worldid, UNITS.TRADER_SHIP_CLASS, point.x, point.y)(issuer=self)
		self.ships[ship] = self.shipStates.reached_branch
		Scheduler().add_new_object(Callback(self.ship_idle, ship), self, run_in=0)

	def __init(self):
		self.office = {} # { ship.worldid : branch }. stores the branch the ship is currently heading to
		self.allured_by_signal_fire = {} # bool, used to get away from a signal fire (and not be allured again immediately)

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

		# mark self as a trader
		db("UPDATE player SET is_trader = 1 WHERE rowid = ?", self.worldid)

		for ship in self.ships:
			# prepare values
			ship_state = self.ships[ship]

			remaining_ticks = None
			# get current callback in scheduler, according to ship state, to retrieve
			# the number of ticks, when the call will actually be done
			current_callback = None
			if ship_state == self.shipStates.reached_branch:
				current_callback = Callback(self.ship_idle, ship)
			if current_callback is not None:
				# current state has a callback
				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)

			targeted_branch = None if ship.worldid not in self.office else self.office[ship.worldid].worldid

			# put them in the database
			db("INSERT INTO trader_ships(rowid, state, remaining_ticks, targeted_branch) \
			   VALUES(?, ?, ?, ?)", ship.worldid, ship_state.index, remaining_ticks, targeted_branch)

	def _load(self, db, worldid):
		super(Trader, self)._load(db, worldid)
		self.__init()

	def load_ship_states(self, db):
		# load ships one by one from db (ship instances themselves are loaded already, but
		# we have to use them here)
		for ship_id, state_id, remaining_ticks, targeted_branch in \
				db("SELECT rowid, state, remaining_ticks, targeted_branch FROM trader_ships"):
			state = self.shipStates[state_id]
			ship = WorldObject.get_object_by_id(ship_id)

			self.ships[ship] = state

			if state == self.shipStates.moving_random:
				ship.add_move_callback(Callback(self.ship_idle, ship))
			elif state == self.shipStates.moving_to_branch:
				ship.add_move_callback(Callback(self.reached_branch, ship))
				assert targeted_branch is not None
				self.office[ship.worldid] = WorldObject.get_object_by_id(targeted_branch)
			elif state == self.shipStates.reached_branch:
				assert remaining_ticks is not None
				Scheduler().add_new_object( \
					Callback(self.ship_idle, ship), self, remaining_ticks)

	def get_ship_count(self):
		"""Returns number of ships"""
		return len(self.ships)

	def send_ship_random(self, ship):
		"""Sends a ship to a random position on the map.
		@param ship: Ship instance that is to be used"""
		super(Trader, self).send_ship_random(ship)
		ship.add_conditional_callback(Callback(self._check_for_signal_fire_in_ship_range, ship), \
		                              callback=Callback(self._ship_found_signal_fire, ship))

	def _check_for_signal_fire_in_ship_range(self, ship):
		"""Returns the signal fire instance, if there is one in the ships range, else False"""
		if ship in self.allured_by_signal_fire and self.allured_by_signal_fire[ship]:
			return False # don't visit signal fire again
		for tile in self.session.world.get_tiles_in_radius(ship.position, ship.radius):
			try:
				if tile.object.id == BUILDINGS.SIGNAL_FIRE_CLASS:
					return tile.object
			except AttributeError:
				pass # tile has no object or object has no id
		return False

	def _ship_found_signal_fire(self, ship):
		signal_fire = self._check_for_signal_fire_in_ship_range(ship)
		self.log.debug("Trader %s ship %s found signal fire %s", self.worldid, ship.worldid, signal_fire)
		# search a branch office in the range of the signal fire and move to it
		branch_offices = self.session.world.get_branch_offices()
		for bo in branch_offices:
			if bo.position.distance(signal_fire.position) <= signal_fire.radius and \
			   bo.owner == signal_fire.owner:
				self.log.debug("Trader %s moving to bo %s", self.worldid, bo)
				self.allured_by_signal_fire[ship] = True
				# HACK: remove allured flag in a few ticks
				def rem_allured(self, ship): self.allured_by_signal_fire[ship] = False
				Scheduler().add_new_object(Callback(rem_allured, self, ship), self, Scheduler().get_ticks(60))
				self.send_ship_random_branch(ship, bo)
				return
		self.log.debug("Trader can't find bo in range of the signal fire")

	def send_ship_random_branch(self, ship, branch_office=None):
		"""Sends a ship to a random branch office on the map
		@param ship: Ship instance that is to be used
		@param branch_office: Branch Office instance to move to. Random one is selected on None."""
		self.log.debug("Trader %s ship %s moving to bo (random=%s)", self.worldid, ship.worldid, \
		               (branch_office is None))
		# maybe this kind of list should be saved somewhere, as this is pretty performance intense
		branchoffices = self.session.world.get_branch_offices()
		if len(branchoffices) == 0:
			# there aren't any branch offices, so move randomly
			self.send_ship_random(ship)
		else:
			# select a branch office
			if branch_office is None:
				rand = self.session.random.randint(0, len(branchoffices)-1)
				self.office[ship.worldid] = branchoffices[rand]
			else:
				self.office[ship.worldid] = branch_office
			# try to find a possible position near the bo
			try:
				ship.move(Circle(self.office[ship.worldid].position.center(), ship.radius), Callback(self.reached_branch, ship))
				self.ships[ship] = self.shipStates.moving_to_branch
			except MoveNotPossible:
				self.send_ship_random(ship)

	def reached_branch(self, ship):
		"""Actions that need to be taken when reaching a branch office
		@param ship: ship instance"""
		self.log.debug("Trader %s ship %s: reached bo", self.worldid, ship.worldid)
		settlement = self.office[ship.worldid].settlement
		# NOTE: must be sorted for mp games (same order everywhere)
		for res in sorted(settlement.buy_list.iterkeys()): # check for resources that the settlement wants to buy
			amount = self.session.random.randint(*TRADER.SELL_AMOUNT) # select a random amount to sell
			if amount == 0:
				continue
			price = int(self.session.db.get_res_value(res) * TRADER.PRICE_MODIFIER_SELL * amount)
			settlement.buy(res, amount, price)
			# don't care if he bought it. the trader just offers.
			self.log.debug("Trader %s: offered sell %s tons of res %s", self.worldid, amount, res)

		# NOTE: must be sorted for mp games (same order everywhere)
		for res in sorted(settlement.sell_list.iterkeys()):
			# select a random amount to buy from the settlement
			amount = self.session.random.randint(*TRADER.BUY_AMOUNT)
			if amount == 0:
				continue
			price = int(self.session.db.get_res_value(res) * TRADER.PRICE_MODIFIER_BUY * amount)
			settlement.sell(res, amount, price)
			self.log.debug("Trader %s: offered buy %s tons of res %s", self.worldid, amount, res)

		del self.office[ship.worldid]
		# wait 2 seconds before going on to the next island
		Scheduler().add_new_object(Callback(self.ship_idle, ship), self, \
		                           Scheduler().get_ticks(TRADER.TRADING_DURATION))
		self.ships[ship] = self.shipStates.reached_branch

	def ship_idle(self, ship):
		"""Called if a ship is idle. Sends ship to a random place or  branch office (which target
		to use is decided by chance, probability for branch office (BUSINESS_S.) is 2/3 by default)
		@param ship: ship instance"""
		if self.session.random.randint(0, 100) < TRADER.BUSINESS_SENSE:
			# delay one tick, to allow old movement calls to completely finish
			self.log.debug("Trader %s ship %s: idle, moving to random bo", self.worldid, ship.worldid)
			Scheduler().add_new_object(Callback(self.send_ship_random_branch, ship), self, run_in=0)
		else:
			self.log.debug("Trader %s ship %s: idle, moving to random location", self.worldid, ship.worldid)
			Scheduler().add_new_object(Callback(self.send_ship_random, ship), self, run_in=0)