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 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 testRadiusCoords(self): area = Rect.init_from_topleft_and_size(0, 0, 100, 100) tree = TileQuadTree(area) tree2 = TileQuadTree(area) tree2_check = lambda coord : coord[0] % 2 == 0 and coord[0] % 3 == 0 and coord[1] % 5 == 0 for coord in area.tuple_iter(): tree.add_tile( _FakeTile(coord[0], coord[1] ) ) if tree2_check(coord): tree2.add_tile( _FakeTile(coord[0], coord[1]) ) def get_diff_msg(l1, l2): msg = 'unequal at radius '+ str(radius) msg += '\nl1: ' + str(l1) msg += '\nl2: ' + str(l2) diff1 = [ i for i in l1 if i not in l2 ] diff2 = [ i for i in l2 if i not in l1 ] msg += '\ndiff1: ' + str(diff1) msg += '\ndiff2: ' + str(diff2) return msg def do_test(center, radius): l1 = [] for tile in tree.get_radius_tiles(center, radius): l1.append((tile.x, tile.y)) l2 = [] tree.visit_radius_tiles(center, radius, lambda x : l2.append((x.x, x.y))) l1.sort() l2.sort() l3 = sorted(center.get_radius_coordinates(radius, include_self=True)) self.assertEqual(l1, l3, get_diff_msg(l1, l3)) self.assertEqual(l1, l2, get_diff_msg(l1, l2)) def do_test2(center, radius): l1 = [] for tile in tree2.get_radius_tiles(center, radius): l1.append((tile.x, tile.y)) l2 = [] tree2.visit_radius_tiles(center, radius, lambda x : l2.append((x.x, x.y))) l1.sort() l2.sort() l3 = [ x for x in sorted(center.get_radius_coordinates(radius, include_self=True)) if tree2_check(x)] self.assertEqual(l1, l3, get_diff_msg(l1, l3)) self.assertEqual(l1, l2, get_diff_msg(l1, l2)) center = Rect.init_from_topleft_and_size(20, 20, 0, 0) center2 = Rect.init_from_topleft_and_size(20, 20, 3, 3) center3 = Rect.init_from_topleft_and_size(20, 20, 5, 2) print 'checking for correctness' for radius in xrange(0,15): do_test(center, radius) do_test(center2, radius) do_test(center3, radius) do_test2(center, radius) do_test2(center2, radius) do_test2(center3, radius)
def get_loading_area(cls, building_id, rotation, pos): if building_id == BUILDINGS.MOUNTAIN or building_id == BUILDINGS.IRON_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 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 __init(self, origin, filename): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id """ self.file = filename self.origin = origin # check if filename is a random map if random_map.is_random_island_id_string(filename): # it's a random map id, create this map and load it db = random_map.create_random_island(filename) else: db = DbReader(filename) # Create a new DbReader instance to load the maps file. 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) in db("SELECT x, y, ground_id FROM ground"): # Load grounds ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) # 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 self.path_nodes = IslandPathNodes(self) # 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) # 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 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 __init__( self, position, session, world, view, targetrenderer, imagemanager, renderer=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)
def __init(self, origin, filename): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id """ self.file = filename self.origin = origin # check if filename is a random map if random_map.is_random_island_id_string(filename): # it's a random map id, create this map and load it db = random_map.create_random_island(filename) else: db = DbReader( filename ) # Create a new DbReader instance to load the maps file. 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 ) in db("SELECT x, y, ground_id FROM ground"): # Load grounds ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) # 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.settlements = [] self.wild_animals = [] 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 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() screen_width_as_coords = (horizons.main.fife.engine_settings.getScreenWidth()/cell_dim.x, \ horizons.main.fife.engine_settings.getScreenHeight()/cell_dim.y) 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 __init(self, deposit_class, mine_empty_msg_shown): self.__deposit_class = deposit_class self._mine_empty_msg_shown = mine_empty_msg_shown # setup loading area # TODO: for now we assume that a mine building is 5x5 with a 3x1 entry on 1 side # this needs to be generalised, possibly by defining the loading tiles in the db pos = self.position if self.rotation == 45: self.loading_area = Rect.init_from_topleft_and_size(pos.origin.x, pos.origin.y + 1, 0, 2) elif self.rotation == 135: self.loading_area = Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y + pos.height - 1, 2, 0) elif self.rotation == 225: self.loading_area = Rect.init_from_topleft_and_size(pos.origin.x + pos.width -1, pos.origin.y + 1, 0, 2) elif self.rotation == 315: self.loading_area = Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y, 2, 0) else: assert False
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 handle_lost_area(self, coords_list): """Handle losing the potential land in the given coordinates list.""" # remove planned fields that are now impossible field_size = Entities.buildings[BUILDINGS.POTATO_FIELD_CLASS].size removed_list = [] for coords, (purpose, _) in self.plan.iteritems(): if purpose in [BUILDING_PURPOSE.POTATO_FIELD, BUILDING_PURPOSE.PASTURE, BUILDING_PURPOSE.SUGARCANE_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) for field_coords in rect.tuple_iter(): self.plan[field_coords] = (BUILDING_PURPOSE.NONE, None) self._refresh_unused_fields() super(ProductionBuilder, self).handle_lost_area(coords_list)
def testInsertFull(self): tiles = {} for coord in self.default_rect.tuple_iter(): tiles[coord] = _FakeTile(coord[0], coord[1]) tree = TileQuadTree(Rect.init_from_topleft_and_size(0,0, 5, 5)) for coord, tile in tiles.iteritems(): self.assertEqual( tree.get_tile(*coord), None ) tree.add_tile(tile) self.assertEqual( tree.get_tile(*coord), tile )
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_CLASS].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_CLASS, 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 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._get_rotated_coords(self._world_coord_to_minimap_coord(tup)) world_to_minimap = self._get_world_to_minimap_ratio() rect = Rect.init_from_topleft_and_size( minimap_point[0], minimap_point[1], int(round(1 / world_to_minimap[0])), int(round(1 / world_to_minimap[1])) ) self._recalculate(rect)
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] - 1, cls.size[1] - 1) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1] - 1, cls.size[0] - 1) buildable = True tearset = [] return _BuildPosition(position, rotation, tearset, buildable)
def testRadiusCoordsSpeed(self): tree = TileQuadTree(Rect.init_from_topleft_and_size(0, 0, 300, 300) ) print 'testing speed (may take a while)' for x in xrange(0,300): if x % 20 == 0: print int((float(x)/300)*100) for y in xrange(0,300): tree.add_tile(_FakeTile(x, y) ) center = Rect.init_from_topleft_and_size(144,145,10,10) import cProfile as profile import tempfile #outfilename = tempfile.mkstemp(text = True)[1] #print 'profile to ', outfilename #profile.runctx( "for i in tree.get_radius_tiles(center, 100): a_app(i)" , globals(), locals(), outfilename) def cb(x): pass outfilename = tempfile.mkstemp(text = True)[1] print 'profile to ', outfilename profile.runctx( "tree.visit_radius_tiles(center, 120, cb)" , globals(), locals(), outfilename)
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._get_rotated_coords( self._world_coord_to_minimap_coord(tup)) world_to_minimap = self._get_world_to_minimap_ratio() rect = Rect.init_from_topleft_and_size(minimap_point[0], minimap_point[1], \ int(round(1/world_to_minimap[0])), \ int(round(1/world_to_minimap[1]))) self._recalculate(rect)
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 = i.rect.left if self.min_x is None or i.rect.left < self.min_x else self.min_x self.min_y = i.rect.top if self.min_y is None or i.rect.top < self.min_y else self.min_y self.max_x = i.rect.right if self.max_x is None or i.rect.right > self.max_x else self.max_x self.max_y = i.rect.bottom if self.max_y is None or i.rect.bottom > self.max_y else 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 testInsert(self): tiles = {} tiles[(2,2)] = _FakeTile(2, 2) tiles[(2,3)] = _FakeTile(2, 3) tiles[(3,2)] = _FakeTile(3, 2) tiles[(3,3)] = _FakeTile(3, 3) tiles[(0,0)] = _FakeTile(0, 0) tree = TileQuadTree(Rect.init_from_topleft_and_size(0,0, 5, 5)) for coord, tile in tiles.iteritems(): self.assertEqual( tree.get_tile(*coord), None ) tree.add_tile(tile) self.assertEqual( tree.get_tile(*coord), tile )
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_CLASS].size) moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] coords_list = set(position.get_radius_coordinates(Entities.buildings[BUILDINGS.LUMBERJACK_CLASS].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 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] - 1, cls.size[1] - 1) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1] - 1, cls.size[0] - 1) buildable = True tearset = [] try: 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) if check_settlement: cls._check_settlement(session, position, ship=ship, issuer=issuer) except _NotBuildableError: buildable = False return _BuildPosition(position, rotation, tearset, buildable)
def __init(self, deposit_class, mine_empty_msg_shown): self.__deposit_class = deposit_class self._mine_empty_msg_shown = mine_empty_msg_shown # setup loading area # TODO: for now we assume that a mine building is 5x5 with a 3x1 entry on 1 side # this needs to be generalised, possibly by defining the loading tiles in the db pos = self.position if self.rotation == 45: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x, pos.origin.y + 1, 0, 2) elif self.rotation == 135: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x + 1, pos.origin.y + pos.height - 1, 2, 0) elif self.rotation == 225: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x + pos.width - 1, pos.origin.y + 1, 0, 2) elif self.rotation == 315: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x + 1, pos.origin.y, 2, 0) else: assert False
def check_build_line(cls, session, point1, point2, rotation=45, ship=None): 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 for x in xrange(area.left, area.right + 1, cls.size[0]): for y in xrange(area.top, area.bottom + 1, cls.size[1]): possible_builds.append(cls.check_build(session, Point(x, y), rotation=rotation, ship=ship)) return possible_builds
def check_build_line(cls, session, point1, point2, rotation=45, ship=None): 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 for x in xrange(area.left, area.right + 1, cls.size[0]): for y in xrange(area.top, area.bottom + 1, cls.size[1]): possible_builds.append( \ cls.check_build(session, Point(x, y), rotation=rotation, ship=ship) \ ) return possible_builds
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_job(self): jobs = JobList(self, JobList.order_by.random) collectable_resources = self.get_needed_resources() # iterate over all possible providers and needed resources # and save possible job targets position_rect = Rect.init_from_topleft_and_size(self.position.x, self.position.y, 0, 0) reach = RadiusRect(position_rect, self.walking_range) for provider in self.home_island.get_providers_in_range(reach): if self.check_possible_job_target(provider): for res in collectable_resources: job = self.check_possible_job_target_for(provider, res) if job is not None: jobs.append(job) return self.get_best_possible_job(jobs)
def _timed_update(self): """Regular updates for domains we can't or don't want to keep track of.""" # update ship dots self.renderer.removeAll("minimap_ship") for ship in self.world.ship_map.itervalues(): coord = self._world_coord_to_minimap_coord(ship().position.to_tuple()) color = ship().owner.color.to_tuple() area_to_color = Rect.init_from_topleft_and_size(coord[0], coord[1], 2, 2) for tup in area_to_color.tuple_iter(): try: self.renderer.addPoint("minimap_ship", self.renderernodes[self._get_rotated_coords(tup)], *color) except KeyError: # this happens in rare cases, when the ship is at the border of the map, # and since we color an area, that's bigger than a point, it can exceed the # minimap's dimensions. pass
def get_job(self): jobs = JobList(self, JobList.order_by.random) collectable_resources = self.get_needed_resources() # iterate over all possible providers and needed resources # and save possible job targets position_rect = Rect.init_from_topleft_and_size( self.position.x, self.position.y, 0, 0) reach = RadiusRect(position_rect, self.walking_range) for provider in self.home_island.get_providers_in_range(reach): if self.check_possible_job_target(provider): for res in collectable_resources: job = self.check_possible_job_target_for(provider, res) if job is not None: jobs.append(job) return self.get_best_possible_job(jobs)
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.get_buildings_by_id(BUILDINGS.FARM_CLASS) 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_CLASS].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 _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_ID: if self.owner.inventory[resource_id] < amount: return BUILD_RESULT.NEED_RESOURCES elif self.settlement.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 _timed_update(self): """Regular updates for domains we can't or don't want to keep track of.""" # update ship dots self.renderer.removeAll("minimap_ship") for ship in self.world.ship_map.itervalues(): coord = self._world_coord_to_minimap_coord( ship().position.to_tuple()) color = ship().owner.color.to_tuple() area_to_color = Rect.init_from_topleft_and_size( coord[0], coord[1], 2, 2) for tup in area_to_color.tuple_iter(): try: self.renderer.addPoint( "minimap_ship", self.renderernodes[self._get_rotated_coords(tup)], *color) except KeyError: # this happens in rare cases, when the ship is at the border of the map, # and since we color an area, that's bigger than a point, it can exceed the # minimap's dimensions. pass
def __init(self, origin, filename): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id """ self.file = filename self.origin = origin # check if filename is a random map if random_map.is_random_island_id_string(filename): # it's a random map id, create this map and load it db = random_map.create_random_island(filename) else: db = DbReader(filename) # Create a new DbReader instance to load the maps file. 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) in db("select x, y, ground_id from ground"): # Load grounds ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) # 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.settlements = [] self.buildings = [] self.provider_buildings = ProviderHandler() self.wild_animals = [] 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 create_map(): """ Create a map with a square island (20x20) at position (20, 20) and return the path to the database file. """ # Create island. fd, islandfile = tempfile.mkstemp() os.close(fd) db = DbReader(islandfile) db("CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL, action_id TEXT NOT NULL, rotation INTEGER NOT NULL)") db("CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)") db("BEGIN TRANSACTION") 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([x, y] + list(ground)) db.execute_many("INSERT INTO ground VALUES(?, ?, ?, ?, ?)", tiles) db("COMMIT") # Create savegame with the island above. fd, savegame = tempfile.mkstemp() os.close(fd) db = DbReader(savegame) read_savegame_template(db) db("BEGIN TRANSACTION") db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", 20, 20, islandfile) db("COMMIT") return savegame
def extend_settlement_with_tent(self, position): """Build a tent to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" size = Entities.buildings[BUILDINGS.RESIDENTIAL].size min_distance = None best_coords = None for (x, y) in self.settlement_manager.village_builder.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 = Rect.init_from_topleft_and_size(x, y, size[0], size[1]).distance(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.settlement_manager.village_builder.build_tent(best_coords)
def _init(self, savegame_db): """ @param savegame_db: Dbreader with loaded savegame database """ #load properties self.properties = {} for (name, value) in savegame_db("SELECT name, value FROM map_properties"): self.properties[name] = value # create playerlist self.players = [] self.player = None # player sitting in front of this machine self.trader = None self.pirate = None # load player human_players = [] for player_worldid, client_id in savegame_db( "SELECT rowid, client_id FROM player WHERE is_trader = 0"): player = None # check if player is an ai ai_data = self.session.db( "SELECT class_package, class_name FROM ai WHERE id = ?", client_id) if len(ai_data) > 0: class_package, class_name = ai_data[0] # import ai class and call load on it module = __import__('horizons.ai.' + class_package, fromlist=[class_name]) ai_class = getattr(module, class_name) player = ai_class.load(self.session, savegame_db, player_worldid) else: # no ai player = HumanPlayer.load(self.session, savegame_db, player_worldid) self.players.append(player) if client_id == horizons.main.fife.get_uh_setting("ClientID"): self.player = player elif client_id is not None and len(ai_data) == 0: # possible human player candidate with different client id human_players.append(player) if self.player is None: # we have no human player. # check if there is only one player with an id (i.e. human player) # this would be the case if the savegame originates from a different installation. # if there's more than one of this kind, we can't be sure what to select. # TODO: create interface for selecting player, if we want this if (len(human_players) == 1): # exactly one player, we can quite safely use this one self.player = human_players[0] if self.player is None and self.session.is_game_loaded(): self.log.warning( 'WARNING: Cannot autoselect a player because there are no \ or multiple candidates.') # load islands self.islands = [] for (islandid, ) in savegame_db("SELECT rowid + 1000 FROM island"): island = Island(savegame_db, islandid, self.session) self.islands.append(island) #calculate map dimensions self.min_x, self.min_y, self.max_x, self.max_y = None, None, None, None for i in self.islands: self.min_x = i.rect.left if self.min_x is None or i.rect.left < self.min_x else self.min_x self.min_y = i.rect.top if self.min_y is None or i.rect.top < self.min_y else self.min_y self.max_x = i.rect.right if self.max_x is None or i.rect.right > self.max_x else self.max_x self.max_y = i.rect.bottom if self.max_y is None or i.rect.bottom > self.max_y else 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 = {} default_grounds = Entities.grounds[int( self.properties.get('default_ground', GROUND.WATER))] # extra world size that is added so that he player can't see the "black void" border = 30 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): ground = 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)] = ground # "unfill" parts that are occupied by island # TODO: check if constructing a list of water coords is faster than calling the Ground() so many times for island in self.islands: for coord in island.ground_map: if coord in self.ground_map: del self.ground_map[coord] # load world buildings (e.g. fish) for (building_worldid, building_typeid) in \ savegame_db("SELECT rowid, type FROM building WHERE location = ?", self.worldid): load_building(self.session, savegame_db, building_typeid, building_worldid) self.water = list(self.ground_map) # assemble list of water and coastline for ship, that can drive through shallow water # NOTE: this is rather a temporary fix to make the fisher be able to move # since there are tile between coastline and deep sea, all non-constructible tiles # are added to this list as well, which will contain a few too many self.water_and_coastline = self.water[:] for island in self.islands: for coord, tile in island.ground_map.iteritems(): if 'coastline' in tile.classes or 'constructible' not in tile.classes: self.water_and_coastline.append(coord) # create ship position list. entries: ship_map[(x, y)] = ship self.ship_map = {} # create shiplist, which is currently used for saving ships # and having at least one reference to them self.ships = [] if self.session.is_game_loaded(): # for now, we have one trader in every game, so this is safe: trader_id = savegame_db( "SELECT rowid FROM player WHERE is_trader = 1")[0][0] self.trader = Trader.load(self.session, savegame_db, trader_id) # load all units (we do it here cause all buildings are loaded by now) for (worldid, typeid ) in savegame_db("SELECT rowid, type FROM unit ORDER BY rowid"): Entities.units[typeid].load(self.session, savegame_db, worldid) if self.session.is_game_loaded(): # let trader command it's ships. we have to do this here cause ships have to be # initialised for this, and trader has to exist before ships are loaded. self.trader.load_ship_states(savegame_db) self.inited = True """TUTORIAL:
def generate_map(seed=None): """Generates a whole map. @param seed: argument passed to random.seed @return filename to the sqlite db containing the new map""" rand = random.Random(seed) filename = tempfile.mkstemp()[1] shutil.copyfile(PATHS.SAVEGAME_TEMPLATE, filename) db = DbReader(filename) island_space = (35, 35) island_min_size = (25, 25) island_max_size = (28, 28) method = rand.randint(0, 1) # choose map creation method if method == 0: # generate up to 9 islands number_of_islands = 0 for i in Rect.init_from_topleft_and_size(0, 0, 2, 2): if rand.randint(0, 2) != 0: # 2/3 chance for an island here number_of_islands = number_of_islands + 1 x = int(i.x * island_space[0] * (rand.random() / 6 + 0.90)) y = int(i.y * island_space[1] * (rand.random() / 6 + 0.90)) island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 0, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0], island_max_size[0]), \ 'height': rand.randint(island_min_size[1], island_max_size[1])} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) # if there is 1 or 0 islands created, it places 1 large island in the centre if number_of_islands == 0: x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif number_of_islands == 1: db("DELETE FROM island") x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif method == 1: # places 1 large island in the centre x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) return filename
def create_random_island(id_string): """Creates a random island as sqlite db. It is rather primitive; it places shapes on the dict. @param id_string: random island id string @return: sqlite db reader containing island """ # NOTE: the tilesystem will be redone soon, so constants indicating grounds are temporary # here and will have to be changed anyways. match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed = [ long(i) for i in match_obj.groups() ] rand = random.Random(seed) map_dict = {} # creation_method 0 - standard small island for the 3x3 grid # creation_method 1 - large island # place this number of shapes for i in xrange(int(float(width + height) / 2 * 1.5)): x = rand.randint(4, width - 4) y = rand.randint(4, height - 4) # place shape determined by shape_id on (x, y) if creation_method == 0: shape_id = rand.randint(3, 5) elif creation_method == 1: shape_id = rand.randint(5, 8) if rand.randint(1, 4) == 1: # use a rect if creation_method == 0: for shape_coord in Rect.init_from_topleft_and_size( x - 3, y - 3, 5, 5).tuple_iter(): map_dict[shape_coord] = 1 elif creation_method == 1: for shape_coord in Rect.init_from_topleft_and_size( x - 5, y - 5, 8, 8).tuple_iter(): map_dict[shape_coord] = 1 else: # use a circle, where radius is determined by shape_id for shape_coord in Circle(Point(x, y), shape_id).tuple_iter(): map_dict[shape_coord] = 1 # write values to db map_db = DbReader(":memory:") map_db( "CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL)" ) map_db( "CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)" ) map_db("BEGIN TRANSACTION") # assign these characters, if a coastline is found in this offset offset_coastline = {'a': (0, -1), 'b': (1, 0), 'c': (0, 1), 'd': (-1, 0)} for x, y in map_dict.iterkeys(): # add a coastline tile for coastline, or default land else coastline = "" for offset_char in sorted(offset_coastline): if (x + offset_coastline[offset_char][0], y + offset_coastline[offset_char][1]) not in map_dict: coastline += offset_char if coastline: # TODO: use coastline tile depending on coastline map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, 49) else: map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, GROUND.DEFAULT_LAND) map_db("COMMIT") return map_db
def create_random_island(id_string): """Creates a random island as sqlite db. It is rather primitive; it places shapes on the dict. @param id_string: random island id string @return: sqlite db reader containing island """ # NOTE: the tilesystem will be redone soon, so constants indicating grounds are temporary # here and will have to be changed anyways. match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed = [ long(i) for i in match_obj.groups() ] rand = random.Random(seed) map_dict = {} # creation_method 0 - standard small island for the 3x3 grid # creation_method 1 - large island # place this number of shapes for i in xrange(int(float(width + height) / 2 * 1.5)): x = rand.randint(8, width - 8) y = rand.randint(8, height - 8) # place shape determined by shape_id on (x, y) if creation_method == 0: shape_id = rand.randint(3, 5) elif creation_method == 1: shape_id = rand.randint(5, 8) if rand.randint(1, 4) == 1: # use a rect if creation_method == 0: for shape_coord in Rect.init_from_topleft_and_size( x - 3, y - 3, 5, 5).tuple_iter(): map_dict[shape_coord] = True elif creation_method == 1: for shape_coord in Rect.init_from_topleft_and_size( x - 5, y - 5, 8, 8).tuple_iter(): map_dict[shape_coord] = True else: # use a circle, where radius is determined by shape_id for shape_coord in Circle(Point(x, y), shape_id).tuple_iter(): map_dict[shape_coord] = True # write values to db map_db = DbReader(":memory:") map_db( "CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL)" ) map_db( "CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)" ) map_db("BEGIN TRANSACTION") # add grass tiles for x, y in map_dict.iterkeys(): map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, GROUND.DEFAULT_LAND) def fill_tiny_spaces(tile): """Fills 1 tile gulfs and straits with the specified tile @param tile: ground tile to fill with """ neighbours = [(-1, 0), (0, -1), (0, 1), (1, 0)] corners = [(-1, -1), (-1, 1)] knight_moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)] bad_configs = set([ 0, 1 << 0, 1 << 1, 1 << 2, 1 << 3, (1 << 0) | (1 << 3), (1 << 1) | (1 << 2) ]) while True: to_fill = {} for x, y in map_dict.iterkeys(): for x_offset, y_offset in neighbours: x2 = x + x_offset y2 = y + y_offset if (x2, y2) in map_dict: continue # (x2, y2) is now a point just off the island neighbours_dirs = 0 for i in range(len(neighbours)): x3 = x2 + neighbours[i][0] y3 = y2 + neighbours[i][1] if (x3, y3) not in map_dict: neighbours_dirs |= (1 << i) if neighbours_dirs in bad_configs: # part of a straight 1 tile gulf to_fill[(x2, y2)] = True else: for x_offset, y_offset in corners: x3 = x2 + x_offset y3 = y2 + y_offset x4 = x2 - x_offset y4 = y2 - y_offset if (x3, y3) in map_dict and (x4, y4) in map_dict: # part of a diagonal 1 tile gulf to_fill[(x2, y2)] = True break # block 1 tile straits for x_offset, y_offset in knight_moves: x2 = x + x_offset y2 = y + y_offset if (x2, y2) not in map_dict: continue if abs(x_offset) == 1: y2 = y + y_offset / 2 if (x2, y2) in map_dict or (x, y2) in map_dict: continue else: x2 = x + x_offset / 2 if (x2, y2) in map_dict or (x2, y) in map_dict: continue to_fill[(x2, y2)] = True if to_fill: for x, y in to_fill.iterkeys(): map_dict[(x, y)] = tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) else: break # possible movement directions all_moves = { 'sw': (-1, -1), 'w': (-1, 0), 'nw': (-1, 1), 's': (0, -1), 'n': (0, 1), 'se': (1, -1), 'e': (1, 0), 'ne': (1, 1) } def get_island_outline(): """ @return: the points just off the island as a dict """ result = {} for x, y in map_dict.iterkeys(): for offset_x, offset_y in all_moves.itervalues(): coords = (x + offset_x, y + offset_y) if coords not in map_dict: result[coords] = True return result # add grass to sand tiles fill_tiny_spaces(GROUND.DEFAULT_LAND) outline = get_island_outline() for x, y in outline.iterkeys(): filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_dict: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.SAND_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.SAND_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.SAND_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.SAND_NORTH # sandy corner elif filled == ['se']: tile = GROUND.SAND_NORTHWEST1 elif filled == ['ne']: tile = GROUND.SAND_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.SAND_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.SAND_NORTHEAST1 # grassy corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.SAND_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.SAND_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.SAND_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.SAND_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) for coords, type in outline.iteritems(): map_dict[coords] = type # add sand to shallow water tiles fill_tiny_spaces(GROUND.SAND) outline = get_island_outline() for x, y in outline.iterkeys(): filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_dict: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.COAST_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.COAST_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.COAST_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.COAST_NORTH # mostly wet corner elif filled == ['se']: tile = GROUND.COAST_NORTHWEST1 elif filled == ['ne']: tile = GROUND.COAST_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.COAST_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.COAST_NORTHEAST1 # mostly dry corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.COAST_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.COAST_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.COAST_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.COAST_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) for coords, type in outline.iteritems(): map_dict[coords] = type # add shallow water to deep water tiles fill_tiny_spaces(GROUND.SHALLOW_WATER) outline = get_island_outline() for x, y in outline.iterkeys(): filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_dict: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.DEEP_WATER_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.DEEP_WATER_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.DEEP_WATER_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.DEEP_WATER_NORTH # mostly deep corner elif filled == ['se']: tile = GROUND.DEEP_WATER_NORTHWEST1 elif filled == ['ne']: tile = GROUND.DEEP_WATER_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.DEEP_WATER_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.DEEP_WATER_NORTHEAST1 # mostly shallow corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.DEEP_WATER_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.DEEP_WATER_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.DEEP_WATER_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.DEEP_WATER_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) map_db("COMMIT") return map_db
def create_random_island(id_string): """Creates a random island as sqlite db. It is rather primitive; it places shapes on the dict. The coordinates of tiles will be 0 <= x < width and 0 <= y < height @param id_string: random island id string @return: sqlite db reader containing island """ # NOTE: the tilesystem will be redone soon, so constants indicating grounds are temporary # here and will have to be changed anyways. match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed = [ long(i) for i in match_obj.groups() ] rand = random.Random(seed) map_set = set() # creation_method 0 - standard small island for the 3x3 grid # creation_method 1 - large island # creation_method 2 - a number of randomly sized and placed islands # place this number of shapes for i in xrange(15 + width * height / 45): # place shape determined by shape_id on (x, y) add = True rect_chance = 6 if creation_method == 0: shape_id = rand.randint(3, 5) elif creation_method == 1: shape_id = rand.randint(5, 8) elif creation_method == 2: shape_id = rand.randint(2, 8) rect_chance = 29 if rand.randint(0, 4) == 0: rect_chance = 13 add = False shape = None if rand.randint(1, rect_chance) == 1: # use a rect if add: x = rand.randint(8, width - 7) y = rand.randint(8, height - 7) if creation_method == 0: shape = Rect.init_from_topleft_and_size(x - 3, y - 3, 5, 5) elif creation_method == 1: shape = Rect.init_from_topleft_and_size(x - 5, y - 5, 8, 8) elif creation_method == 2: shape = Rect.init_from_topleft_and_size( x - 5, y - 5, rand.randint(2, 8), rand.randint(2, 8)) else: x = rand.randint(0, width) y = rand.randint(0, height) shape = Rect.init_from_topleft_and_size( x - 5, y - 5, rand.randint(2, 8), rand.randint(2, 8)) else: # use a circle, where radius is determined by shape_id radius = shape_id if not add and rand.randint(0, 6) < 5: x = rand.randint(-radius * 3 / 2, width + radius * 3 / 2) y = rand.randint(-radius * 3 / 2, height + radius * 3 / 2) shape = Circle(Point(x, y), shape_id) elif width - radius - 4 >= radius + 3 and height - radius - 4 >= radius + 3: x = rand.randint(radius + 3, width - radius - 4) y = rand.randint(radius + 3, height - radius - 4) shape = Circle(Point(x, y), shape_id) if shape: for shape_coord in shape.tuple_iter(): if add: map_set.add(shape_coord) elif shape_coord in map_set: map_set.discard(shape_coord) # write values to db map_db = DbReader(":memory:") map_db( "CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL)" ) map_db( "CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)" ) map_db("BEGIN TRANSACTION") # add grass tiles for x, y in map_set: map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, GROUND.DEFAULT_LAND) def fill_tiny_spaces(tile): """Fills 1 tile gulfs and straits with the specified tile @param tile: ground tile to fill with """ all_neighbours = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] neighbours = [(-1, 0), (0, -1), (0, 1), (1, 0)] corners = [(-1, -1), (-1, 1)] knight_moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)] bad_configs = set([ 0, 1 << 0, 1 << 1, 1 << 2, 1 << 3, (1 << 0) | (1 << 3), (1 << 1) | (1 << 2) ]) edge_set = copy.copy(map_set) reduce_edge_set = True while True: to_fill = set() to_ignore = set() for x, y in edge_set: # ignore the tiles with no empty neighbours if reduce_edge_set: is_edge = False for x_offset, y_offset in all_neighbours: if (x + x_offset, y + y_offset) not in map_set: is_edge = True break if not is_edge: to_ignore.add((x, y)) continue for x_offset, y_offset in neighbours: x2 = x + x_offset y2 = y + y_offset if (x2, y2) in map_set: continue # (x2, y2) is now a point just off the island neighbours_dirs = 0 for i in xrange(len(neighbours)): x3 = x2 + neighbours[i][0] y3 = y2 + neighbours[i][1] if (x3, y3) not in map_set: neighbours_dirs |= (1 << i) if neighbours_dirs in bad_configs: # part of a straight 1 tile gulf to_fill.add((x2, y2)) else: for x_offset, y_offset in corners: x3 = x2 + x_offset y3 = y2 + y_offset x4 = x2 - x_offset y4 = y2 - y_offset if (x3, y3) in map_set and (x4, y4) in map_set: # part of a diagonal 1 tile gulf to_fill.add((x2, y2)) break # block 1 tile straits for x_offset, y_offset in knight_moves: x2 = x + x_offset y2 = y + y_offset if (x2, y2) not in map_set: continue if abs(x_offset) == 1: y2 = y + y_offset / 2 if (x2, y2) in map_set or (x, y2) in map_set: continue else: x2 = x + x_offset / 2 if (x2, y2) in map_set or (x2, y) in map_set: continue to_fill.add((x2, y2)) # block diagonal 1 tile straits for x_offset, y_offset in corners: x2 = x + x_offset y2 = y + y_offset x3 = x + 2 * x_offset y3 = y + 2 * y_offset if (x2, y2) not in map_set and (x3, y3) in map_set: to_fill.add((x2, y2)) elif (x2, y2) in map_set and (x2, y) not in map_set and ( x, y2) not in map_set: to_fill.add((x2, y)) if to_fill: for x, y in to_fill: map_set.add((x, y)) map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) old_size = len(edge_set) edge_set = edge_set.difference(to_ignore).union(to_fill) reduce_edge_set = old_size - len(edge_set) > 50 else: break # possible movement directions all_moves = { 'sw': (-1, -1), 'w': (-1, 0), 'nw': (-1, 1), 's': (0, -1), 'n': (0, 1), 'se': (1, -1), 'e': (1, 0), 'ne': (1, 1) } def get_island_outline(): """ @return: the points just off the island as a dict """ result = set() for x, y in map_set: for offset_x, offset_y in all_moves.itervalues(): coords = (x + offset_x, y + offset_y) if coords not in map_set: result.add(coords) return result # add grass to sand tiles fill_tiny_spaces(GROUND.DEFAULT_LAND) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.SAND_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.SAND_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.SAND_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.SAND_NORTH # sandy corner elif filled == ['se']: tile = GROUND.SAND_NORTHWEST1 elif filled == ['ne']: tile = GROUND.SAND_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.SAND_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.SAND_NORTHEAST1 # grassy corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.SAND_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.SAND_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.SAND_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.SAND_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) map_set = map_set.union(outline) # add sand to shallow water tiles fill_tiny_spaces(GROUND.SAND) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.COAST_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.COAST_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.COAST_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.COAST_NORTH # mostly wet corner elif filled == ['se']: tile = GROUND.COAST_NORTHWEST1 elif filled == ['ne']: tile = GROUND.COAST_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.COAST_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.COAST_NORTHEAST1 # mostly dry corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.COAST_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.COAST_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.COAST_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.COAST_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) map_set = map_set.union(outline) # add shallow water to deep water tiles fill_tiny_spaces(GROUND.SHALLOW_WATER) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.DEEP_WATER_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.DEEP_WATER_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.DEEP_WATER_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.DEEP_WATER_NORTH # mostly deep corner elif filled == ['se']: tile = GROUND.DEEP_WATER_NORTHWEST1 elif filled == ['ne']: tile = GROUND.DEEP_WATER_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.DEEP_WATER_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.DEEP_WATER_NORTHEAST1 # mostly shallow corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.DEEP_WATER_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.DEEP_WATER_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.DEEP_WATER_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.DEEP_WATER_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) map_db("COMMIT") return map_db
def __init__(self, session, gui): super(IngameGui, self).__init__() self.session = session self.main_gui = gui self.widgets = {} self.tabwidgets = {} self.settlement = None self.resource_source = None self.resources_needed, self.resources_usable = {}, {} self._old_menu = None self.widgets = LazyWidgetsDict(self.styles, center_widgets=False) screenwidth = horizons.main.fife.engine_settings.getScreenWidth() cityinfo = self.widgets['city_info'] cityinfo.child_finder = PychanChildFinder(cityinfo) cityinfo.position = ( screenwidth/2 - cityinfo.size[0]/2 - 10, 5 ) self.logbook = LogBook(session) # self.widgets['minimap'] is the guichan gui around the actual minimap, # which is saved in self.minimap minimap = self.widgets['minimap'] minimap.position = (screenwidth - minimap.size[0] -20, 4) minimap.show() minimap_rect = Rect.init_from_topleft_and_size(minimap.position[0]+77, 55, 120, 120) self.minimap = Minimap(minimap_rect, self.session, \ self.session.view.renderer['GenericRenderer']) minimap.mapEvents({ 'zoomIn' : self.session.view.zoom_in, 'zoomOut' : self.session.view.zoom_out, 'rotateRight' : Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right), 'rotateLeft' : Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left), 'speedUp' : self.session.speed_up, 'speedDown' : self.session.speed_down }) minimap_overlay = minimap.findChild(name='minimap_overlay_image') self.minimap.use_overlay_icon(minimap_overlay) menupanel = self.widgets['menu_panel'] menupanel.position = (screenwidth - menupanel.size[0] +15, 149) menupanel.show() menupanel.mapEvents({ 'destroy_tool' : self.session.destroy_tool, 'build' : self.show_build_menu, 'helpLink' : self.main_gui.on_help, 'gameMenuButton' : self.main_gui.show_pause, 'logbook' : self.logbook.toggle_visibility }) self.widgets['tooltip'].hide() for w in ('status','status_extra','status_gold','status_extra_gold'): self.widgets[w].child_finder = PychanChildFinder(self.widgets[w]) self.widgets['status_gold'].show() self.message_widget = MessageWidget(self.session, \ cityinfo.position[0] + cityinfo.size[0], 5) self.resbar = ResBar(self.session, gui, self.widgets, self.resource_source) # map button names to build functions calls with the building id building_list = horizons.main.db.get_building_id_buttonname_settlerlvl() self.callbacks_build = {} for id,button_name,settler_level in building_list: if not settler_level in self.callbacks_build: self.callbacks_build[settler_level] = {} self.callbacks_build[settler_level][button_name] = Callback(self._build, id)
class Island(BuildingOwner, WorldObject): """The Island class represents an Island by keeping a list of all instances on the map, that belong to the island. The island variable is also set on every instance that belongs to an island, making it easy to determine to which island the instance belongs, when selected. An Island instance is created at map creation, when all tiles are added to the map. @param origin: Point instance - Position of the (0, 0) ground tile. @param filename: file from which the island is loaded. Each island holds some important attributes: * grounds - All grounds that belong to the island are referenced here. * grounds_map - a dictionary that binds tuples of coordinates with a reference to the tile: { (x, y): tileref, ...} This is important for pathfinding and quick tile fetching. * buildings - a list of all Building instances that are present on the island. * settlements - a list of all Settlement instances that are present on the island. * path_nodes - a special dictionary used by the pather to save paths. TUTORIAL: Why do we use a separate __init() function, and do not use the __init__() function? Simple, if we load the game, the class is not loaded as new instance, so the __init__ function is not called. Rather the load function is called. So everything that new classes and loaded classes share to initialize, comes into the __init() function. This is the common way of doing this in Unknown Horizons, so better get used to it :) To continue hacking, check out the __init() function now. """ log = logging.getLogger("world.island") def __init__(self, db, islandid, session): """ @param db: db instance with island table @param islandid: id of island in that table @param session: reference to Session instance """ super(Island, self).__init__(worldid=islandid) self.session = session x, y, filename = db( "SELECT x, y, file FROM island WHERE rowid = ? - 1000", islandid)[0] self.__init(Point(x, y), filename) # create building indexers self.building_indexers = {} self.building_indexers[BUILDINGS.TREE_CLASS] = BuildingIndexer( WildAnimal.walking_range, self, self.session.random) # load settlements for (settlement_id, ) in db( "SELECT rowid FROM settlement WHERE island = ?", islandid): Settlement.load(db, settlement_id, self.session) # load buildings from horizons.world import load_building for (building_worldid, building_typeid) in \ db("SELECT rowid, type FROM building WHERE location = ?", islandid): load_building(self.session, db, building_typeid, building_worldid) def __init(self, origin, filename): """ Load the actual island from a file @param origin: Point @param filename: String, filename of island db or random map id """ self.file = filename self.origin = origin # check if filename is a random map if random_map.is_random_island_id_string(filename): # it's a random map id, create this map and load it db = random_map.create_random_island(filename) else: db = DbReader( filename ) # Create a new DbReader instance to load the maps file. 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 ) in db("SELECT x, y, ground_id FROM ground"): # Load grounds ground = Entities.grounds[ground_id](self.session, self.origin.x + rel_x, self.origin.y + rel_y) # 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.settlements = [] self.wild_animals = [] 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: To continue hacking, you should now take off to the real fun stuff and check out horizons/world/building/__init__.py. """ def save(self, db): super(Island, self).save(db) db("INSERT INTO island (rowid, x, y, file) VALUES (? - 1000, ?, ?, ?)", self.worldid, self.origin.x, self.origin.y, self.file) for settlement in self.settlements: settlement.save(db, self.worldid) for animal in self.wild_animals: animal.save(db) def get_coordinates(self): """Returns list of coordinates, that are on the island.""" return self.ground_map.keys() def get_tile(self, point): """Returns whether a tile is on island or not. @param point: Point contains position of the tile. @return: tile instance if tile is on island, else None.""" try: return self.ground_map[(point.x, point.y)] except KeyError: return None def get_tile_tuple(self, tup): """Overloaded get_tile, takes a tuple as argument""" try: return self.ground_map[tup] except KeyError: return None def get_tiles_tuple(self, tuples): """Same as get_tile, but takes a list of tuples. @param tuples: iterable of tuples @return: list of tiles""" for tup in tuples: if tup in self.ground_map: yield self.ground_map[tup] def get_building(self, point): """Returns the building at the point @param point: position of the tile to look on @return: Building class instance or None if none is found. """ try: return self.ground_map[point.to_tuple()].object except KeyError: return None def get_settlement(self, point): """Look for a settlement on a specific tile @param point: Point to look on @return: Settlement at point, or None""" try: return self.get_tile(point).settlement # some tiles might be none, so we have to catch that error here except AttributeError: return None def get_settlements(self, rect, player=None): """Returns the list of settlements for the coordinates describing a rect. @param rect: Area to search for settlements @return: list of Settlement instances at that position.""" settlements = set() if self.rect.intersects(rect): for point in rect: try: if player is None or self.get_tile( point).settlement.owner == player: settlements.add(self.get_tile(point).settlement) except AttributeError: # some tiles don't have settlements, we don't explicitly check for them cause # its faster this way. pass settlements.discard( None) # None values might have been added, we don't want them return list(settlements) def add_settlement(self, position, radius, player): """Adds a settlement to the island at the position x, y with radius as area of influence. @param position: Rect describing the position of the new branch office @param radius: int radius of the area of influence. @param player: int id of the player that owns the settlement""" settlement = Settlement(self.session, player) self.add_existing_settlement(position, radius, settlement) # TODO: Move this to command, this message should not appear while loading self.session.ingame_gui.message_widget.add(position.center().x, \ position.center().y, \ 'NEW_SETTLEMENT', \ {'player':player.name}, \ self.session.world.player == player) self.session.world.notify_new_settlement() return settlement def add_existing_settlement(self, position, radius, settlement): """Same as add_settlement, but uses settlement from parameter. May also be called for extension of an existing settlement by a new building. (this is useful for loading, where every loaded building extends the radius of its settlement). @param position: Rect""" if settlement not in self.settlements: self.settlements.append(settlement) self.assign_settlement(position, radius, settlement) self.session.scenario_eventhandler.check_events( CONDITIONS.settlements_num_greater) return settlement def assign_settlement(self, position, radius, settlement): """Assigns the settlement property to tiles within the circle defined by \ position and radius. @param position: Rect @param radius: @param settlement: """ for coord in position.get_radius_coordinates(radius, include_self=True): tile = self.get_tile_tuple(coord) if tile is not None: if tile.settlement == settlement: continue if tile.settlement is None: tile.settlement = settlement settlement.ground_map[coord] = tile self.session.ingame_gui.minimap.update(coord) building = tile.object # assign buildings on tiles to settlement if building is not None and building.settlement is None and \ building.island == self: # don't steal from other islands building.settlement = settlement building.owner = settlement.owner settlement.add_building(building) #TODO: inherit resources etc def add_building(self, building, player): """Adds a building to the island at the position x, y with player as the owner. @param building: Building class instance of the building that is to be added. @param player: int id of the player that owns the settlement""" building = super(Island, self).add_building(building, player) for building.settlement in self.get_settlements( building.position, player): self.assign_settlement(building.position, building.radius, building.settlement) break if building.settlement is not None: building.settlement.add_building(building) building.init() if building.id in self.building_indexers: self.building_indexers[building.id].add(building) # Reset the tiles this building was covering for point in building.position: self.path_nodes.reset_tile_walkability(point.to_tuple()) return building def remove_building(self, building): # removal code (before super call) if building.settlement is not None: building.settlement.remove_building(building) assert (building not in building.settlement.buildings) super(Island, self).remove_building(building) if building.id in self.building_indexers: self.building_indexers[building.id].remove(building) # Reset the tiles this building was covering (after building has been completely removed) for point in building.position: self.path_nodes.reset_tile_walkability(point.to_tuple()) def get_building_index(self, resource_id): if resource_id == RES.WILDANIMALFOOD_ID: return self.building_indexers[BUILDINGS.TREE_CLASS] return None def get_surrounding_tiles(self, point, radius=1): """Returns tiles around point with specified radius. @param point: instance of Point""" for position in Circle(point, radius): tile = self.get_tile(position) if tile is not None: yield tile def get_tiles_in_radius(self, location, radius, include_self): """Returns tiles in radius of location. This is a generator. @param location: anything that supports get_radius_coordinates (usually Rect). @param include_self: bool, whether to include the coordinates in location """ for coord in location.get_radius_coordinates(radius, include_self): try: yield self.ground_map[coord] except KeyError: pass def __iter__(self): return self.ground_map.iterkeys() def check_wild_animal_population(self): """Creates a wild animal if they died out.""" self.log.debug("Checking wild animal population: %s", len(self.wild_animals)) if len(self.wild_animals) == 0: # find a tree where we can place it for building in self.buildings: if building.id == BUILDINGS.TREE_CLASS: point = building.position.origin Entities.units[UNITS.WILD_ANIMAL_CLASS]( self, x=point.x, y=point.y, session=self.session) return
def generate_map(seed=None): """Generates a whole map. @param seed: argument passed to random.seed @return filename to the sqlite db containing the new map""" rand = random.Random(seed) filename = tempfile.mkstemp()[1] shutil.copyfile(PATHS.SAVEGAME_TEMPLATE, filename) db = DbReader(filename) island_space = (35, 35) island_min_size = (25, 25) island_max_size = (28, 28) method = min(2, rand.randint( 0, 9)) # choose map creation method with 80% chance for method 2 if method == 0: # generate up to 9 islands number_of_islands = 0 for i in Rect.init_from_topleft_and_size(0, 0, 2, 2): if rand.randint(0, 2) != 0: # 2/3 chance for an island here number_of_islands = number_of_islands + 1 x = int(i.x * island_space[0] * (rand.random() / 6 + 0.90)) y = int(i.y * island_space[1] * (rand.random() / 6 + 0.90)) island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 0, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0], island_max_size[0]), \ 'height': rand.randint(island_min_size[1], island_max_size[1])} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) # if there is 1 or 0 islands created, it places 1 large island in the centre if number_of_islands == 0: x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif number_of_islands == 1: db("DELETE FROM island") x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif method == 1: # places 1 large island in the centre x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif method == 2: # tries to fill at most land_coefficient * 100% of the map with land map_width = 140 map_height = 140 min_island_size = 20 max_island_size = 65 max_islands = 20 min_space = 2 land_coefficient = max(0.3, min(0.6, rand.gauss(0.45, 0.07))) islands = [] estimated_land = 0 max_land_amount = map_width * map_height * land_coefficient for i in xrange(max_islands): size_modifier = 1.1 - 0.2 * estimated_land / float(max_land_amount) width = rand.randint(min_island_size - 5, max_island_size) width = max( min_island_size, min(max_island_size, int(round(width * size_modifier)))) coef = max(0.25, min(4, rand.gauss(1, 0.2))) height = max(min_island_size, min(int(round(width * coef)), max_island_size)) size = width * height if estimated_land + size > max_land_amount: continue for j in xrange(7): # try to place the island 7 times x = rand.randint(0, map_width - width) y = rand.randint(0, map_height - height) rect = Rect.init_from_topleft_and_size(x, y, width, height) blocked = False for existing_island in islands: if rect.distance(existing_island) < min_space: blocked = True break if blocked: continue island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 2, 'seed': island_seed, \ 'width': width, 'height': height} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) islands.append(rect) estimated_land += size break return filename
def __init__(self, session, gui): super(IngameGui, self).__init__() self.session = session self.main_gui = gui self.widgets = {} self.tabwidgets = {} self.settlement = None self.resource_source = None self.resources_needed, self.resources_usable = {}, {} self._old_menu = None self.widgets = LazyWidgetsDict(self.styles, center_widgets=False) cityinfo = self.widgets['city_info'] cityinfo.child_finder = PychanChildFinder(cityinfo) cityinfo.position_technique = "center-10:top+5" self.logbook = LogBook() self.logbook.add_pause_request_listener( Callback(self.session.speed_pause)) self.logbook.add_unpause_request_listener( Callback(self.session.speed_unpause)) self.scenario_chooser = ScenarioChooser(self.session) # self.widgets['minimap'] is the guichan gui around the actual minimap, # which is saved in self.minimap minimap = self.widgets['minimap'] minimap.position_technique = "right-20:top+4" minimap.show() minimap_rect = Rect.init_from_topleft_and_size( minimap.position[0] + 77, 55, 120, 120) self.minimap = Minimap(minimap_rect, self.session, \ self.session.view.renderer['GenericRenderer']) minimap.mapEvents({ 'zoomIn': self.session.view.zoom_in, 'zoomOut': self.session.view.zoom_out, 'rotateRight': Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right), 'rotateLeft': Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left), 'speedUp': self.session.speed_up, 'speedDown': self.session.speed_down }) minimap_overlay = minimap.findChild(name='minimap_overlay_image') self.minimap.use_overlay_icon(minimap_overlay) self.widgets['menu_panel'].position_technique = "right+15:top+153" self.widgets['menu_panel'].show() self.widgets['menu_panel'].mapEvents({ 'destroy_tool': self.session.destroy_tool, 'build': self.show_build_menu, 'helpLink': self.main_gui.on_help, 'gameMenuButton': self.main_gui.show_pause, 'logbook': self.logbook.toggle_visibility }) self.widgets['tooltip'].hide() self.widgets['status'].child_finder = PychanChildFinder( self.widgets['status']) self.widgets['status_extra'].child_finder = PychanChildFinder( self.widgets['status_extra']) self.message_widget = MessageWidget(self.session, \ cityinfo.position[0] + cityinfo.size[0], 5) self.widgets['status_gold'].show() self.widgets['status_gold'].child_finder = PychanChildFinder( self.widgets['status_gold']) self.widgets['status_extra_gold'].child_finder = PychanChildFinder( self.widgets['status_extra_gold']) # map button names to build functions calls with the building id callbackWithArguments = pychan.tools.callbackWithArguments self.callbacks_build = {} for id, button_name, settler_level in horizons.main.db.get_building_id_buttonname_settlerlvl( ): if not settler_level in self.callbacks_build: self.callbacks_build[settler_level] = {} self.callbacks_build[settler_level][button_name] = Callback( self._build, id)