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 check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): """Check if a building is buildable here. All tiles, that the building occupies are checked. @param point: Point instance, coords @param rotation: prefered rotation of building @param check_settlement: whether to check for a settlement (for settlementless buildings) @param ship: ship instance if building from ship @return instance of _BuildPosition""" # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation == 45 or rotation == 225: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True problem = None tearset = [] try: island = cls._check_island(session, position) # TODO: if the rotation changes here for non-quadratic buildings, wrong results will be returned rotation = cls._check_rotation(session, position, rotation) tearset = cls._check_buildings(session, position, island=island) cls._check_units(session, position) if check_settlement: cls._check_settlement(session, position, ship=ship, issuer=issuer) except _NotBuildableError as e: buildable = False problem = (e.errortype, _(BuildableErrorTypes.text[e.errortype])) return _BuildPosition(position, rotation, tearset, buildable, problem=problem)
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 check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): """Check if a building is buildable here. All tiles, that the building occupies are checked. @param point: Point instance, coords @param rotation: preferred rotation of building @param check_settlement: whether to check for a settlement (for settlementless buildings) @param ship: ship instance if building from ship @return instance of _BuildPosition""" # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation in [45, 225]: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True problem = None tearset = [] try: island = cls._check_island(session, position) # TODO: if the rotation changes here for non-quadratic buildings, wrong results will be returned rotation = cls._check_rotation(session, position, rotation) tearset = cls._check_buildings(session, position, island=island) cls._check_units(session, position) if check_settlement: cls._check_settlement(session, position, ship=ship, issuer=issuer) except _NotBuildableError as e: buildable = False problem = (e.errortype, BuildableErrorTypes.text[e.errortype]) return _BuildPosition(position, rotation, tearset, buildable, problem=problem)
def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None): """ @param position: a Rect or a Pychan Icon, where we will draw to @param world: World object or fake thereof @param view: View object for cam control. Can be None to disable this @param renderer: renderer to be used if position isn't an icon @param targetrenderer: fife target renderer for drawing on icons @param imagemanager: fife imagemanager for drawing on icons @param cam_border: boolean, whether to draw the cam border @param use_rotation: boolean, whether to use rotation (it must also be enabled in the settings) @param on_click: function taking 1 argument or None for scrolling @param preview: flag, whether to only show the map as preview @param tooltip: always show this tooltip when cursor hovers over minimap NOTE: Preview generation in a different process overwrites this method. """ if isinstance(position, Rect): self.location = position self.renderer = renderer else: # assume icon self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) self.icon = position self.use_overlay_icon(self.icon) # FIXME PY3 width / height of icon is sometimes zero. Why? if self.location.height == 0 or self.location.width == 0: self.location = Rect.init_from_topleft_and_size(0, 0, 128, 128) self.session = session self.world = world if self.world: self._update_world_to_minimap_ratio() self.view = view self.rotation = 0 self.fixed_tooltip = tooltip self.click_handler = on_click if on_click is not None else self.default_on_click self.cam_border = cam_border self.use_rotation = use_rotation self.preview = preview self.location_center = self.location.center self._id = str(next(self.__class__.__minimap_id_counter)) # internal identifier, used for allocating resources self._image_size_cache = {} # internal detail self.imagemanager = imagemanager self.minimap_image = _MinimapImage(self, targetrenderer) self._rotation_setting = horizons.globals.fife.get_uh_setting("MinimapRotation") if self.use_rotation: SettingChanged.subscribe(self._on_setting_changed)
def check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation in [45, 225]: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True tearset = [] return _BuildPosition(position, rotation, tearset, buildable)
def check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation == 45 or rotation == 225: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True tearset = [] return _BuildPosition(position, rotation, tearset, buildable)
def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None, mousearea=None): """ @param position: a Rect or a Pychan Icon, where we will draw to @param world: World object or fake thereof @param view: View object for cam control. Can be None to disable this @param renderer: renderer to be used if position isn't an icon @param targetrenderer: fife target renderer for drawing on icons @param imagemanager: fife imagemanager for drawing on icons @param cam_border: boolean, whether to draw the cam border @param use_rotation: boolean, whether to use rotation @param on_click: function taking 1 argument or None for scrolling @param preview: flag, whether to only show the map as preview @param tooltip: always show this tooltip when cursor hovers over minimap NOTE: Preview generation in a different process overwrites this method. """ if isinstance(position, Rect): self.location = position self.renderer = renderer else: # assume icon self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) self.icon = position if mousearea is None: mousearea = self.icon self.use_overlay_icon(mousearea) # FIXME PY3 width / height of icon is sometimes zero. Why? if self.location.height == 0 or self.location.width == 0: self.location = Rect.init_from_topleft_and_size(0, 0, 128, 128) self.session = session self.world = world self.view = view self.fixed_tooltip = tooltip self.click_handler = on_click if on_click is not None else self.default_on_click self.cam_border = cam_border self.use_rotation = use_rotation self.preview = preview self.location_center = self.location.center self._id = str(next(self.__class__.__minimap_id_counter)) # internal identifier, used for allocating resources self._image_size_cache = {} # internal detail self.imagemanager = imagemanager self.minimap_image = _MinimapImage(self, targetrenderer) self.transform = None
def get_loading_area(cls, building_id, rotation, pos): if building_id == BUILDINGS.MOUNTAIN or building_id == BUILDINGS.MINE: if rotation == 45: return Rect.init_from_topleft_and_size(pos.origin.x, pos.origin.y + 1, 1, 3) elif rotation == 135: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y + pos.height - 1, 3, 1) elif rotation == 225: return Rect.init_from_topleft_and_size(pos.origin.x + pos.width -1, pos.origin.y + 1, 1, 3) elif rotation == 315: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y, 3, 1) assert False else: return pos
def get_loading_area(cls, building_id, rotation, pos): if building_id in [BUILDINGS.MOUNTAIN, BUILDINGS.MINE]: if rotation == 45: return Rect.init_from_topleft_and_size(pos.origin.x, pos.origin.y + 1, 1, 3) elif rotation == 135: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y + pos.height - 1, 3, 1) elif rotation == 225: return Rect.init_from_topleft_and_size(pos.origin.x + pos.width - 1, pos.origin.y + 1, 1, 3) elif rotation == 315: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y, 3, 1) assert False else: return pos
def test_rect(): r1 = Rect(Point(0, 0), 1, 1) r2 = Rect(0, 0, 1, 1) r3 = Rect(Point(2, 2), 1, 1) assert r1 == r2 assert not r1.contains(Point(-1, -1)) assert r2.contains(Point(0, 0)) assert r2.contains(Point(1, 1)) assert r1.intersects(r2) assert not r1.intersects(r3)
def testRect(self): r1 = Rect(Point(0, 0), 1, 1) r2 = Rect(0, 0, 1, 1) r3 = Rect(Point(2, 2), 1, 1) self.assertEqual(r1, r2) self.assertTrue(r1 == r2) self.assertFalse(r1.contains(Point(-1, -1))) self.assertTrue(r2.contains(Point(0, 0))) self.assertTrue(r2.contains(Point(1, 1))) self.assertTrue(r1.intersects(r2)) self.assertFalse(r1.intersects(r3))
def _build_extra_road_connection(self, building, collector_building): collector_coords = set( coords for coords in self.production_builder.iter_possible_road_coords( collector_building.position, collector_building.position ) ) destination_coords = set( coords for coords in self.production_builder.iter_possible_road_coords(building.loading_area, building.position) ) pos = building.loading_area beacon = Rect.init_from_borders(pos.left - 1, pos.top - 1, pos.right + 1, pos.bottom + 1) path = RoadPlanner()( self.owner.personality_manager.get("RoadPlanner"), collector_coords, destination_coords, beacon, self.production_builder.get_path_nodes(), ) if path is None: return BUILD_RESULT.IMPOSSIBLE cost = self.production_builder.get_road_cost(path) for resource_id, amount in cost.iteritems(): if resource_id == RES.GOLD: if self.owner.get_component(StorageComponent).inventory[resource_id] < amount: return BUILD_RESULT.NEED_RESOURCES elif self.settlement.get_component(StorageComponent).inventory[resource_id] < amount: return BUILD_RESULT.NEED_RESOURCES return BUILD_RESULT.OK if self.production_builder.build_road(path) else BUILD_RESULT.UNKNOWN_ERROR
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 extend_settlement_with_storage(self, target_position): """Build a storage to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" if not self.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES 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.buildability_cache) storage_surrounding_offsets = Rect.get_surrounding_offsets( storage_class.size) coastline = self.land_manager.coastline options = [] for (x, y) in sorted(storage_spots): builder = BasicBuilder.create(BUILDINGS.STORAGE, (x, y), 0) alignment = 1 for (dx, dy) in storage_surrounding_offsets: coords = (x + dx, y + dy) if coords in coastline or coords not in self.plan or self.plan[ coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 distance = distances.distance_rect_rect(target_position, builder.position) value = distance - alignment * 0.7 options.append((-value, builder)) return self.build_best_option(options, BUILDING_PURPOSE.STORAGE)
def check_build_line(cls, session, point1, point2, rotation=45, ship=None): if point1 == point2: # this is actually a masked single build return [cls.check_build_fuzzy(session, point1, rotation=rotation, ship=ship)] possible_builds = [] area = Rect.init_from_corners(point1, point2) # correct placement for large buildings (mouse should be at center of building) area.left -= (cls.size[0] - 1) // 2 area.right -= (cls.size[0] - 1) // 2 area.top -= (cls.size[1] - 1) // 2 area.bottom -= (cls.size[1] - 1) // 2 xstart, xend = area.left, area.right + 1 xstep = cls.size[0] if point1.x > point2.x: xstart, xend = area.right, area.left - 1 xstep *= -1 ystart, yend = area.top, area.bottom + 1 ystep = cls.size[1] if point1.y > point2.y: ystart, yend = area.bottom, area.top - 1 ystep *= -1 for x in range(xstart, xend, xstep): for y in range(ystart, yend, ystep): possible_builds.append( cls.check_build(session, Point(x, y), rotation=rotation, ship=ship) ) return possible_builds
def extend_settlement_with_storage(self, target_position): """Build a storage to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" if not self.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES 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.buildability_cache) storage_surrounding_offsets = Rect.get_surrounding_offsets(storage_class.size) coastline = self.land_manager.coastline options = [] for (x, y) in sorted(storage_spots): builder = BasicBuilder.create(BUILDINGS.STORAGE, (x, y), 0) alignment = 1 for (dx, dy) in storage_surrounding_offsets: coords = (x + dx, y + dy) if coords in coastline or coords not in self.plan or self.plan[coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 distance = distances.distance_rect_rect(target_position, builder.position) value = distance - alignment * 0.7 options.append((-value, builder)) return self.build_best_option(options, BUILDING_PURPOSE.STORAGE)
def extend_settlement_with_tent(self, position): """Build a tent to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" distance_rect_rect = distances.distance_rect_rect size = Entities.buildings[BUILDINGS.RESIDENTIAL].size min_distance = None best_coords = None for (x, y) in self.tent_queue: ok = True for dx in xrange(size[0]): for dy in xrange(size[1]): if (x + dx, y + dy) not in self.settlement.ground_map: ok = False break if not ok: continue distance = distance_rect_rect(Rect.init_from_topleft_and_size(x, y, size[0], size[1]), position) if min_distance is None or distance < min_distance: min_distance = distance best_coords = (x, y) if min_distance is None: return BUILD_RESULT.IMPOSSIBLE return self.build_tent(best_coords)
def _get_road_to_builder(self, builder): """Return a path from the builder to a building with general collectors (None if impossible).""" loading_area = builder.get_loading_area() collector_coords = set() for building in self.collector_buildings: if loading_area.distance(building.position) == 1: return [] if loading_area.distance(building.position) > building.radius: continue # the collector building is too far to be useful for coords in self.iter_possible_road_coords( building.position, building.position): collector_coords.add(coords) destination_coords = set( self.iter_possible_road_coords(loading_area, builder.position)) if self is self.settlement_manager.production_builder: if not self.settlement_manager.production_builder.road_connectivity_cache.is_connection_possible( collector_coords, destination_coords): return None blocked_coords = set([ coords for coords in builder.position.tuple_iter() ]).union(self.land_manager.coastline) beacon = Rect.init_from_borders(loading_area.left - 1, loading_area.top - 1, loading_area.right + 1, loading_area.bottom + 1) return RoadPlanner()(self.owner.personality_manager.get('RoadPlanner'), collector_coords, destination_coords, beacon, self.get_path_nodes(), blocked_coords=blocked_coords)
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 create_map(): """ Create a map with a square island (20x20) at position (20, 20) and return the path to the database file. """ tiles = [] for x, y in Rect.init_from_topleft_and_size(0, 0, 20, 20).tuple_iter(): if (0 < x < 20) and (0 < y < 20): ground = GROUND.DEFAULT_LAND else: # Add coastline at the borders. ground = GROUND.SHALLOW_WATER tiles.append([0, 20 + x, 20 + y] + list(ground)) fd, map_file = tempfile.mkstemp() os.close(fd) db = DbReader(map_file) with open('content/map-template.sql') as map_template: db.execute_script(map_template.read()) db('BEGIN') db.execute_many("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", tiles) db('COMMIT') db.close() return map_file
def _build_extra_road_connection(self, building, collector_building): collector_coords = set( coords for coords in self.production_builder.iter_possible_road_coords( collector_building.position, collector_building.position)) destination_coords = set( coords for coords in self.production_builder.iter_possible_road_coords( building.loading_area, building.position)) pos = building.loading_area beacon = Rect.init_from_borders(pos.left - 1, pos.top - 1, pos.right + 1, pos.bottom + 1) path = RoadPlanner()(self.owner.personality_manager.get('RoadPlanner'), collector_coords, destination_coords, beacon, self.production_builder.get_path_nodes()) if path is None: return BUILD_RESULT.IMPOSSIBLE cost = self.production_builder.get_road_cost(path) for resource_id, amount in cost.items(): if resource_id == RES.GOLD: if self.owner.get_component( StorageComponent).inventory[resource_id] < amount: return BUILD_RESULT.NEED_RESOURCES elif self.settlement.get_component( StorageComponent).inventory[resource_id] < amount: return BUILD_RESULT.NEED_RESOURCES return BUILD_RESULT.OK if self.production_builder.build_road( path) else BUILD_RESULT.UNKNOWN_ERROR
def extend_settlement_with_tent(self, position): """Build a tent to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" distance_rect_rect = distances.distance_rect_rect size = Entities.buildings[BUILDINGS.RESIDENTIAL].size min_distance = None best_coords = None for (x, y) in self.tent_queue: ok = True for dx in xrange(size[0]): for dy in xrange(size[1]): if (x + dx, y + dy) not in self.settlement.ground_map: ok = False break if not ok: continue distance = distance_rect_rect( Rect.init_from_topleft_and_size(x, y, size[0], size[1]), position) if min_distance is None or distance < min_distance: min_distance = distance best_coords = (x, y) if min_distance is None: return BUILD_RESULT.IMPOSSIBLE return self.build_tent(best_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 check_build_line(cls, session, point1, point2, rotation=45, ship=None): if point1 == point2: # this is actually a masked single build return [cls.check_build_fuzzy(session, point1, rotation=rotation, ship=ship)] possible_builds = [] area = Rect.init_from_corners(point1, point2) # correct placement for large buildings (mouse should be at center of building) area.left -= (cls.size[0] - 1) / 2 area.right -= (cls.size[0] - 1) / 2 area.top -= (cls.size[1] - 1) / 2 area.bottom -= (cls.size[1] - 1) / 2 xstart, xend = area.left, area.right + 1 xstep = cls.size[0] if point1.x > point2.x: xstart, xend = area.right, area.left - 1 xstep *= -1 ystart, yend = area.top, area.bottom + 1 ystep = cls.size[1] if point1.y > point2.y: ystart, yend = area.bottom, area.top - 1 ystep *= -1 for x in xrange(xstart, xend, xstep): for y in xrange(ystart, yend, ystep): possible_builds.append(cls.check_build(session, Point(x, y), rotation=rotation, ship=ship)) return possible_builds
def __init(self, db, island_id, preview): """ Load the actual island from a file @param preview: flag, map preview mode """ p_x, p_y, width, height = db( "SELECT MIN(x), MIN(y), (1 + MAX(x) - MIN(x)), (1 + MAX(y) - MIN(y)) FROM ground WHERE island_id = ?", island_id - 1001)[0] # rect for quick checking if a tile isn't on this island # NOTE: it contains tiles, that are not on the island! self.rect = Rect(Point(p_x, p_y), width, height) self.ground_map = {} for (x, y, ground_id, action_id, rotation) in db( "SELECT x, y, ground_id, action_id, rotation FROM ground WHERE island_id = ?", island_id - 1001): # Load grounds if not preview: # actual game, need actual tiles ground = Entities.grounds[str( '%d-%s' % (ground_id, action_id))](self.session, x, y) ground.act(rotation) else: ground = MapPreviewTile(x, y, ground_id) # These are important for pathfinding and building to check if the ground tile # is blocked in any way. self.ground_map[(ground.x, ground.y)] = ground self._init_cache() self.settlements = [] self.wild_animals = [] self.num_trees = 0 # define the rectangle with the smallest area that contains every island tile its position min_x = min(zip(*self.ground_map.keys())[0]) max_x = max(zip(*self.ground_map.keys())[0]) min_y = min(zip(*self.ground_map.keys())[1]) max_y = max(zip(*self.ground_map.keys())[1]) self.position = Rect.init_from_borders(min_x, min_y, max_x, max_y) if not preview: # this isn't needed for previews, but it is in actual games self.path_nodes = IslandPathNodes(self) # repopulate wild animals every 2 mins if they die out. Scheduler().add_new_object(self.check_wild_animal_population, self, Scheduler().get_ticks(120), -1) """TUTORIAL:
def build_tent(self, coords=None): """Build the next tent (or the specified one if coords is not None).""" if not self.tent_queue: return BUILD_RESULT.IMPOSSIBLE # can_trigger_next_section is used to start building the next section when the old one is done # if a tent is built just to extend the area then that can't trigger the next section # TODO: handle extension tents nicer than just letting them die can_trigger_next_section = False if coords is None: coords = self.tent_queue[0] can_trigger_next_section = True ok = True x, y = coords owned_by_other = False size = Entities.buildings[BUILDINGS.RESIDENTIAL].size for dx in xrange(size[0]): for dy in xrange(size[1]): coords2 = (x + dx, y + dy) if coords2 not in self.settlement.ground_map: ok = False if self.island.ground_map[coords2].settlement is not None: owned_by_other = True if ok and not owned_by_other: if not self.have_resources(BUILDINGS.RESIDENTIAL): return BUILD_RESULT.NEED_RESOURCES assert BasicBuilder(BUILDINGS.RESIDENTIAL, (x, y), 0).execute(self.land_manager) if ok or owned_by_other: if self.tent_queue[0] == coords: self.tent_queue.popleft() else: for i in xrange(len(self.tent_queue)): if self.tent_queue[i] == coords: del self.tent_queue[i] break if owned_by_other: self.log.debug( '%s tent position owned by other player at (%d, %d)', self, x, y) return BUILD_RESULT.IMPOSSIBLE if not ok: # need to extends the area, it is not owned by another player self.log.debug( '%s tent position not owned by the player at (%d, %d), extending settlement area instead', self, x, y) return self.extend_settlement( Rect.init_from_topleft_and_size(x, y, size[0], size[1])) if not self.roads_built: self.build_roads() if can_trigger_next_section and self.plan[coords][1][ 0] > self.current_section: self.current_section = self.plan[coords][1][0] return BUILD_RESULT.OK
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_radius_offsets(cls): building_class = Entities.buildings[BUILDINGS.STORAGE] size = building_class.size assert size[0] == size[1] rect = Rect.init_from_topleft_and_size(0, 0, size[0], size[1]) cls._radius_offsets = [] for coords in rect.get_radius_coordinates(building_class.radius): cls._radius_offsets.append(coords)
def load_raw_map(self, savegame_db, preview=False): self.map_name = savegame_db.map_name # load islands self.islands = [] for (islandid,) in savegame_db("SELECT DISTINCT island_id + 1001 FROM ground"): island = Island(savegame_db, islandid, self.session, preview=preview) self.islands.append(island) #calculate map dimensions self.min_x, self.min_y, self.max_x, self.max_y = 0, 0, 0, 0 for island in self.islands: self.min_x = min(island.position.left, self.min_x) self.min_y = min(island.position.top, self.min_y) self.max_x = max(island.position.right, self.max_x) self.max_y = max(island.position.bottom, self.max_y) self.min_x -= savegame_db.map_padding self.min_y -= savegame_db.map_padding self.max_x += savegame_db.map_padding self.max_y += savegame_db.map_padding self.map_dimensions = Rect.init_from_borders(self.min_x, self.min_y, self.max_x, self.max_y) #add water self.log.debug("Filling world with water...") self.ground_map = {} # big sea water tile class if not preview: default_grounds = Entities.grounds[self.properties.get('default_ground', '%d-straight' % GROUND.WATER[0])] fake_tile_class = Entities.grounds['-1-special'] fake_tile_size = 10 for x in xrange(self.min_x-MAP.BORDER, self.max_x+MAP.BORDER, fake_tile_size): for y in xrange(self.min_y-MAP.BORDER, self.max_y+MAP.BORDER, fake_tile_size): fake_tile_x = x - 1 fake_tile_y = y + fake_tile_size - 1 if not preview: # we don't need no references, we don't need no mem control default_grounds(self.session, fake_tile_x, fake_tile_y) for x_offset in xrange(fake_tile_size): if self.min_x <= x + x_offset < self.max_x: for y_offset in xrange(fake_tile_size): if self.min_y <= y + y_offset < self.max_y: self.ground_map[(x+x_offset, y+y_offset)] = fake_tile_class(self.session, fake_tile_x, fake_tile_y) self.fake_tile_map = copy.copy(self.ground_map) # remove parts that are occupied by islands, create the island map and the full map self.island_map = {} self.full_map = copy.copy(self.ground_map) for island in self.islands: for coords in island.ground_map: if coords in self.ground_map: self.full_map[coords] = island.ground_map[coords] del self.ground_map[coords] self.island_map[coords] = island
def get_displayed_area(self): """Returns the coords of what is displayed on the screen as Rect""" coords = self.cam.getLocation().getLayerCoordinates() cell_dim = self.cam.getCellImageDimensions() width_x = horizons.globals.fife.engine_settings.getScreenWidth() // cell_dim.x + 1 width_y = horizons.globals.fife.engine_settings.getScreenHeight() // cell_dim.y + 1 screen_width_as_coords = (width_x // self.zoom, width_y // self.zoom) return Rect.init_from_topleft_and_size(coords.x - (screen_width_as_coords[0] // 2), coords.y - (screen_width_as_coords[1] // 2), *screen_width_as_coords)
def get_displayed_area(self): """Returns the coords of what is displayed on the screen as Rect""" coords = self.cam.getLocationRef().getLayerCoordinates() cell_dim = self.cam.getCellImageDimensions() width_x = horizons.globals.fife.engine_settings.getScreenWidth() // cell_dim.x + 1 width_y = horizons.globals.fife.engine_settings.getScreenHeight() // cell_dim.y + 1 screen_width_as_coords = (width_x // self.zoom, width_y // self.zoom) return Rect.init_from_topleft_and_size(coords.x - (screen_width_as_coords[0] // 2), coords.y - (screen_width_as_coords[1] // 2), *screen_width_as_coords)
def build_tent(self, coords=None): """Build the next tent (or the specified one if coords is not None).""" if not self.tent_queue: return BUILD_RESULT.IMPOSSIBLE # can_trigger_next_section is used to start building the next section when the old one is done # if a tent is built just to extend the area then that can't trigger the next section # TODO: handle extension tents nicer than just letting them die can_trigger_next_section = False if coords is None: coords = self.tent_queue[0] can_trigger_next_section = True ok = True x, y = coords owned_by_other = False size = Entities.buildings[BUILDINGS.RESIDENTIAL].size for dx in xrange(size[0]): for dy in xrange(size[1]): coords2 = (x + dx, y + dy) if coords2 not in self.settlement.ground_map: ok = False if self.island.ground_map[coords2].settlement is not None: owned_by_other = True if ok and not owned_by_other: builder = self.make_builder(BUILDINGS.RESIDENTIAL, x, y, False) if not builder.have_resources(): return BUILD_RESULT.NEED_RESOURCES if not builder.execute(): self.log.debug('%s unable to build a tent at (%d, %d)', self, x, y) return BUILD_RESULT.UNKNOWN_ERROR if ok or owned_by_other: if self.tent_queue[0] == coords: self.tent_queue.popleft() else: for i in xrange(len(self.tent_queue)): if self.tent_queue[i] == coords: del self.tent_queue[i] break if owned_by_other: self.log.debug('%s tent position owned by other player at (%d, %d)', self, x, y) return BUILD_RESULT.IMPOSSIBLE if not ok: # need to extends the area, it is not owned by another player self.log.debug('%s tent position not owned by the player at (%d, %d), extending settlement area instead', self, x, y) return self.extend_settlement(Rect.init_from_topleft_and_size(x, y, size[0], size[1])) if not self.roads_built: self.build_roads() if can_trigger_next_section and self.plan[coords][1][0] > self.current_section: self.current_section = self.plan[coords][1][0] return BUILD_RESULT.OK
def load_raw_map(self, savegame_db, preview=False): self.map_name = savegame_db.map_name # Load islands. for (islandid,) in savegame_db("SELECT DISTINCT island_id + 1001 FROM ground"): island = Island(savegame_db, islandid, self.session, preview=preview) self.islands.append(island) # Calculate map dimensions. self.min_x, self.min_y, self.max_x, self.max_y = 0, 0, 0, 0 for island in self.islands: self.min_x = min(island.position.left, self.min_x) self.min_y = min(island.position.top, self.min_y) self.max_x = max(island.position.right, self.max_x) self.max_y = max(island.position.bottom, self.max_y) self.min_x -= savegame_db.map_padding self.min_y -= savegame_db.map_padding self.max_x += savegame_db.map_padding self.max_y += savegame_db.map_padding self.map_dimensions = Rect.init_from_borders(self.min_x, self.min_y, self.max_x, self.max_y) # Add water. self.log.debug("Filling world with water...") self.ground_map = {} # big sea water tile class if not preview: default_grounds = Entities.grounds[self.properties.get('default_ground', '%d-straight' % GROUND.WATER[0])] fake_tile_class = Entities.grounds['-1-special'] fake_tile_size = 10 for x in xrange(self.min_x-MAP.BORDER, self.max_x+MAP.BORDER, fake_tile_size): for y in xrange(self.min_y-MAP.BORDER, self.max_y+MAP.BORDER, fake_tile_size): fake_tile_x = x - 1 fake_tile_y = y + fake_tile_size - 1 if not preview: # we don't need no references, we don't need no mem control default_grounds(self.session, fake_tile_x, fake_tile_y) for x_offset in xrange(fake_tile_size): if self.min_x <= x + x_offset < self.max_x: for y_offset in xrange(fake_tile_size): if self.min_y <= y + y_offset < self.max_y: self.ground_map[(x+x_offset, y+y_offset)] = fake_tile_class(self.session, fake_tile_x, fake_tile_y) self.fake_tile_map = copy.copy(self.ground_map) # Remove parts that are occupied by islands, create the island map and the full map. self.island_map = {} self.full_map = copy.copy(self.ground_map) for island in self.islands: for coords in island.ground_map: if coords in self.ground_map: self.full_map[coords] = island.ground_map[coords] del self.ground_map[coords] self.island_map[coords] = island
def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None): """ @param position: a Rect or a Pychan Icon, where we will draw to @param world: World object or fake thereof @param view: View object for cam control. Can be None to disable this @param renderer: renderer to be used if position isn't an icon @param targetrenderer: fife target rendererfor drawing on icons @param imagemanager: fife imagemanager for drawing on icons @param cam_border: boolean, whether to draw the cam border @param use_rotation: boolean, whether to use rotation (it must also be enabled in the settings) @param on_click: function taking 1 argument or None for scrolling @param preview: flag, whether to only show the map as preview @param tooltip: always show this tooltip when cursor hovers over minimap """ if isinstance(position, Rect): self.location = position self.renderer = renderer else: # assume icon self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) self.icon = position self.use_overlay_icon(self.icon) self.session = session self.world = world if self.world: self._update_world_to_minimap_ratio() self.view = view self.rotation = 0 self.fixed_tooltip = tooltip if on_click is not None: self.on_click = on_click self.cam_border = cam_border self.use_rotation = use_rotation self.preview = preview self.location_center = self.location.center self._id = str(self.__class__.__minimap_id_counter.next()) # internal identifier, used for allocating resources self._image_size_cache = {} # internal detail self.imagemanager = imagemanager self.minimap_image = _MinimapImage(self, targetrenderer) #import random #ExtScheduler().add_new_object(lambda : self.highlight( (50+random.randint(-50,50), random.randint(-50,50) + 50 )), self, 2, loops=-1) self._rotation_setting = horizons.globals.fife.get_uh_setting("MinimapRotation") if self.use_rotation: MinimapRotationSettingChanged.subscribe(self._on_rotation_setting_change)
def load(cls, db, worldid, session, island): self = cls.__new__(cls) self.session = session super(Settlement, self).load(db, worldid) owner = db("SELECT owner FROM settlement WHERE rowid = ?", worldid)[0][0] upgrade_permissions = {} tax_settings = {} for level, allowed, tax in db("SELECT level, upgrading_allowed, tax_setting FROM settlement_level_properties WHERE settlement = ?", worldid): upgrade_permissions[level] = allowed tax_settings[level] = tax self.__init(session, WorldObject.get_object_by_id(owner), upgrade_permissions, tax_settings) try: # normal tile loading for new savegames tile_data = db("SELECT data FROM settlement_tiles WHERE rowid = ?", worldid)[0][0] tile_data = json.loads(tile_data) for (x, y) in tile_data: # NOTE: json saves tuples as list tup = (x, y) tile = island.ground_map[tup] self.ground_map[tup] = tile tile.settlement = self except sqlite3.OperationalError: print "Updating data of outdated savegame.." # old savegame, create settlement tiles provisionally (not correct, but useable) # TODO: remove when there aren't any savegames from before december 2011 any more for b_type, x, y in db("SELECT type, x, y FROM building WHERE location = ?", worldid): cls = Entities.buildings[b_type] position = Rect.init_from_topleft_and_size(x, y, cls.size[0], cls.size[1]) for coord in position.get_radius_coordinates(cls.radius, include_self=True): tile = island.get_tile_tuple(coord) if tile is not None: if tile.settlement is None: self.ground_map[coord] = island.ground_map[coord] tile.settlement = self # load super here cause basic stuff is just set up now # load all buildings from this settlement # the buildings will expand the area of the settlement by adding everything, # that is in the radius of the building, to the settlement. from horizons.world import load_building for building_id, building_type in \ db("SELECT rowid, type FROM building WHERE location = ?", worldid): building = load_building(session, db, building_type, building_id) if building_type == BUILDINGS.WAREHOUSE: self.warehouse = building for res, amount in db("SELECT res, amount FROM settlement_produced_res WHERE settlement = ?", worldid): self.produced_res[res] = amount return self
def __init(self, db, island_id, preview): """ Load the actual island from a file @param preview: flag, map preview mode """ p_x, p_y, width, height = db( "SELECT MIN(x), MIN(y), (1 + MAX(x) - MIN(x)), (1 + MAX(y) - MIN(y)) FROM ground WHERE island_id = ?", island_id - 1001)[0] self.ground_map = {} for (x, y, ground_id, action_id, rotation) in db( "SELECT x, y, ground_id, action_id, rotation FROM ground WHERE island_id = ?", island_id - 1001): # Load grounds if not preview: # actual game, need actual tiles ground = Entities.grounds[str('{:d}-{}'.format( ground_id, action_id))](self.session, x, y) ground.act(rotation) else: ground = MapPreviewTile(x, y, ground_id) # These are important for pathfinding and building to check if the ground tile # is blocked in any way. self.ground_map[(ground.x, ground.y)] = ground self._init_cache() # Contains references to all resource deposits (but not mines) # on the island, regardless of the owner: # {building_id: {(x, y): building_instance, ...}, ...} self.deposits = defaultdict(dict) self.settlements = [] self.wild_animals = [] self.num_trees = 0 # define the rectangle with the smallest area that contains every island tile its position min_x = min(list(zip(*self.ground_map.keys()))[0]) max_x = max(list(zip(*self.ground_map.keys()))[0]) min_y = min(list(zip(*self.ground_map.keys()))[1]) max_y = max(list(zip(*self.ground_map.keys()))[1]) self.position = Rect.init_from_borders(min_x, min_y, max_x, max_y) if not preview: # This isn't needed for map previews, but it is in actual games. self.path_nodes = IslandPathNodes(self) self.barrier_nodes = IslandBarrierNodes(self) # Repopulate wild animals every 2 mins if they die out. Scheduler().add_new_object(self.check_wild_animal_population, self, run_in=Scheduler().get_ticks(120), loops=-1) """TUTORIAL:
def load_raw_map(self, savegame_db, preview=False): # load islands self.islands = [] for (islandid,) in savegame_db("SELECT rowid + 1000 FROM island"): island = Island(savegame_db, islandid, self.session, preview=preview) self.islands.append(island) #calculate map dimensions self.min_x, self.min_y, self.max_x, self.max_y = 0, 0, 0, 0 for i in self.islands: self.min_x = min(i.rect.left, self.min_x) self.min_y = min(i.rect.top, self.min_y) self.max_x = max(i.rect.right, self.max_x) self.max_y = max(i.rect.bottom, self.max_y) self.min_x -= 10 self.min_y -= 10 self.max_x += 10 self.max_y += 10 self.map_dimensions = Rect.init_from_borders(self.min_x, self.min_y, self.max_x, self.max_y) #add water self.log.debug("Filling world with water...") self.ground_map = {} # big sea water tile class if not preview: default_grounds = Entities.grounds[int(self.properties.get('default_ground', GROUND.WATER[0]))] # extra world size that is added so that the player can't see the "black void" border = 30 fake_tile_class = Entities.grounds[-1] for x in xrange(self.min_x-border, self.max_x+border, 10): for y in xrange(self.min_y-border, self.max_y+border, 10): if not preview: # we don't need no references, we don't need no mem control default_grounds(self.session, x, y) for x_offset in xrange(0, 10): if x+x_offset < self.max_x and x+x_offset >= self.min_x: for y_offset in xrange(0, 10): if y+y_offset < self.max_y and y+y_offset >= self.min_y: self.ground_map[(x+x_offset, y+y_offset)] = fake_tile_class(self.session, x, y) # remove parts that are occupied by islands, create the island map and the full map self.island_map = {} self.full_map = copy.copy(self.ground_map) for island in self.islands: for coords in island.ground_map: if coords in self.ground_map: self.full_map[coords] = island.ground_map[coords] del self.ground_map[coords] self.island_map[coords] = island
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(self, db, island_id, preview): """ Load the actual island from a file @param preview: flag, map preview mode """ p_x, p_y, width, height = db( "SELECT MIN(x), MIN(y), (1 + MAX(x) - MIN(x)), (1 + MAX(y) - MIN(y)) FROM ground WHERE island_id = ?", island_id - 1001, )[0] self.ground_map = {} for (x, y, ground_id, action_id, rotation) in db( "SELECT x, y, ground_id, action_id, rotation FROM ground WHERE island_id = ?", island_id - 1001 ): # Load grounds if not preview: # actual game, need actual tiles ground = Entities.grounds[str("%d-%s" % (ground_id, action_id))](self.session, x, y) ground.act(rotation) else: ground = MapPreviewTile(x, y, ground_id) # These are important for pathfinding and building to check if the ground tile # is blocked in any way. self.ground_map[(ground.x, ground.y)] = ground self._init_cache() # Contains references to all resource deposits (but not mines) # on the island, regardless of the owner: # {building_id: {(x, y): building_instance, ...}, ...} self.deposits = defaultdict(dict) self.settlements = [] self.wild_animals = [] self.num_trees = 0 # define the rectangle with the smallest area that contains every island tile its position min_x = min(zip(*self.ground_map.keys())[0]) max_x = max(zip(*self.ground_map.keys())[0]) min_y = min(zip(*self.ground_map.keys())[1]) max_y = max(zip(*self.ground_map.keys())[1]) self.position = Rect.init_from_borders(min_x, min_y, max_x, max_y) if not preview: # This isn't needed for map previews, but it is in actual games. self.path_nodes = IslandPathNodes(self) # Repopulate wild animals every 2 mins if they die out. Scheduler().add_new_object( self.check_wild_animal_population, self, run_in=Scheduler().get_ticks(120), loops=-1 ) """TUTORIAL:
def __init(self, origin, filename, preview=False): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id @param preview: flag, map preview mode """ self.file = filename self.origin = origin db = self._get_island_db() p_x, p_y, width, height = db("SELECT (MIN(x) + ?), (MIN(y) + ?), (1 + MAX(x) - MIN(x)), (1 + MAX(y) - MIN(y)) FROM ground", self.origin.x, self.origin.y)[0] # rect for quick checking if a tile isn't on this island # NOTE: it contains tiles, that are not on the island! self.rect = Rect(Point(p_x, p_y), width, height) self.ground_map = {} for (rel_x, rel_y, ground_id, action_id, rotation) in db("SELECT x, y, ground_id, action_id, rotation FROM ground"): # Load grounds if not preview: # actual game, need actual tiles ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) ground.act(action_id, rotation) else: ground = Point(self.origin.x + rel_x, self.origin.y + rel_y) ground.classes = tuple() ground.settlement = None # These are important for pathfinding and building to check if the ground tile # is blocked in any way. self.ground_map[(ground.x, ground.y)] = ground self._init_cache() self.settlements = [] self.wild_animals = [] self.num_trees = 0 # define the rectangle with the smallest area that contains every island tile its position min_x = min(zip(*self.ground_map.keys())[0]) max_x = max(zip(*self.ground_map.keys())[0]) min_y = min(zip(*self.ground_map.keys())[1]) max_y = max(zip(*self.ground_map.keys())[1]) self.position = Rect.init_from_borders(min_x, min_y, max_x, max_y) if not preview: # this isn't needed for previews, but it is in actual games self.path_nodes = IslandPathNodes(self) # repopulate wild animals every 2 mins if they die out. Scheduler().add_new_object(self.check_wild_animal_population, self, Scheduler().get_ticks(120), -1) """TUTORIAL:
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 _update(self, tup): """Recalculate and redraw minimap for real world coord tup @param tup: (x, y)""" if self.world is None or not self.world.inited: return # don't draw while loading minimap_point = self._world_to_minimap( tup, self._get_rotation_setting() ) world_to_minimap = self._world_to_minimap_ratio # TODO: remove this remnant of the old implementation, perhaps by refactoring recalculate() minimap_point = ( minimap_point[0] + self.location.left, minimap_point[1] + self.location.top, ) rect = Rect.init_from_topleft_and_size(minimap_point[0], minimap_point[1], int(round(1/world_to_minimap[0])) + 1, int(round(1/world_to_minimap[1])) + 1) self._recalculate(rect)
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 testRect(self): r1 = Rect(Point(0,0), 1, 1) r2 = Rect(0, 0, 1, 1) r3 = Rect(Point(2, 2), 1, 1) self.assertEqual(r1, r2) self.assertTrue(r1 == r2) self.assertFalse(r1.contains(Point(-1,-1))) self.assertTrue(r2.contains(Point(0,0))) self.assertTrue(r2.contains(Point(1,1))) self.assertTrue(r1.intersects(r2)) self.assertFalse(r1.intersects(r3))
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 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 _get_road_to_builder(self, builder): """Return a path from the builder to a building with general collectors (None if impossible).""" loading_area = builder.get_loading_area() collector_coords = set() for building in self.collector_buildings: if loading_area.distance(building.position) == 1: return [] if loading_area.distance(building.position) > building.radius: continue # the collector building is too far to be useful for coords in self.iter_possible_road_coords(building.position, building.position): collector_coords.add(coords) blocked_coords = set([coords for coords in builder.position.tuple_iter()]) destination_coords = set(self.iter_possible_road_coords(loading_area, builder.position)) beacon = Rect.init_from_borders(loading_area.left - 1, loading_area.top - 1, loading_area.right + 1, loading_area.bottom + 1) return RoadPlanner()(self.owner.personality_manager.get('RoadPlanner'), collector_coords, destination_coords, beacon, self.get_path_nodes(), blocked_coords=blocked_coords)
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 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)