def __init__(self, owner, land_manager): super().__init__() self.owner = owner self.resource_manager = ResourceManager(self) self.trade_manager = TradeManager(self) self.__init(land_manager) self.village_builder = VillageBuilder(self) self.production_builder = ProductionBuilder(self) self.village_builder.display() self.production_builder.display() self.__init_goals() if not self.feeder_island: self._set_taxes_and_permissions( self.personality.initial_sailor_taxes, self.personality.initial_pioneer_taxes, self.personality.initial_settler_taxes, self.personality.initial_citizen_taxes, self.personality.initial_merchants_taxes, self.personality.initial_sailor_upgrades, self.personality.initial_pioneer_upgrades, self.personality.initial_settler_upgrades, self.personality.initial_citizen_upgrades)
def _load(self, db, owner, worldid): self.owner = owner super(SettlementManager, self).load(db, worldid) # load the main part land_manager_id = db( "SELECT land_manager FROM ai_settlement_manager WHERE rowid = ?", worldid)[0][0] land_manager = WorldObject.get_object_by_id(land_manager_id) # find the settlement for settlement in self.owner.session.world.settlements: if settlement.owner == self.owner and settlement.island == land_manager.island: land_manager.settlement = settlement break assert land_manager.settlement self.resource_manager = ResourceManager.load(db, self) self.trade_manager = TradeManager.load(db, self) self.__init(land_manager) # load the master builders self.village_builder = VillageBuilder.load(db, self) self.production_builder = ProductionBuilder.load(db, self) self.village_builder.display() self.production_builder.display() self.__init_goals() # the add_building events happen before the settlement manager is loaded so they have to be repeated here for building in self.settlement.buildings: self.add_building(building)
def _load(self, db, owner, worldid): self.owner = owner super(SettlementManager, self).load(db, worldid) # load the main part land_manager_id = db("SELECT land_manager FROM ai_settlement_manager WHERE rowid = ?", worldid)[0][0] land_manager = WorldObject.get_object_by_id(land_manager_id) # find the settlement for settlement in self.owner.session.world.settlements: if settlement.owner == self.owner and settlement.island == land_manager.island: land_manager.settlement = settlement break assert land_manager.settlement self.resource_manager = ResourceManager.load(db, self) self.trade_manager = TradeManager.load(db, self) self.__init(land_manager) # load the master builders self.village_builder = VillageBuilder.load(db, self) self.production_builder = ProductionBuilder.load(db, self) self.village_builder.display() self.production_builder.display() self.__init_goals() # the add_building events happen before the settlement manager is loaded so they have to be repeated here for building in self.settlement.buildings: self.add_building(building)
def __init__(self, owner, land_manager): super(SettlementManager, self).__init__() self.owner = owner self.resource_manager = ResourceManager(self) self.trade_manager = TradeManager(self) self.__init(land_manager) self.village_builder = VillageBuilder(self) self.production_builder = ProductionBuilder(self) self.village_builder.display() self.production_builder.display() self.__init_goals() if not self.feeder_island: self._set_taxes_and_permissions(self.personality.initial_sailor_taxes, self.personality.initial_pioneer_taxes, \ self.personality.initial_settler_taxes, self.personality.initial_sailor_upgrades, self.personality.initial_pioneer_upgrades)
class SettlementManager(WorldObject): """ This is the main settlement control class. Important attributes: * feeder_island: boolean showing whether the island is a feeder island (feeder islands have no village area) * island: Island instance * settlement: Settlement instance * land_manager: LandManager instance * production_chain: dictionary where the key is a resource id and the value is the ProductionChain instance * production_builder: ProductionBuilder instance * village_builder: VillageBuilder instance * resource_manager: ResourceManager instance * trade_manager: TradeManager instance """ log = logging.getLogger("ai.aiplayer") def __init__(self, owner, land_manager): super(SettlementManager, self).__init__() self.owner = owner self.resource_manager = ResourceManager(self) self.trade_manager = TradeManager(self) self.__init(land_manager) self.village_builder = VillageBuilder(self) self.production_builder = ProductionBuilder(self) self.village_builder.display() self.production_builder.display() self.__init_goals() if not self.feeder_island: self._set_taxes_and_permissions( self.personality.initial_sailor_taxes, self.personality.initial_pioneer_taxes, self.personality.initial_citizen_taxes, self.personality.initial_settler_taxes, self.personality.initial_sailor_upgrades, self.personality.initial_pioneer_upgrades, self.personality.initial_settler_upgrades) def __init(self, land_manager): self.owner = land_manager.owner self.session = self.owner.session self.land_manager = land_manager self.island = self.land_manager.island self.settlement = self.land_manager.settlement self.feeder_island = land_manager.feeder_island self.personality = self.owner.personality_manager.get( 'SettlementManager') # create a production chain for every building material, settler consumed resource, and resources that have to be imported from feeder islands self.production_chain = {} for resource_id in [ RES.COMMUNITY, RES.BOARDS, RES.FOOD, RES.TEXTILE, RES.FAITH, RES.EDUCATION, RES.GET_TOGETHER, RES.BRICKS, RES.TOOLS, RES.LIQUOR, RES.TOBACCO_PRODUCTS, RES.SALT, RES.MEDICAL_HERBS ]: self.production_chain[resource_id] = ProductionChain.create( self, resource_id) # initialize caches self.__resident_resource_usage_cache = {} def __init_goals(self): """Initialize the list of all the goals the settlement can use.""" self._goals = [] # [SettlementGoal, ...] self._goals.append(BoardsGoal(self)) self._goals.append(SignalFireGoal(self)) self._goals.append(EnlargeCollectorAreaGoal(self)) self._goals.append(ImproveCollectorCoverageGoal(self)) self._goals.append(BricksGoal(self)) if self.feeder_island: self._goals.append(StorageSpaceGoal(self)) self._goals.append(FeederFoodGoal(self)) self._goals.append(FeederTextileGoal(self)) self._goals.append(FeederLiquorGoal(self)) self._goals.append(FeederSaltGoal(self)) self._goals.append(FeederTobaccoProductsGoal(self)) self._goals.append(FeederMedicalProductsGoal(self)) else: self._goals.append(BoatBuilderGoal(self)) self._goals.append(ClayDepositCoverageGoal(self)) self._goals.append(FoundFeederIslandGoal(self)) self._goals.append(MountainCoverageGoal(self)) self._goals.append(FoodGoal(self)) self._goals.append(CommunityGoal(self)) self._goals.append(FaithGoal(self)) self._goals.append(TextileGoal(self)) self._goals.append(EducationGoal(self)) self._goals.append(GetTogetherGoal(self)) self._goals.append(SaltGoal(self)) self._goals.append(TobaccoProductsGoal(self)) self._goals.append(ToolsGoal(self)) self._goals.append(TentGoal(self)) self._goals.append(TradingShipGoal(self)) self._goals.append(CombatShipGoal(self)) self._goals.append(FireStationGoal(self)) self._goals.append(DoctorGoal(self)) self._goals.append(MedicalHerbsProductsGoal(self)) def save(self, db): super(SettlementManager, self).save(db) db( "INSERT INTO ai_settlement_manager(rowid, land_manager) VALUES(?, ?)", self.worldid, self.land_manager.worldid) self.village_builder.save(db) self.production_builder.save(db) self.resource_manager.save(db) self.trade_manager.save(db) @classmethod def load(cls, db, owner, worldid): self = cls.__new__(cls) self._load(db, owner, worldid) return self def _load(self, db, owner, worldid): self.owner = owner super(SettlementManager, self).load(db, worldid) # load the main part land_manager_id = db( "SELECT land_manager FROM ai_settlement_manager WHERE rowid = ?", worldid)[0][0] land_manager = WorldObject.get_object_by_id(land_manager_id) # find the settlement for settlement in self.owner.session.world.settlements: if settlement.owner == self.owner and settlement.island == land_manager.island: land_manager.settlement = settlement break assert land_manager.settlement self.resource_manager = ResourceManager.load(db, self) self.trade_manager = TradeManager.load(db, self) self.__init(land_manager) # load the master builders self.village_builder = VillageBuilder.load(db, self) self.production_builder = ProductionBuilder.load(db, self) self.village_builder.display() self.production_builder.display() self.__init_goals() # the add_building events happen before the settlement manager is loaded so they have to be repeated here for building in self.settlement.buildings: self.add_building(building) def _set_taxes_and_permissions(self, sailor_taxes, pioneer_taxes, settler_taxes, citizen_taxes, sailor_upgrades, pioneer_upgrades, settler_upgrades): """Set new tax settings and building permissions.""" if abs(self.settlement.tax_settings[TIER.SAILORS] - sailor_taxes) > 1e-9: self.log.info("%s set sailors' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.SAILORS], sailor_taxes) SetTaxSetting(self.settlement, TIER.SAILORS, sailor_taxes).execute(self.land_manager.session) if abs(self.settlement.tax_settings[TIER.PIONEERS] - pioneer_taxes) > 1e-9: self.log.info("%s set pioneers' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.PIONEERS], pioneer_taxes) SetTaxSetting(self.settlement, TIER.PIONEERS, pioneer_taxes).execute(self.land_manager.session) if abs(self.settlement.tax_settings[TIER.SETTLERS] - settler_taxes) > 1e-9: self.log.info("%s set settlers' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.SETTLERS], settler_taxes) SetTaxSetting(self.settlement, TIER.SETTLERS, settler_taxes).execute(self.land_manager.session) if abs(self.settlement.tax_settings[TIER.CITIZENS] - citizen_taxes) > 1e-9: self.log.info("%s set citizens' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.CITIZENS], citizen_taxes) SetTaxSetting(self.settlement, TIER.CITIZENS, citizen_taxes).execute(self.land_manager.session) if self.settlement.upgrade_permissions[ TIER.SAILORS] != sailor_upgrades: self.log.info('%s set sailor upgrade permissions to %s', self, sailor_upgrades) SetSettlementUpgradePermissions(self.settlement, TIER.SAILORS, sailor_upgrades).execute( self.land_manager.session) if self.settlement.upgrade_permissions[ TIER.PIONEERS] != pioneer_upgrades: self.log.info('%s set pioneer upgrade permissions to %s', self, pioneer_upgrades) SetSettlementUpgradePermissions(self.settlement, TIER.PIONEERS, pioneer_upgrades).execute( self.land_manager.session) if self.settlement.upgrade_permissions[ TIER.SETTLERS] != settler_upgrades: self.log.info('%s set settler upgrade permissions to %s', self, settler_upgrades) SetSettlementUpgradePermissions(self.settlement, TIER.SETTLERS, settler_upgrades).execute( self.land_manager.session) def _set_taxes_and_permissions_prefix(self, prefix): """Set new tax settings and building permissions according to the prefix used in the personality file.""" sailor_taxes = getattr(self.personality, '%s_sailor_taxes' % prefix) pioneer_taxes = getattr(self.personality, '%s_pioneer_taxes' % prefix) settler_taxes = getattr(self.personality, '%s_settler_taxes' % prefix) citizen_taxes = getattr(self.personality, '%s_citizen_taxes' % prefix) sailor_upgrades = getattr(self.personality, '%s_sailor_upgrades' % prefix) pioneer_upgrades = getattr(self.personality, '%s_pioneer_upgrades' % prefix) settler_upgrades = getattr(self.personality, '%s_settler_upgrades' % prefix) self._set_taxes_and_permissions(sailor_taxes, pioneer_taxes, settler_taxes, citizen_taxes, sailor_upgrades, pioneer_upgrades, settler_upgrades) def can_provide_resources(self): """Return a boolean showing whether this settlement is complete enough to concentrate on building a new settlement.""" if self.village_builder.tent_queue: return False settler_houses = 0 residences = self.settlement.buildings_by_id.get( BUILDINGS.RESIDENTIAL, []) for building in residences: if building.level >= TIER.SETTLERS: settler_houses += 1 if settler_houses > len( residences) * self.personality.new_settlement_settler_ratio: return True return False def get_resource_production(self, resource_id): """Return the current production capacity (including import) per tick of the given resource.""" # as long as there are enough collectors it is correct to calculate it this way if resource_id == RES.LIQUOR and not self.feeder_island: # normal settlements go straight for get-together so their separate liquor production is zero. # feeder islands have to produce liquor because get-together is not tradable return self.get_resource_production( RES.GET_TOGETHER) * self.production_chain[ RES.GET_TOGETHER].get_ratio(RES.LIQUOR) else: return self.production_chain[ resource_id].get_final_production_level() def get_resource_production_requirement(self, resource_id): """Return the amount of resource per tick the settlement needs.""" if resource_id not in self.__resident_resource_usage_cache or self.__resident_resource_usage_cache[ resource_id][0] != Scheduler().cur_tick: total = 0 if resource_id == RES.BRICKS: total = self.personality.dummy_bricks_requirement if self.owner.settler_level > 0 else 0 # dummy value to cause bricks production to be built elif resource_id == RES.BOARDS: total = self.personality.dummy_boards_requirement # dummy value to cause boards production to be built elif resource_id == RES.TOOLS: total = self.personality.dummy_tools_requirement if self.owner.settler_level > 1 else 0 # dummy value to cause tools production to be built elif resource_id == RES.LIQUOR: total = self.production_chain[RES.GET_TOGETHER].get_ratio( RES.LIQUOR) * self.get_resource_production_requirement( RES.GET_TOGETHER) else: for residence in self.settlement.buildings_by_id.get( BUILDINGS.RESIDENTIAL, []): for production in residence.get_component( Producer).get_productions(): production_line = production._prod_line if resource_id in production_line.consumed_res: # subtract because the amount will be negative total -= float( production_line.consumed_res[resource_id] ) / production_line.time / GAME_SPEED.TICKS_PER_SECOND self.__resident_resource_usage_cache[resource_id] = ( Scheduler().cur_tick, total) return self.__resident_resource_usage_cache[resource_id][1] def _manual_upgrade(self, level, limit): """ Manually allow settlers to upgrade. If more then the set limit are already upgrading then don't stop them. @param level: the initial settler level from which to upgrade @param limit: the maximum number of residences of the specified level upgrading at the same time @return: boolean showing whether we gave any new residences the right to upgrade """ num_upgrading = 0 for building in self.settlement.buildings_by_id.get( BUILDINGS.RESIDENTIAL, []): if building.level == level: upgrade_production = building._upgrade_production if upgrade_production is not None and not upgrade_production.is_paused( ): num_upgrading += 1 if num_upgrading >= limit: return False upgraded_any = False for building in self.settlement.buildings_by_id.get( BUILDINGS.RESIDENTIAL, []): if building.level == level: upgrade_production = building._upgrade_production if upgrade_production is not None and upgrade_production.is_paused( ): ToggleActive(building.get_component(Producer), upgrade_production).execute( self.land_manager.session) num_upgrading += 1 upgraded_any = True if num_upgrading >= limit: return True return upgraded_any def get_ideal_production_level(self, resource_id): """ Return the amount of resource per tick the settlement should produce. This is the amount that should be produced to satisfy the people in this settlement, keep up the current export rate, and fix the player's global deficit. This means that different (feeder) islands will have different ideal production levels. """ total = 0.0 for settlement_manager in self.owner.settlement_managers: usage = settlement_manager.get_resource_production_requirement( resource_id) * self.personality.production_level_multiplier production = settlement_manager.get_resource_production( resource_id) resource_import = settlement_manager.trade_manager.get_total_import( resource_id) resource_export = settlement_manager.resource_manager.get_total_export( resource_id) total += usage if settlement_manager is not self: total -= production + resource_export - resource_import return max(0.0, total) def _start_feeder_tick(self): self.log.info('%s food requirement %.5f', self, self.get_ideal_production_level(RES.FOOD)) self.log.info('%s textile requirement %.5f', self, self.get_ideal_production_level(RES.TEXTILE)) self.log.info('%s liquor requirement %.5f', self, self.get_ideal_production_level(RES.LIQUOR)) self.log.info('%s salt requirement %.5f', self, self.get_ideal_production_level(RES.SALT)) self.log.info('%s tobacco products requirement %.5f', self, self.get_ideal_production_level(RES.TOBACCO_PRODUCTS)) self.log.info('%s medical herbs requirement %.5f', self, self.get_ideal_production_level(RES.MEDICAL_HERBS)) self.production_builder.manage_production() self.resource_manager.refresh() def _end_feeder_tick(self): self.resource_manager.replay_deep_low_priority_requests() self.resource_manager.record_expected_exportable_production( self.owner.tick_interval) self.resource_manager.manager_buysell() self.resource_manager.finish_tick() def _start_general_tick(self): self.log.info('%s food production %.5f / %.5f', self, self.get_resource_production(RES.FOOD), self.get_resource_production_requirement(RES.FOOD)) self.log.info('%s textile production %.5f / %.5f', self, self.get_resource_production(RES.TEXTILE), self.get_resource_production_requirement(RES.TEXTILE)) self.log.info( '%s get-together production %.5f / %.5f', self, self.get_resource_production(RES.GET_TOGETHER), self.get_resource_production_requirement(RES.GET_TOGETHER)) self.log.info('%s salt production %.5f / %.5f', self, self.get_resource_production(RES.SALT), self.get_resource_production_requirement(RES.SALT)) self.log.info( '%s tobacco products production %.5f / %.5f', self, self.get_resource_production(RES.TOBACCO_PRODUCTS), self.get_resource_production_requirement(RES.TOBACCO_PRODUCTS)) self.log.info( '%s medical herbs production %.5f / %.5f', self, self.get_resource_production(RES.MEDICAL_HERBS), self.get_resource_production_requirement(RES.MEDICAL_HERBS)) self.production_builder.manage_production() self.trade_manager.refresh() self.resource_manager.refresh() self.need_materials = False def refresh_taxes_and_upgrade_permissions(self): # TODO: use a better system for managing settler upgrades and taxes if self.land_manager.owner.settler_level == 0: # if we are on level 0 and there is a house that can be upgraded then do it. if self._manual_upgrade(0, 1): self._set_taxes_and_permissions_prefix('early') elif self.get_resource_production( RES.BRICKS) > 1e-9 and not self.settlement.count_buildings( BUILDINGS.VILLAGE_SCHOOL): # if we just need the school then upgrade sailors manually free_boards = self.settlement.get_component( StorageComponent).inventory[RES.BOARDS] free_boards -= Entities.buildings[BUILDINGS.VILLAGE_SCHOOL].costs[ RES.BOARDS] free_boards /= 2 # TODO: load this from upgrade resources if free_boards > 0: self._manual_upgrade(0, free_boards) self._set_taxes_and_permissions_prefix('no_school') elif self.settlement.count_buildings(BUILDINGS.VILLAGE_SCHOOL): if self.need_materials: self._set_taxes_and_permissions_prefix('school') else: self._set_taxes_and_permissions_prefix('final') def _end_general_tick(self): self.trade_manager.finalize_requests() self.trade_manager.organize_shipping() self.resource_manager.record_expected_exportable_production( self.owner.tick_interval) self.resource_manager.manager_buysell() self.resource_manager.finish_tick() def _add_goals(self, goals): """Add the settlement's goals that can be activated to the goals list.""" for goal in self._goals: if goal.can_be_activated: goal.update() goals.append(goal) def tick(self, goals): """Refresh the settlement info and add its goals to the player's goal list.""" if self.feeder_island: self._start_feeder_tick() self._add_goals(goals) self._end_feeder_tick() else: self._start_general_tick() self._add_goals(goals) self._end_general_tick() def add_building(self, building): """Called when a new building is added to the settlement (the building already exists during the call).""" coords = building.position.origin.to_tuple() if coords in self.village_builder.plan: self.village_builder.add_building(building) else: self.production_builder.add_building(building) def remove_building(self, building): """Called when a building is removed from the settlement (the building still exists during the call).""" coords = building.position.origin.to_tuple() if coords in self.village_builder.plan: self.village_builder.remove_building(building) else: self.production_builder.remove_building(building) def handle_lost_area(self, coords_list): """ Handle losing the potential land in the given coordinates list. Take the following actions: * remove the lost area from the village, production, and road areas * remove village sections with impossible main squares * remove all planned buildings that are now impossible from the village area * remove planned fields that are now impossible * remove fields that can no longer be serviced by a farm * TODO: if the village area takes too much of the total area then remove / reduce the remaining sections """ self.land_manager.handle_lost_area(coords_list) self.village_builder.handle_lost_area(coords_list) self.production_builder.handle_lost_area(coords_list) self.production_builder.handle_new_area( ) # some of the village area may have been repurposed as production area self.village_builder.display() self.production_builder.display() def handle_disaster(self, message): position = message.building.position if issubclass(message.disaster_class, BuildingInfluencingDisaster): rescue = message.disaster_class.RESCUE_BUILDING_TYPE else: self.log.info('%s ignoring unknown disaster of type %s', self, message.disaster_class.__name__) return rescue_radius = Entities.buildings[rescue].radius handled = False for b in self.settlement.buildings_by_id[rescue]: if b.position.distance(position) > rescue_radius: continue # TODO: check whether the building and the doctor/fire station are connected by road self.log.info( '%s ignoring %s at %s because %s should be able to handle it', self, message.disaster_class.__name__, message.building, rescue) handled = True break if not handled: self.log.info('%s removing %s because of %s', self, message.building, message.disaster_class.__name__) Tear(message.building).execute(self.session) def __str__(self): return '%s.SM(%s/%s)' % ( self.owner, self.settlement.get_component(NamedComponent).name if hasattr(self, 'settlement') else 'unknown', self.worldid if hasattr(self, 'worldid') else 'none')
class SettlementManager(WorldObject): """ This is the main settlement control class. Important attributes: * feeder_island: boolean showing whether the island is a feeder island (feeder islands have no village area) * island: Island instance * settlement: Settlement instance * land_manager: LandManager instance * production_chain: dictionary where the key is a resource id and the value is the ProductionChain instance * production_builder: ProductionBuilder instance * village_builder: VillageBuilder instance * resource_manager: ResourceManager instance * trade_manager: TradeManager instance """ log = logging.getLogger("ai.aiplayer") def __init__(self, owner, land_manager): super(SettlementManager, self).__init__() self.owner = owner self.resource_manager = ResourceManager(self) self.trade_manager = TradeManager(self) self.__init(land_manager) self.village_builder = VillageBuilder(self) self.production_builder = ProductionBuilder(self) self.village_builder.display() self.production_builder.display() self.__init_goals() if not self.feeder_island: self._set_taxes_and_permissions(self.personality.initial_sailor_taxes, self.personality.initial_pioneer_taxes, self.personality.initial_citizen_taxes, self.personality.initial_settler_taxes, self.personality.initial_sailor_upgrades, \ self.personality.initial_pioneer_upgrades, self.personality.initial_settler_upgrades) def __init(self, land_manager): self.owner = land_manager.owner self.session = self.owner.session self.land_manager = land_manager self.island = self.land_manager.island self.settlement = self.land_manager.settlement self.feeder_island = land_manager.feeder_island self.personality = self.owner.personality_manager.get('SettlementManager') # create a production chain for every building material, settler consumed resource, and resources that have to be imported from feeder islands self.production_chain = {} for resource_id in [RES.COMMUNITY, RES.BOARDS, RES.FOOD, RES.TEXTILE, RES.FAITH, RES.EDUCATION, RES.GET_TOGETHER, RES.BRICKS, RES.TOOLS, RES.LIQUOR, RES.TOBACCO_PRODUCTS, RES.SALT]: self.production_chain[resource_id] = ProductionChain.create(self, resource_id) # initialize caches self.__resident_resource_usage_cache = {} def __init_goals(self): """Initialize the list of all the goals the settlement can use.""" self._goals = [] # [SettlementGoal, ...] self._goals.append(BoardsGoal(self)) self._goals.append(SignalFireGoal(self)) self._goals.append(EnlargeCollectorAreaGoal(self)) self._goals.append(ImproveCollectorCoverageGoal(self)) self._goals.append(BricksGoal(self)) if self.feeder_island: self._goals.append(StorageSpaceGoal(self)) self._goals.append(FeederFoodGoal(self)) self._goals.append(FeederTextileGoal(self)) self._goals.append(FeederLiquorGoal(self)) self._goals.append(FeederSaltGoal(self)) self._goals.append(FeederTobaccoProductsGoal(self)) else: self._goals.append(BoatBuilderGoal(self)) self._goals.append(ClayDepositCoverageGoal(self)) self._goals.append(FoundFeederIslandGoal(self)) self._goals.append(MountainCoverageGoal(self)) self._goals.append(FoodGoal(self)) self._goals.append(CommunityGoal(self)) self._goals.append(FaithGoal(self)) self._goals.append(TextileGoal(self)) self._goals.append(EducationGoal(self)) self._goals.append(GetTogetherGoal(self)) self._goals.append(SaltGoal(self)) self._goals.append(TobaccoProductsGoal(self)) self._goals.append(ToolsGoal(self)) self._goals.append(TentGoal(self)) self._goals.append(TradingShipGoal(self)) self._goals.append(CombatShipGoal(self)) self._goals.append(FireStationGoal(self)) def save(self, db): super(SettlementManager, self).save(db) db("INSERT INTO ai_settlement_manager(rowid, land_manager) VALUES(?, ?)", self.worldid, self.land_manager.worldid) self.village_builder.save(db) self.production_builder.save(db) self.resource_manager.save(db) self.trade_manager.save(db) @classmethod def load(cls, db, owner, worldid): self = cls.__new__(cls) self._load(db, owner, worldid) return self def _load(self, db, owner, worldid): self.owner = owner super(SettlementManager, self).load(db, worldid) # load the main part land_manager_id = db("SELECT land_manager FROM ai_settlement_manager WHERE rowid = ?", worldid)[0][0] land_manager = WorldObject.get_object_by_id(land_manager_id) # find the settlement for settlement in self.owner.session.world.settlements: if settlement.owner == self.owner and settlement.island == land_manager.island: land_manager.settlement = settlement break assert land_manager.settlement self.resource_manager = ResourceManager.load(db, self) self.trade_manager = TradeManager.load(db, self) self.__init(land_manager) # load the master builders self.village_builder = VillageBuilder.load(db, self) self.production_builder = ProductionBuilder.load(db, self) self.village_builder.display() self.production_builder.display() self.__init_goals() # the add_building events happen before the settlement manager is loaded so they have to be repeated here for building in self.settlement.buildings: self.add_building(building) def _set_taxes_and_permissions(self, sailor_taxes, pioneer_taxes, settler_taxes, citizen_taxes, sailor_upgrades, pioneer_upgrades, settler_upgrades): """Set new tax settings and building permissions.""" if abs(self.settlement.tax_settings[TIER.SAILORS] - sailor_taxes) > 1e-9: self.log.info("%s set sailors' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.SAILORS], sailor_taxes) SetTaxSetting(self.settlement, TIER.SAILORS, sailor_taxes).execute(self.land_manager.session) if abs(self.settlement.tax_settings[TIER.PIONEERS] - pioneer_taxes) > 1e-9: self.log.info("%s set pioneers' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.PIONEERS], pioneer_taxes) SetTaxSetting(self.settlement, TIER.PIONEERS, pioneer_taxes).execute(self.land_manager.session) if abs(self.settlement.tax_settings[TIER.SETTLERS] - settler_taxes) > 1e-9: self.log.info("%s set settlers' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.SETTLERS], settler_taxes) SetTaxSetting(self.settlement, TIER.SETTLERS, settler_taxes).execute(self.land_manager.session) if abs(self.settlement.tax_settings[TIER.CITIZENS] - citizen_taxes) > 1e-9: self.log.info("%s set citizens' taxes from %.1f to %.1f", self, self.settlement.tax_settings[TIER.CITIZENS], citizen_taxes) SetTaxSetting(self.settlement, TIER.CITIZENS, citizen_taxes).execute(self.land_manager.session) if self.settlement.upgrade_permissions[TIER.SAILORS] != sailor_upgrades: self.log.info('%s set sailor upgrade permissions to %s', self, sailor_upgrades) SetSettlementUpgradePermissions(self.settlement, TIER.SAILORS, sailor_upgrades).execute(self.land_manager.session) if self.settlement.upgrade_permissions[TIER.PIONEERS] != pioneer_upgrades: self.log.info('%s set pioneer upgrade permissions to %s', self, pioneer_upgrades) SetSettlementUpgradePermissions(self.settlement, TIER.PIONEERS, pioneer_upgrades).execute(self.land_manager.session) if self.settlement.upgrade_permissions[TIER.SETTLERS] != settler_upgrades: self.log.info('%s set settler upgrade permissions to %s', self, settler_upgrades) SetSettlementUpgradePermissions(self.settlement, TIER.SETTLERS, settler_upgrades).execute(self.land_manager.session) def _set_taxes_and_permissions_prefix(self, prefix): """Set new tax settings and building permissions according to the prefix used in the personality file.""" sailor_taxes = getattr(self.personality, '%s_sailor_taxes' % prefix) pioneer_taxes = getattr(self.personality, '%s_pioneer_taxes' % prefix) settler_taxes = getattr(self.personality, '%s_settler_taxes' % prefix) citizen_taxes = getattr(self.personality, '%s_citizen_taxes' % prefix) sailor_upgrades = getattr(self.personality, '%s_sailor_upgrades' % prefix) pioneer_upgrades = getattr(self.personality, '%s_pioneer_upgrades' % prefix) settler_upgrades = getattr(self.personality, '%s_settler_upgrades' % prefix) self._set_taxes_and_permissions(sailor_taxes, pioneer_taxes, settler_taxes, citizen_taxes, sailor_upgrades, pioneer_upgrades, settler_upgrades) def can_provide_resources(self): """Return a boolean showing whether this settlement is complete enough to concentrate on building a new settlement.""" if self.village_builder.tent_queue: return False settler_houses = 0 residences = self.settlement.buildings_by_id.get(BUILDINGS.RESIDENTIAL, []) for building in residences: if building.level >= TIER.SETTLERS: settler_houses += 1 if settler_houses > len(residences) * self.personality.new_settlement_settler_ratio: return True return False def get_resource_production(self, resource_id): """Return the current production capacity (including import) per tick of the given resource.""" # as long as there are enough collectors it is correct to calculate it this way if resource_id == RES.LIQUOR and not self.feeder_island: # normal settlements go straight for get-together so their separate liquor production is zero. # feeder islands have to produce liquor because get-together is not tradable return self.get_resource_production(RES.GET_TOGETHER) * self.production_chain[RES.GET_TOGETHER].get_ratio(RES.LIQUOR) else: return self.production_chain[resource_id].get_final_production_level() def get_resource_production_requirement(self, resource_id): """Return the amount of resource per tick the settlement needs.""" if resource_id not in self.__resident_resource_usage_cache or self.__resident_resource_usage_cache[resource_id][0] != Scheduler().cur_tick: total = 0 if resource_id == RES.BRICKS: total = self.personality.dummy_bricks_requirement if self.owner.settler_level > 0 else 0 # dummy value to cause bricks production to be built elif resource_id == RES.BOARDS: total = self.personality.dummy_boards_requirement # dummy value to cause boards production to be built elif resource_id == RES.TOOLS: total = self.personality.dummy_tools_requirement if self.owner.settler_level > 1 else 0 # dummy value to cause tools production to be built elif resource_id == RES.LIQUOR: total = self.production_chain[RES.GET_TOGETHER].get_ratio(RES.LIQUOR) * self.get_resource_production_requirement(RES.GET_TOGETHER) else: for residence in self.settlement.buildings_by_id.get(BUILDINGS.RESIDENTIAL, []): for production in residence.get_component(Producer).get_productions(): production_line = production._prod_line if resource_id in production_line.consumed_res: # subtract because the amount will be negative total -= float(production_line.consumed_res[resource_id]) / production_line.time / GAME_SPEED.TICKS_PER_SECOND self.__resident_resource_usage_cache[resource_id] = (Scheduler().cur_tick, total) return self.__resident_resource_usage_cache[resource_id][1] def _manual_upgrade(self, level, limit): """ Manually allow settlers to upgrade. If more then the set limit are already upgrading then don't stop them. @param level: the initial settler level from which to upgrade @param limit: the maximum number of residences of the specified level upgrading at the same time @return: boolean showing whether we gave any new residences the right to upgrade """ num_upgrading = 0 for building in self.settlement.buildings_by_id.get(BUILDINGS.RESIDENTIAL, []): if building.level == level: upgrade_production = building._upgrade_production if upgrade_production is not None and not upgrade_production.is_paused(): num_upgrading += 1 if num_upgrading >= limit: return False upgraded_any = False for building in self.settlement.buildings_by_id.get(BUILDINGS.RESIDENTIAL, []): if building.level == level: upgrade_production = building._upgrade_production if upgrade_production is not None and upgrade_production.is_paused(): ToggleActive(building.get_component(Producer), upgrade_production).execute(self.land_manager.session) num_upgrading += 1 upgraded_any = True if num_upgrading >= limit: return True return upgraded_any def get_ideal_production_level(self, resource_id): """ Return the amount of resource per tick the settlement should produce. This is the amount that should be produced to satisfy the people in this settlement, keep up the current export rate, and fix the player's global deficit. This means that different (feeder) islands will have different ideal production levels. """ total = 0.0 for settlement_manager in self.owner.settlement_managers: usage = settlement_manager.get_resource_production_requirement(resource_id) * self.personality.production_level_multiplier production = settlement_manager.get_resource_production(resource_id) resource_import = settlement_manager.trade_manager.get_total_import(resource_id) resource_export = settlement_manager.resource_manager.get_total_export(resource_id) total += usage if settlement_manager is not self: total -= production + resource_export - resource_import return max(0.0, total) def _start_feeder_tick(self): self.log.info('%s food requirement %.5f', self, self.get_ideal_production_level(RES.FOOD)) self.log.info('%s textile requirement %.5f', self, self.get_ideal_production_level(RES.TEXTILE)) self.log.info('%s liquor requirement %.5f', self, self.get_ideal_production_level(RES.LIQUOR)) self.log.info('%s salt requirement %.5f', self, self.get_ideal_production_level(RES.SALT)) self.log.info('%s tobacco products requirement %.5f', self, self.get_ideal_production_level(RES.TOBACCO_PRODUCTS)) self.production_builder.manage_production() self.resource_manager.refresh() def _end_feeder_tick(self): self.resource_manager.replay_deep_low_priority_requests() self.resource_manager.record_expected_exportable_production(self.owner.tick_interval) self.resource_manager.manager_buysell() self.resource_manager.finish_tick() def _start_general_tick(self): self.log.info('%s food production %.5f / %.5f', self, self.get_resource_production(RES.FOOD), self.get_resource_production_requirement(RES.FOOD)) self.log.info('%s textile production %.5f / %.5f', self, self.get_resource_production(RES.TEXTILE), self.get_resource_production_requirement(RES.TEXTILE)) self.log.info('%s get-together production %.5f / %.5f', self, self.get_resource_production(RES.GET_TOGETHER), self.get_resource_production_requirement(RES.GET_TOGETHER)) self.log.info('%s salt production %.5f / %.5f', self, self.get_resource_production(RES.SALT), self.get_resource_production_requirement(RES.SALT)) self.log.info('%s tobacco products production %.5f / %.5f', self, self.get_resource_production(RES.TOBACCO_PRODUCTS), self.get_resource_production_requirement(RES.TOBACCO_PRODUCTS)) self.production_builder.manage_production() self.trade_manager.refresh() self.resource_manager.refresh() self.need_materials = False def refresh_taxes_and_upgrade_permissions(self): # TODO: use a better system for managing settler upgrades and taxes if self.land_manager.owner.settler_level == 0: # if we are on level 0 and there is a house that can be upgraded then do it. if self._manual_upgrade(0, 1): self._set_taxes_and_permissions_prefix('early') elif self.get_resource_production(RES.BRICKS) > 1e-9 and not self.settlement.count_buildings(BUILDINGS.VILLAGE_SCHOOL): # if we just need the school then upgrade sailors manually free_boards = self.settlement.get_component(StorageComponent).inventory[RES.BOARDS] free_boards -= Entities.buildings[BUILDINGS.VILLAGE_SCHOOL].costs[RES.BOARDS] free_boards /= 2 # TODO: load this from upgrade resources if free_boards > 0: self._manual_upgrade(0, free_boards) self._set_taxes_and_permissions_prefix('no_school') elif self.settlement.count_buildings(BUILDINGS.VILLAGE_SCHOOL): if self.need_materials: self._set_taxes_and_permissions_prefix('school') else: self._set_taxes_and_permissions_prefix('final') def _end_general_tick(self): self.trade_manager.finalize_requests() self.trade_manager.organize_shipping() self.resource_manager.record_expected_exportable_production(self.owner.tick_interval) self.resource_manager.manager_buysell() self.resource_manager.finish_tick() def _add_goals(self, goals): """Add the settlement's goals that can be activated to the goals list.""" for goal in self._goals: if goal.can_be_activated: goal.update() goals.append(goal) def tick(self, goals): """Refresh the settlement info and add its goals to the player's goal list.""" if self.feeder_island: self._start_feeder_tick() self._add_goals(goals) self._end_feeder_tick() else: self._start_general_tick() self._add_goals(goals) self._end_general_tick() def add_building(self, building): """Called when a new building is added to the settlement (the building already exists during the call).""" coords = building.position.origin.to_tuple() if coords in self.village_builder.plan: self.village_builder.add_building(building) else: self.production_builder.add_building(building) def remove_building(self, building): """Called when a building is removed from the settlement (the building still exists during the call).""" coords = building.position.origin.to_tuple() if coords in self.village_builder.plan: self.village_builder.remove_building(building) else: self.production_builder.remove_building(building) def handle_lost_area(self, coords_list): """ Handle losing the potential land in the given coordinates list. Take the following actions: * remove the lost area from the village, production, and road areas * remove village sections with impossible main squares * remove all planned buildings that are now impossible from the village area * remove planned fields that are now impossible * remove fields that can no longer be serviced by a farm * TODO: if the village area takes too much of the total area then remove / reduce the remaining sections """ self.land_manager.handle_lost_area(coords_list) self.village_builder.handle_lost_area(coords_list) self.production_builder.handle_lost_area(coords_list) self.production_builder.handle_new_area() # some of the village area may have been repurposed as production area self.village_builder.display() self.production_builder.display() def handle_disaster(self, message): if issubclass(message.disaster_class, FireDisaster): position = message.building.position fire_station_radius = Entities.buildings[BUILDINGS.FIRE_STATION].radius handled = False for fire_station in self.settlement.buildings_by_id[BUILDINGS.FIRE_STATION]: if fire_station.position.distance(position) > fire_station_radius: continue # TODO: check whether the building and the fire station are connected by road self.log.info('%s ignoring %s at %s because %s should be able to handle it', self, message.disaster_class.__name__, message.building, fire_station) handled = True break if not handled: self.log.info('%s removing %s because of %s', self, message.building, message.disaster_class.__name__) Tear(message.building).execute(self.session) else: self.log.info('%s ignoring unknown disaster of type %s', self, message.disaster_class.__name__) def __str__(self): return '%s.SM(%s/%s)' % (self.owner, self.settlement.get_component(NamedComponent).name if hasattr(self, 'settlement') else 'unknown', self.worldid if hasattr(self, 'worldid') else 'none')