def handle_lost_area(self, coords_list): """Handle losing the potential land in the given coordinates list.""" # remove planned fields that are now impossible lost_coords_list = [] for coords in coords_list: if coords in self.plan: lost_coords_list.append(coords) self.register_change_list(lost_coords_list, BUILDING_PURPOSE.NONE, None) field_size = Entities.buildings[BUILDINGS.POTATO_FIELD].size removed_list = [] for coords, (purpose, _) in self.plan.iteritems(): if purpose in [BUILDING_PURPOSE.POTATO_FIELD, BUILDING_PURPOSE.PASTURE, BUILDING_PURPOSE.SUGARCANE_FIELD, BUILDING_PURPOSE.TOBACCO_FIELD]: rect = Rect.init_from_topleft_and_size_tuples(coords, field_size) for field_coords in rect.tuple_iter(): if field_coords not in self.land_manager.production: removed_list.append(coords) break for coords in removed_list: rect = Rect.init_from_topleft_and_size_tuples(coords, field_size) self.register_change_list(list(rect.tuple_iter()), BUILDING_PURPOSE.NONE, None) self._refresh_unused_fields() super(ProductionBuilder, self).handle_lost_area(coords_list) self.road_connectivity_cache.modify_area(lost_coords_list)
def handle_lost_area(self, coords_list): """Handle losing the potential land in the given coordinates list.""" # remove planned fields that are now impossible lost_coords_list = [] for coords in coords_list: if coords in self.plan: lost_coords_list.append(coords) self.register_change_list(lost_coords_list, BUILDING_PURPOSE.NONE, None) field_size = Entities.buildings[BUILDINGS.POTATO_FIELD].size removed_list = [] for coords, (purpose, _) in self.plan.items(): if purpose in [ BUILDING_PURPOSE.POTATO_FIELD, BUILDING_PURPOSE.PASTURE, BUILDING_PURPOSE.SUGARCANE_FIELD, BUILDING_PURPOSE.TOBACCO_FIELD, BUILDING_PURPOSE.HERBARY ]: rect = Rect.init_from_topleft_and_size_tuples( coords, field_size) for field_coords in rect.tuple_iter(): if field_coords not in self.land_manager.production: removed_list.append(coords) break for coords in removed_list: rect = Rect.init_from_topleft_and_size_tuples(coords, field_size) self.register_change_list(list(rect.tuple_iter()), BUILDING_PURPOSE.NONE, None) self._refresh_unused_fields() super(ProductionBuilder, self).handle_lost_area(coords_list) self.road_connectivity_cache.modify_area(lost_coords_list)
def generate_random_minimap(size, parameters): """Called as subprocess, calculates minimap data and passes it via string via stdout""" # called as standalone basically, so init everything we need from horizons.entities import Entities from horizons.main import _create_main_db if not VERSION.IS_DEV_VERSION: # Hack enable atlases. # Usually the minimap generator uses single tile files, but in release # mode these are not available. Therefor we have to hackenable atlases # for the minimap generation in this case. This forces the game to use # the correct imageloader # In normal dev mode + enabled atlases we ignore this and just continue # to use single tile files instead of atlases for the minimap generation. # These are always available in dev checkouts PATHS.DB_FILES = PATHS.DB_FILES + (PATHS.ATLAS_DB_PATH, ) db = _create_main_db() horizons.globals.db = db horizons.globals.fife.init_animation_loader(not VERSION.IS_DEV_VERSION) Entities.load_grounds(db, load_now=False) # create all references map_file = generate_random_map(*parameters) world = load_raw_world(map_file) location = Rect.init_from_topleft_and_size_tuples((0, 0), size) # communicate via stdout. Sometimes the process seems to print more information, therefore # we add markers around our data so it's easier for the caller to get to the data. args = (location, world, Minimap.COLORS['island'], Minimap.COLORS['water']) data = [(x, y, r, g, b) for (x, y), (r, g, b) in iter_minimap_points_colors(*args)] print('DATA', json.dumps(data), 'ENDDATA')
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 generate_random_minimap(size, parameters): """Called as subprocess, calculates minimap data and passes it via string via stdout""" # called as standalone basically, so init everything we need from horizons.entities import Entities from horizons.main import _create_main_db if not VERSION.IS_DEV_VERSION: # Hack enable atlases. # Usually the minimap generator uses single tile files, but in release # mode these are not available. Therefor we have to hackenable atlases # for the minimap generation in this case. This forces the game to use # the correct imageloader # In normal dev mode + enabled atlases we ignore this and just continue # to use single tile files instead of atlases for the minimap generation. # These are always available in dev checkouts PATHS.DB_FILES = PATHS.DB_FILES + (PATHS.ATLAS_DB_PATH, ) db = _create_main_db() horizons.globals.db = db horizons.globals.fife.init_animation_loader(not VERSION.IS_DEV_VERSION) Entities.load_grounds(db, load_now=False) # create all references map_file = generate_random_map(*parameters) world = load_raw_world(map_file) location = Rect.init_from_topleft_and_size_tuples((0, 0), size) # communicate via stdout. Sometimes the process seems to print more information, therefore # we add markers around our data so it's easier for the caller to get to the data. args = (location, world, Minimap.COLORS['island'], Minimap.COLORS['water']) data = [(x, y, r, g, b) for (x, y), (r, g, b) in iter_minimap_points(*args)] print('DATA', json.dumps(data), 'ENDDATA')
def __init__(self, building_id, coords, orientation): self.building_id = building_id self.coords = coords self.orientation = orientation size = Entities.buildings[building_id].size if orientation % 2 != 0: size = (size[1], size[0]) self.position = Rect.init_from_topleft_and_size_tuples(coords, size)
def __init__(self, building_id, coords, orientation): self.building_id = building_id self.coords = coords self.orientation = orientation size = Entities.buildings[building_id].size if orientation % 2 != 0: size = (size[1], size[0]) self.position = Rect.init_from_topleft_and_size_tuples(coords, size)
def __init_outline(cls): """Save a template outline that surrounds a lumberjack.""" position = Rect.init_from_topleft_and_size_tuples((0, 0), Entities.buildings[BUILDINGS.LUMBERJACK].size) moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] coords_list = set(position.get_radius_coordinates(Entities.buildings[BUILDINGS.LUMBERJACK].radius, True)) result = set() for x, y in coords_list: for dx, dy in moves: coords = (x + dx, y + dy) if coords not in coords_list: result.add(coords) cls.__template_outline = list(result)
def __init_outline(cls): """Save a template outline that surrounds a lumberjack.""" position = Rect.init_from_topleft_and_size_tuples((0, 0), Entities.buildings[BUILDINGS.LUMBERJACK].size) moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] coords_list = set(position.get_radius_coordinates(Entities.buildings[BUILDINGS.LUMBERJACK].radius, True)) result = set() for x, y in coords_list: for dx, dy in moves: coords = (x + dx, y + dy) if coords not in coords_list: result.add(coords) cls.__template_outline = sorted(list(result)) cls.__radius_offsets = sorted(position.get_radius_coordinates(Entities.buildings[BUILDINGS.LUMBERJACK].radius))
def _get_possible_building_positions(self, section_coords_set, size): """Return {(x, y): Rect, ...} that contains every size x size potential building location where only the provided coordinates are legal.""" result = {} for (x, y) in sorted(section_coords_set): ok = True for dx in xrange(size[0]): for dy in xrange(size[1]): coords = (x + dx, y + dy) if coords not in section_coords_set or not self.land_manager.coords_usable(coords): ok = False break if not ok: break if ok: result[(x, y)] = Rect.init_from_topleft_and_size_tuples((x, y), size) return result
def _get_possible_building_positions(self, section_coords_set, size): """Return {(x, y): Rect, ...} that contains every size x size potential building location where only the provided coordinates are legal.""" result = {} for (x, y) in sorted(section_coords_set): ok = True for dx in range(size[0]): for dy in range(size[1]): coords = (x + dx, y + dy) if coords not in section_coords_set or not self.land_manager.coords_usable(coords): ok = False break if not ok: break if ok: result[(x, y)] = Rect.init_from_topleft_and_size_tuples((x, y), size) return result
def _handle_farm_removal(self, building): """Handle farm removal by removing planned fields and tearing existing ones that can't be serviced by another farm.""" unused_fields = set() farms = self.settlement.buildings_by_id.get(BUILDINGS.FARM, []) for coords in building.position.get_radius_coordinates( building.radius): if coords not in self.plan: continue object = self.island.ground_map[coords].object if object is None or object.id not in self.field_building_classes: continue used_by_another_farm = False for farm in farms: if farm.worldid != building.worldid and object.position.distance( farm.position) <= farm.radius: used_by_another_farm = True break if not used_by_another_farm: unused_fields.add(object) # tear the finished but no longer used fields down for unused_field in unused_fields: self.register_change_list(list(unused_field.position.tuple_iter()), BUILDING_PURPOSE.NONE, None) Tear(unused_field).execute(self.session) # remove the planned but never built fields from the plan self._refresh_unused_fields() for unused_fields_list in self.unused_fields.values(): for coords in unused_fields_list: position = Rect.init_from_topleft_and_size_tuples( coords, Entities.buildings[BUILDINGS.POTATO_FIELD].size) if building.position.distance(position) > building.radius: continue # it never belonged to the removed building used_by_another_farm = False for farm in farms: if farm.worldid != building.worldid and position.distance( farm.position) <= farm.radius: used_by_another_farm = True break if not used_by_another_farm: self.register_change_list(list(position.tuple_iter()), BUILDING_PURPOSE.NONE, None) self._refresh_unused_fields()
def _upgrade_to_rev69(self, db): settlement_map = {} for data in db("SELECT rowid, data FROM settlement_tiles"): settlement_id = int(data[0]) coords_list = [ tuple(raw_coords) for raw_coords in json.loads(data[1]) ] # json saves tuples as list for coords in coords_list: settlement_map[coords] = settlement_id db("DELETE FROM settlement_tiles") deposits = [] for (worldid, building_id, x, y, location_id) in db( "SELECT rowid, type, x, y, location FROM building WHERE type = ? OR type = ?", BUILDINGS.CLAY_DEPOSIT, BUILDINGS.MOUNTAIN): worldid = int(worldid) building_id = int(building_id) origin_coords = (int(x), int(y)) location_id = int(location_id) settlement_ids = set() position = Rect.init_from_topleft_and_size_tuples( origin_coords, Entities.buildings[building_id].size) for coords in position.tuple_iter(): if coords in settlement_map: settlement_ids.add(settlement_map[coords]) if not settlement_ids: continue # no settlement covers any of the deposit else: # assign all of it to the earlier settlement settlement_id = sorted(settlement_ids)[0] for coords in position.tuple_iter(): settlement_map[coords] = settlement_id if location_id != settlement_id: db("UPDATE building SET location = ? WHERE rowid = ?", settlement_id, worldid) # save the new settlement tiles data ground_map = defaultdict(lambda: []) for (coords, settlement_id) in settlement_map.iteritems(): ground_map[settlement_id].append(coords) for (settlement_id, coords_list) in ground_map.iteritems(): data = json.dumps(coords_list) db("INSERT INTO settlement_tiles(rowid, data) VALUES(?, ?)", settlement_id, data)
def _handle_farm_removal(self, building): """Handle farm removal by removing planned fields and tearing existing ones that can't be serviced by another farm.""" unused_fields = set() farms = self.settlement.buildings_by_id.get(BUILDINGS.FARM, []) for coords in building.position.get_radius_coordinates(building.radius): if not coords in self.plan: continue object = self.island.ground_map[coords].object if object is None or object.id not in self.field_building_classes: continue used_by_another_farm = False for farm in farms: if farm.worldid != building.worldid and object.position.distance(farm.position) <= farm.radius: used_by_another_farm = True break if not used_by_another_farm: unused_fields.add(object) # tear the finished but no longer used fields down for unused_field in unused_fields: for x, y in unused_field.position.tuple_iter(): self.register_change(x, y, BUILDING_PURPOSE.NONE, None) Tear(unused_field).execute(self.session) # remove the planned but never built fields from the plan self._refresh_unused_fields() for unused_fields_list in self.unused_fields.itervalues(): for coords in unused_fields_list: position = Rect.init_from_topleft_and_size_tuples(coords, Entities.buildings[BUILDINGS.POTATO_FIELD].size) if building.position.distance(position) > building.radius: continue # it never belonged to the removed building used_by_another_farm = False for farm in farms: if farm.worldid != building.worldid and position.distance(farm.position) <= farm.radius: used_by_another_farm = True break if not used_by_another_farm: for x, y in position.tuple_iter(): self.register_change(x, y, BUILDING_PURPOSE.NONE, None) self._refresh_unused_fields()
def _upgrade_to_rev69(self, db): settlement_map = {} for data in db("SELECT rowid, data FROM settlement_tiles"): settlement_id = int(data[0]) coords_list = [tuple(raw_coords) for raw_coords in json.loads(data[1])] # json saves tuples as list for coords in coords_list: settlement_map[coords] = settlement_id db("DELETE FROM settlement_tiles") for (worldid, building_id, x, y, location_id) in db( "SELECT rowid, type, x, y, location FROM building WHERE type = ? OR type = ?", BUILDINGS.CLAY_DEPOSIT, BUILDINGS.MOUNTAIN, ): worldid = int(worldid) building_id = int(building_id) origin_coords = (int(x), int(y)) location_id = int(location_id) settlement_ids = set() position = Rect.init_from_topleft_and_size_tuples(origin_coords, Entities.buildings[building_id].size) for coords in position.tuple_iter(): if coords in settlement_map: settlement_ids.add(settlement_map[coords]) if not settlement_ids: continue # no settlement covers any of the deposit else: # assign all of it to the earlier settlement settlement_id = sorted(settlement_ids)[0] for coords in position.tuple_iter(): settlement_map[coords] = settlement_id if location_id != settlement_id: db("UPDATE building SET location = ? WHERE rowid = ?", settlement_id, worldid) # save the new settlement tiles data ground_map = defaultdict(list) for (coords, settlement_id) in settlement_map.iteritems(): ground_map[settlement_id].append(coords) for (settlement_id, coords_list) in ground_map.iteritems(): data = json.dumps(coords_list) db("INSERT INTO settlement_tiles(rowid, data) VALUES(?, ?)", settlement_id, data)
def generate_minimap(cls, size, parameters): """Called as subprocess, calculates minimap data and passes it via string via stdout""" # called as standalone basically, so init everything we need from horizons.main import _create_main_db from horizons.entities import Entities from horizons.ext.dummy import Dummy db = _create_main_db() Entities.load_grounds(db, load_now=False) # create all references map_file = SingleplayerMenu._generate_random_map(parameters) world = cls._load_raw_world(map_file) location = Rect.init_from_topleft_and_size_tuples((0, 0), size) minimap = Minimap(location, session=None, view=None, world=world, targetrenderer=Dummy(), imagemanager=Dummy(), cam_border=False, use_rotation=False, preview=True) # communicate via stdout print minimap.dump_data()
def generate_random_minimap(size, parameters): """Called as subprocess, calculates minimap data and passes it via string via stdout""" # called as standalone basically, so init everything we need from horizons.entities import Entities from horizons.ext.dummy import Dummy from horizons.main import _create_main_db if not VERSION.IS_DEV_VERSION: # Hack enable atlases. # Usually the minimap generator uses single tile files, but in release # mode these are not available. Therefor we have to hackenable atlases # for the minimap generation in this case. This forces the game to use # the correct imageloader # In normal dev mode + enabled atlases we ignore this and just continue # to use single tile files instead of atlases for the minimap generation. # These are always available in dev checkouts PATHS.DB_FILES = PATHS.DB_FILES + (PATHS.ATLAS_DB_PATH, ) db = _create_main_db() horizons.globals.db = db horizons.globals.fife.init_animation_loader(not VERSION.IS_DEV_VERSION) Entities.load_grounds(db, load_now=False) # create all references map_file = generate_random_map(*parameters) world = load_raw_world(map_file) location = Rect.init_from_topleft_and_size_tuples((0, 0), size) minimap = Minimap( location, session=None, view=None, world=world, targetrenderer=Dummy(), imagemanager=Dummy(), cam_border=False, use_rotation=False, preview=True) # communicate via stdout print minimap.dump_data()
def generate_random_minimap(size, parameters): """Called as subprocess, calculates minimap data and passes it via string via stdout""" # called as standalone basically, so init everything we need from horizons.entities import Entities from horizons.ext.dummy import Dummy from horizons.main import _create_main_db if not VERSION.IS_DEV_VERSION: # Hack enable atlases. # Usually the minimap generator uses single tile files, but in release # mode these are not available. Therefor we have to hackenable atlases # for the minimap generation in this case. This forces the game to use # the correct imageloader # In normal dev mode + enabled atlases we ignore this and just continue # to use single tile files instead of atlases for the minimap generation. # These are always available in dev checkouts PATHS.DB_FILES = PATHS.DB_FILES + (PATHS.ATLAS_DB_PATH, ) db = _create_main_db() horizons.globals.db = db horizons.globals.fife.init_animation_loader(not VERSION.IS_DEV_VERSION) Entities.load_grounds(db, load_now=False) # create all references map_file = generate_random_map(*parameters) world = load_raw_world(map_file) location = Rect.init_from_topleft_and_size_tuples((0, 0), size) minimap = Minimap( location, session=None, view=None, world=world, targetrenderer=Dummy(), imagemanager=Dummy(), cam_border=False, use_rotation=False, preview=True) # communicate via stdout print minimap.dump_data()
def generate_minimap(cls, size, parameters): """Called as subprocess, calculates minimap data and passes it via string via stdout""" # called as standalone basically, so init everything we need from horizons.main import _create_main_db from horizons.entities import Entities from horizons.ext.dummy import Dummy db = _create_main_db() Entities.load_grounds(db, load_now=False) # create all references map_file = SingleplayerMenu._generate_random_map( parameters ) world = cls._load_raw_world(map_file) location = Rect.init_from_topleft_and_size_tuples( (0, 0), size) minimap = Minimap(location, session=None, view=None, world=world, targetrenderer=Dummy(), imagemanager=Dummy(), cam_border=False, use_rotation=False, preview=True) # communicate via stdout print minimap.dump_data()
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 _enlarge_collector_area(self): if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] # valid moves for collectors collector_area = self.production_builder.get_collector_area() # area_label contains free tiles in the production area and all road tiles area_label = dict.fromkeys( self.land_manager.roads) # {(x, y): area_number, ...} for coords, (purpose, _) in self.production_builder.plan.iteritems(): if purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: if coords in area_label and area_label[coords] is not None: continue queue = deque([coords]) while queue: x, y = queue[0] queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in area_label and area_label[coords2] is None: area_label[coords2] = areas queue.append(coords2) areas += 1 coords_set_by_area = defaultdict(lambda: set()) for coords, area_number in area_label.iteritems(): if coords in self.production_builder.plan and self.production_builder.plan[ coords][ 0] == BUILDING_PURPOSE.NONE and coords not in collector_area: coords_set_by_area[area_number].add(coords) options = [] for (x, y), area_number in area_label.iteritems(): builder = self.production_builder.make_builder( BUILDINGS.STORAGE, x, y, False) if not builder: continue coords_set = set( builder.position.get_radius_coordinates( Entities.buildings[BUILDINGS.STORAGE].radius)) useful_area = len( coords_set_by_area[area_number].intersection(coords_set)) if not useful_area: continue alignment = 1 for tile in self.production_builder.iter_neighbour_tiles( builder.position): coords = (tile.x, tile.y) if coords not in self.production_builder.plan or self.production_builder.plan[ coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = useful_area + alignment * self.personality.alignment_coefficient options.append((value, builder)) if options: return self.production_builder.build_best_option( options, BUILDING_PURPOSE.STORAGE) # enlarge the settlement area instead since just enlarging the collector area is impossible if self.village_builder.tent_queue: tent_size = Entities.buildings[BUILDINGS.RESIDENTIAL].size tent_radius = Entities.buildings[BUILDINGS.RESIDENTIAL].radius best_coords = None best_area = 0 for x, y in self.village_builder.tent_queue: new_area = 0 for coords in Rect.init_from_topleft_and_size( x, y, tent_size[0], tent_size[1]).get_radius_coordinates(tent_radius): if coords in area_label and coords not in self.land_manager.roads and coords not in collector_area: new_area += 1 if new_area > best_area: best_coords = (x, y) best_area = new_area if best_coords is not None: return self.production_builder.extend_settlement_with_tent( Rect.init_from_topleft_and_size_tuples( best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE
def _enlarge_collector_area(self): if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] # valid moves for collectors collector_area = self.production_builder.get_collector_area() coastline = self.land_manager.coastline # area_label contains free tiles in the production area and all road tiles area_label = dict.fromkeys( self.land_manager.roads) # {(x, y): area_number, ...} for coords, (purpose, _) in self.production_builder.plan.iteritems(): if coords not in coastline and purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: assert coords not in coastline if coords in area_label and area_label[coords] is not None: continue queue = deque([coords]) while queue: x, y = queue[0] queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in area_label and area_label[coords2] is None: area_label[coords2] = areas queue.append(coords2) areas += 1 coords_set_by_area = defaultdict(lambda: set()) for coords, area_number in area_label.iteritems(): if coords in self.production_builder.plan and self.production_builder.plan[ coords][ 0] == BUILDING_PURPOSE.NONE and coords not in collector_area: coords_set_by_area[area_number].add(coords) storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection( storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.production_builder.buildability_cache) storage_surrounding_offsets = Rect.get_surrounding_offsets( storage_class.size) options = [] num_offsets = int( len(self._radius_offsets) * self.personality.overlap_precision) radius_offsets = self.session.random.sample(self._radius_offsets, num_offsets) for coords in sorted(storage_spots): if coords not in area_label: continue x, y = coords area_number = area_label[coords] area_coords_set = coords_set_by_area[area_number] useful_area = 0 for dx, dy in radius_offsets: coords = (x + dx, y + dy) if coords in area_coords_set: useful_area += 1 if not useful_area: continue alignment = 1 builder = BasicBuilder.create(BUILDINGS.STORAGE, (x, y), 0) for (dx, dy) in storage_surrounding_offsets: coords = (x + dx, y + dy) if coords in coastline or coords not in self.production_builder.plan or self.production_builder.plan[ coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = useful_area + alignment * self.personality.alignment_coefficient options.append((value, builder)) if options: return self.production_builder.build_best_option( options, BUILDING_PURPOSE.STORAGE) # enlarge the settlement area instead since just enlarging the collector area is impossible if self.village_builder.tent_queue: tent_size = Entities.buildings[BUILDINGS.RESIDENTIAL].size tent_radius = Entities.buildings[BUILDINGS.RESIDENTIAL].radius best_coords = None best_area = 0 for x, y in self.village_builder.tent_queue: new_area = 0 for coords in Rect.init_from_topleft_and_size( x, y, tent_size[0], tent_size[1]).get_radius_coordinates(tent_radius): if coords in area_label and coords not in self.land_manager.roads and coords not in collector_area: new_area += 1 if new_area > best_area: best_coords = (x, y) best_area = new_area if best_coords is not None: return self.village_builder.extend_settlement_with_tent( Rect.init_from_topleft_and_size_tuples( best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE
def _enlarge_collector_area(self): if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] # valid moves for collectors collector_area = self.production_builder.get_collector_area() coastline = self.land_manager.coastline # area_label contains free tiles in the production area and all road tiles area_label = dict.fromkeys(self.land_manager.roads) # {(x, y): area_number, ...} for coords, (purpose, _) in self.production_builder.plan.iteritems(): if coords not in coastline and purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: assert coords not in coastline if coords in area_label and area_label[coords] is not None: continue queue = deque([coords]) while queue: x, y = queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in area_label and area_label[coords2] is None: area_label[coords2] = areas queue.append(coords2) areas += 1 coords_set_by_area = defaultdict(set) for coords, area_number in area_label.iteritems(): if coords in self.production_builder.plan and self.production_builder.plan[coords][0] == BUILDING_PURPOSE.NONE and coords not in collector_area: coords_set_by_area[area_number].add(coords) storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection(storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.production_builder.buildability_cache) storage_surrounding_offsets = Rect.get_surrounding_offsets(storage_class.size) options = [] num_offsets = int(len(self._radius_offsets) * self.personality.overlap_precision) radius_offsets = self.session.random.sample(self._radius_offsets, num_offsets) for coords in sorted(storage_spots): if coords not in area_label: continue x, y = coords area_number = area_label[coords] area_coords_set = coords_set_by_area[area_number] useful_area = 0 for dx, dy in radius_offsets: coords = (x + dx, y + dy) if coords in area_coords_set: useful_area += 1 if not useful_area: continue alignment = 1 builder = BasicBuilder.create(BUILDINGS.STORAGE, (x, y), 0) for (dx, dy) in storage_surrounding_offsets: coords = (x + dx, y + dy) if coords in coastline or coords not in self.production_builder.plan or self.production_builder.plan[coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = useful_area + alignment * self.personality.alignment_coefficient options.append((value, builder)) if options: return self.production_builder.build_best_option(options, BUILDING_PURPOSE.STORAGE) # enlarge the settlement area instead since just enlarging the collector area is impossible if self.village_builder.tent_queue: tent_size = Entities.buildings[BUILDINGS.RESIDENTIAL].size tent_radius = Entities.buildings[BUILDINGS.RESIDENTIAL].radius best_coords = None best_area = 0 for x, y in self.village_builder.tent_queue: new_area = 0 for coords in Rect.init_from_topleft_and_size(x, y, tent_size[0], tent_size[1]).get_radius_coordinates(tent_radius): if coords in area_label and coords not in self.land_manager.roads and coords not in collector_area: new_area += 1 if new_area > best_area: best_coords = (x, y) best_area = new_area if best_coords is not None: return self.village_builder.extend_settlement_with_tent(Rect.init_from_topleft_and_size_tuples(best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE
def _get_position(cls, coords, building_id): """Return the position Rect of a building of the given type at the given position.""" return Rect.init_from_topleft_and_size_tuples(coords, Entities.buildings[building_id].size)
def _enlarge_collector_area(self): if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] # valid moves for collectors collector_area = self.production_builder.get_collector_area() # area_label contains free tiles in the production area and all road tiles area_label = dict.fromkeys(self.land_manager.roads) # {(x, y): area_number, ...} for coords, (purpose, _) in self.production_builder.plan.iteritems(): if purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: if coords in area_label and area_label[coords] is not None: continue queue = deque([coords]) while queue: x, y = queue[0] queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in area_label and area_label[coords2] is None: area_label[coords2] = areas queue.append(coords2) areas += 1 coords_set_by_area = defaultdict(lambda: set()) for coords, area_number in area_label.iteritems(): if coords in self.production_builder.plan and self.production_builder.plan[coords][0] == BUILDING_PURPOSE.NONE and coords not in collector_area: coords_set_by_area[area_number].add(coords) options = [] for (x, y), area_number in area_label.iteritems(): builder = self.production_builder.make_builder(BUILDINGS.STORAGE, x, y, False) if not builder: continue coords_set = set(builder.position.get_radius_coordinates(Entities.buildings[BUILDINGS.STORAGE].radius)) useful_area = len(coords_set_by_area[area_number].intersection(coords_set)) if not useful_area: continue alignment = 1 for tile in self.production_builder.iter_neighbour_tiles(builder.position): coords = (tile.x, tile.y) if coords not in self.production_builder.plan or self.production_builder.plan[coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = useful_area + alignment * self.personality.alignment_coefficient options.append((value, builder)) if options: return self.production_builder.build_best_option(options, BUILDING_PURPOSE.STORAGE) # enlarge the settlement area instead since just enlarging the collector area is impossible if self.village_builder.tent_queue: tent_size = Entities.buildings[BUILDINGS.RESIDENTIAL].size tent_radius = Entities.buildings[BUILDINGS.RESIDENTIAL].radius best_coords = None best_area = 0 for x, y in self.village_builder.tent_queue: new_area = 0 for coords in Rect.init_from_topleft_and_size(x, y, tent_size[0], tent_size[1]).get_radius_coordinates(tent_radius): if coords in area_label and coords not in self.land_manager.roads and coords not in collector_area: new_area += 1 if new_area > best_area: best_coords = (x, y) best_area = new_area if best_coords is not None: return self.production_builder.extend_settlement_with_tent(Rect.init_from_topleft_and_size_tuples(best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE
def _get_position(cls, coords, building_id): """Return the position Rect of a building of the given type at the given position.""" return Rect.init_from_topleft_and_size_tuples( coords, Entities.buildings[building_id].size)