def build(self, settlement_manager, resource_id): village_builder = settlement_manager.village_builder building_purpose = self.get_purpose(resource_id) building_id = BUILDING_PURPOSE.get_building(building_purpose) building_class = Entities.buildings[building_id] for coords, (purpose, (section, _)) in village_builder.plan.iteritems(): if section > village_builder.current_section or purpose != building_purpose: continue object = village_builder.land_manager.island.ground_map[coords].object if object is not None and object.id == self.id: continue if building_purpose != BUILDING_PURPOSE.MAIN_SQUARE: if not self._need_producer(settlement_manager, coords, resource_id): continue if not village_builder.have_resources(building_id): return (BUILD_RESULT.NEED_RESOURCES, None) if coords not in village_builder.settlement.buildability_cache.cache[building_class.size]: position = Rect.init_from_topleft_and_size_tuples(coords, building_class.size) return (BUILD_RESULT.OUT_OF_SETTLEMENT, position) building = BasicBuilder(building_id, coords, 0).execute(settlement_manager.land_manager) assert building if self.get_purpose(resource_id) == BUILDING_PURPOSE.MAIN_SQUARE and not village_builder.roads_built: village_builder.build_roads() return (BUILD_RESULT.OK, building) return (BUILD_RESULT.SKIP, None)
def _create_village_producer_assignments(self): """ Create an assignment of residence spots to village producer spots. This is useful for deciding which of the village producers would be most useful. """ self.producer_assignment = {} # {BUILDING_PURPOSE constant: {village producer coordinates: [residence coordinates, ...]}} purposes = [BUILDING_PURPOSE.PAVILION, BUILDING_PURPOSE.VILLAGE_SCHOOL, BUILDING_PURPOSE.TAVERN] residence_positions = self._get_sorted_residence_positions() residence_range = Entities.buildings[BUILDINGS.RESIDENTIAL_CLASS].radius for purpose in purposes: producer_positions = sorted(self._get_position(coords, BUILDING_PURPOSE.get_building(purpose)) for coords, (pos_purpose, _) in self.plan.iteritems() if pos_purpose == purpose) self.producer_assignment[purpose] = {} for producer_position in producer_positions: self.producer_assignment[purpose][producer_position.origin.to_tuple()] = [] options = [] for producer_position in producer_positions: for position in residence_positions: distance = producer_position.distance(position) if distance <= residence_range: options.append((distance, producer_position.origin.to_tuple(), position.origin.to_tuple())) options.sort(reverse = True) assigned_residence_coords = set() for _, producer_coords, residence_coords in options: if residence_coords in assigned_residence_coords: continue if len(self.producer_assignment[purpose][producer_coords]) >= self.personality.max_coverage_building_capacity: continue assigned_residence_coords.add(residence_coords) self.producer_assignment[purpose][producer_coords].append(residence_coords)
def _create_special_village_building_assignments(self): """ Create an assignment of residence spots to special village building spots. This is useful for deciding which of the special village buildings would be most useful. """ distance_rect_rect = distances.distance_rect_rect self.special_building_assignments = { } # {BUILDING_PURPOSE constant: {village producer coordinates: [residence coordinates, ...]}} residence_positions = self._get_sorted_building_positions( BUILDING_PURPOSE.RESIDENCE) building_types = [] for purpose in [ BUILDING_PURPOSE.PAVILION, BUILDING_PURPOSE.VILLAGE_SCHOOL, BUILDING_PURPOSE.TAVERN ]: building_types.append( (purpose, Entities.buildings[BUILDINGS.RESIDENTIAL].radius, self.personality.max_coverage_building_capacity)) building_types.append( (BUILDING_PURPOSE.FIRE_STATION, Entities.buildings[BUILDINGS.FIRE_STATION].radius, self.personality.max_fire_station_capacity)) building_types.append((BUILDING_PURPOSE.DOCTOR, Entities.buildings[BUILDINGS.DOCTOR].radius, self.personality.max_doctor_capacity)) for purpose, range, max_capacity in building_types: producer_positions = sorted( self._get_position(coords, BUILDING_PURPOSE.get_building(purpose)) for coords, (pos_purpose, _) in self.plan.iteritems() if pos_purpose == purpose) self.special_building_assignments[purpose] = {} for producer_position in producer_positions: self.special_building_assignments[purpose][ producer_position.origin.to_tuple()] = [] options = [] for producer_position in producer_positions: for position in residence_positions: distance = distance_rect_rect(producer_position, position) if distance <= range: options.append( (distance, producer_position.origin.to_tuple(), position.origin.to_tuple())) options.sort(reverse=True) assigned_residence_coords = set() for _, producer_coords, residence_coords in options: if residence_coords in assigned_residence_coords: continue if len(self.special_building_assignments[purpose] [producer_coords]) >= max_capacity: continue assigned_residence_coords.add(residence_coords) self.special_building_assignments[purpose][ producer_coords].append(residence_coords)
def build(self, settlement_manager, resource_id): village_builder = settlement_manager.village_builder building_purpose = self.get_purpose(resource_id) for coords, (purpose, (section, _)) in village_builder.plan.iteritems(): if section > village_builder.current_section or purpose != building_purpose: continue object = village_builder.land_manager.island.ground_map[coords].object if object is not None and object.id == self.id: continue if building_purpose != BUILDING_PURPOSE.MAIN_SQUARE: if not self._need_producer(settlement_manager, coords, resource_id): continue builder = village_builder.make_builder(BUILDING_PURPOSE.get_building(purpose), coords[0], coords[1], False) if not builder.have_resources(): return (BUILD_RESULT.NEED_RESOURCES, None) if not self.in_settlement(settlement_manager, builder.position): return (BUILD_RESULT.OUT_OF_SETTLEMENT, builder.position) building = builder.execute() if not building: return (BUILD_RESULT.UNKNOWN_ERROR, None) if self.get_purpose(resource_id) == BUILDING_PURPOSE.MAIN_SQUARE and not village_builder.roads_built: village_builder.build_roads() return (BUILD_RESULT.OK, building) return (BUILD_RESULT.SKIP, None)
def _need_producer(self, settlement_manager, coords, resource_id): if not settlement_manager.settlement.count_buildings(BUILDING_PURPOSE.get_building(self.get_purpose(resource_id))): return True # if none exist and we need the resource then build it assigned_residences = settlement_manager.village_builder.special_building_assignments[self.get_purpose(resource_id)][coords] total = len(assigned_residences) not_serviced = 0 for residence_coords in assigned_residences: if settlement_manager.village_builder.plan[residence_coords][0] != BUILDING_PURPOSE.RESIDENCE: continue not_serviced += 1 if not_serviced > 0 and not_serviced >= total * settlement_manager.owner.personality_manager.get('AbstractVillageBuilding').fraction_of_assigned_residences_built: return True return False
def _create_special_village_building_assignments(self): """ Create an assignment of residence spots to special village building spots. This is useful for deciding which of the special village buildings would be most useful. """ distance_rect_rect = distances.distance_rect_rect self.special_building_assignments = {} # {BUILDING_PURPOSE constant: {village producer coordinates: [residence coordinates, ...]}} residence_positions = self._get_sorted_building_positions(BUILDING_PURPOSE.RESIDENCE) building_types = [] for purpose in [BUILDING_PURPOSE.PAVILION, BUILDING_PURPOSE.VILLAGE_SCHOOL, BUILDING_PURPOSE.TAVERN]: building_types.append((purpose, Entities.buildings[BUILDINGS.RESIDENTIAL].radius, self.personality.max_coverage_building_capacity)) building_types.append((BUILDING_PURPOSE.FIRE_STATION, Entities.buildings[BUILDINGS.FIRE_STATION].radius, self.personality.max_fire_station_capacity)) building_types.append((BUILDING_PURPOSE.DOCTOR, Entities.buildings[BUILDINGS.DOCTOR].radius, self.personality.max_doctor_capacity)) for purpose, range, max_capacity in building_types: producer_positions = sorted(self._get_position(coords, BUILDING_PURPOSE.get_building(purpose)) for coords, (pos_purpose, _) in self.plan.iteritems() if pos_purpose == purpose) self.special_building_assignments[purpose] = {} for producer_position in producer_positions: self.special_building_assignments[purpose][producer_position.origin.to_tuple()] = [] options = [] for producer_position in producer_positions: for position in residence_positions: distance = distance_rect_rect(producer_position, position) if distance <= range: options.append((distance, producer_position.origin.to_tuple(), position.origin.to_tuple())) options.sort(reverse = True) assigned_residence_coords = set() for _, producer_coords, residence_coords in options: if residence_coords in assigned_residence_coords: continue if len(self.special_building_assignments[purpose][producer_coords]) >= max_capacity: continue assigned_residence_coords.add(residence_coords) self.special_building_assignments[purpose][producer_coords].append(residence_coords)
def build(self, settlement_manager, resource_id): village_builder = settlement_manager.village_builder building_purpose = self.get_purpose(resource_id) building_id = BUILDING_PURPOSE.get_building(building_purpose) building_class = Entities.buildings[building_id] for coords, (purpose, (section, _)) in village_builder.plan.iteritems(): if section > village_builder.current_section or purpose != building_purpose: continue object = village_builder.land_manager.island.ground_map[ coords].object if object is not None and object.id == self.id: continue if building_purpose != BUILDING_PURPOSE.MAIN_SQUARE: if not self._need_producer(settlement_manager, coords, resource_id): continue if not village_builder.have_resources(building_id): return (BUILD_RESULT.NEED_RESOURCES, None) if coords not in village_builder.settlement.buildability_cache.cache[ building_class.size]: position = Rect.init_from_topleft_and_size_tuples( coords, building_class.size) return (BUILD_RESULT.OUT_OF_SETTLEMENT, position) building = BasicBuilder(building_id, coords, 0).execute(settlement_manager.land_manager) assert building if self.get_purpose( resource_id ) == BUILDING_PURPOSE.MAIN_SQUARE and not village_builder.roads_built: village_builder.build_roads() return (BUILD_RESULT.OK, building) return (BUILD_RESULT.SKIP, None)
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 and road areas * remove village sections with impossible main squares * remove all planned buildings that are now impossible * TODO: if the village area takes too much of the total area then remove / reduce the remaining sections """ # remove village sections with impossible main squares removed_sections = set() for coords, (purpose, (section, _)) in self.plan.iteritems(): if purpose != BUILDING_PURPOSE.MAIN_SQUARE: continue possible = True for main_square_coords in self._get_position( coords, BUILDINGS.MAIN_SQUARE).tuple_iter(): if main_square_coords not in self.land_manager.village: possible = False break if not possible: # impossible to build the main square because a part of the area is owned by another player: remove the whole section removed_sections.add(section) removed_coords_list = [] for coords, (purpose, (section, _)) in self.plan.iteritems(): if purpose == BUILDING_PURPOSE.RESERVED or purpose == BUILDING_PURPOSE.NONE: continue position = self._get_position( coords, BUILDING_PURPOSE.get_building(purpose)) building = self.settlement.ground_map[ coords].object if coords in self.settlement.ground_map else None if section in removed_sections: if purpose == BUILDING_PURPOSE.ROAD: if building is None or building.id != BUILDINGS.TRAIL: removed_coords_list.append(coords) continue # leave existing roads behind elif building is not None and not building.buildable_upon: # TODO: remove the actual building pass for building_coords in position.tuple_iter(): removed_coords_list.append(building_coords) else: # remove the planned village buildings that are no longer possible if purpose == BUILDING_PURPOSE.ROAD: if coords not in self.land_manager.village: removed_coords_list.append(coords) continue possible = True for building_coords in position.tuple_iter(): if building_coords not in self.land_manager.village: possible = False if possible: continue for building_coords in position.tuple_iter(): removed_coords_list.append(building_coords) for coords in removed_coords_list: del self.plan[coords] self._recreate_tent_queue() # TODO: renumber the sections # TODO: create a new plan with village producers self._return_unused_space() self._create_special_village_building_assignments() super(VillageBuilder, self).handle_lost_area(coords_list)
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 and road areas * remove village sections with impossible main squares * remove all planned buildings that are now impossible * TODO: if the village area takes too much of the total area then remove / reduce the remaining sections """ # remove village sections with impossible main squares removed_sections = set() for coords, (purpose, (section, _)) in self.plan.iteritems(): if purpose != BUILDING_PURPOSE.MAIN_SQUARE: continue possible = True for main_square_coords in self._get_position(coords, BUILDINGS.MAIN_SQUARE_CLASS).tuple_iter(): if main_square_coords not in self.land_manager.village: possible = False break if not possible: # impossible to build the main square because a part of the area is owned by another player: remove the whole section removed_sections.add(section) removed_coords_list = [] for coords, (purpose, (section, _)) in self.plan.iteritems(): if purpose == BUILDING_PURPOSE.RESERVED or purpose == BUILDING_PURPOSE.NONE: continue position = self._get_position(coords, BUILDING_PURPOSE.get_building(purpose)) building = self.settlement.ground_map[coords].object if coords in self.settlement.ground_map else None if section in removed_sections: if purpose == BUILDING_PURPOSE.ROAD: if building is None or building.id != BUILDINGS.TRAIL_CLASS: removed_coords_list.append(coords) continue # leave existing roads behind elif building is not None and not building.buildable_upon: # TODO: remove the actual building pass for building_coords in position.tuple_iter(): removed_coords_list.append(building_coords) else: # remove the planned village buildings that are no longer possible if purpose == BUILDING_PURPOSE.ROAD: if coords not in self.land_manager.village: removed_coords_list.append(coords) continue possible = True for building_coords in position.tuple_iter(): if building_coords not in self.land_manager.village: possible = False if possible: continue for building_coords in position.tuple_iter(): removed_coords_list.append(building_coords) for coords in removed_coords_list: del self.plan[coords] self._recreate_tent_queue() # TODO: renumber the sections # TODO: create a new plan with village producers self._return_unused_space() self._create_village_producer_assignments() super(VillageBuilder, self).handle_lost_area(coords_list)