def __create_game(self, load=None, chosen_map=None): """ Actually create a game, join it, and display the lobby. @param load: game data tuple for creating loaded games @param chosen_map: the name of the map to start a new game on (overrides the gui) """ # create the game if load: mapname, gamename = load path = SavegameManager.get_multiplayersave_map(mapname) maxplayers = SavegameAccessor.get_players_num(path) load = SavegameAccessor.get_hash(path) else: mapindex = None if chosen_map is not None: for i, map in enumerate(self.maps_display): if map == chosen_map: mapindex = i break if mapindex is None: mapindex = self.current.collectData('maplist') mapname = self.maps_display[mapindex] maxplayers = self.current.collectData('playerlimit') + 2 # 1 is the first entry gamename = self.current.collectData('gamename') load = None game = NetworkInterface().creategame(mapname, maxplayers, gamename, load) if game is None: return self.__show_gamelobby()
def __join_game(self, game=None): """Joins a multiplayer game. Displays lobby for that specific game""" if game == None: game = self.__get_selected_game() if ( game.load is not None and SavegameAccessor.get_hash(SavegameManager.get_multiplayersave_map(game.mapname)) != game.load ): self.show_popup(_("Error"), self.current.findChild(name="save_missing_help_button").btn_text, size=1) return if game.get_uuid() == -1: # -1 signals no game AmbientSoundComponent.play_special("error") return if game.get_version() != NetworkInterface().get_clientversion(): self.show_popup( _("Wrong version"), # xgettext:python-format _( "The game's version differs from your version. Every player in a multiplayer game must use the same version. This can be fixed by every player updating to the latest version. Game version: {game_version} Your version: {own_version}" ).format(game_version=game.get_version(), own_version=NetworkInterface().get_clientversion()), ) return # actual join join_worked = NetworkInterface().joingame(game.get_uuid()) if not join_worked: return self.__apply_new_nickname() self.__show_gamelobby()
def __update_game_details(self, game=None): """Set map name and other misc data in a widget. Only possible in certain states""" if game == None: game = self.__get_selected_game() # xgettext:python-format self.current.findChild(name="game_map").text = _("Map: {map_name}").format(map_name=game.get_map_name()) self.current.findChild(name="game_name").text = _("Name: {game_name}").format(game_name=game.get_name()) # xgettext:python-format self.current.findChild(name="game_playersnum").text = _("Players: {player_amount}/{player_limit}").format( player_amount=game.get_player_count(), player_limit=game.get_player_limit() ) creator_text = self.current.findChild(name="game_creator") # xgettext:python-format creator_text.text = _("Creator: {player}").format(player=game.get_creator()) creator_text.adaptLayout() vbox_inner = self.current.findChild(name="game_info") if game.load is not None: # work around limitations of current systems via messages path = SavegameManager.get_multiplayersave_map(game.mapname) if SavegameAccessor.get_hash(path) != game.load: text = "" btn_name = "save_missing_help_button" btn = vbox_inner.findChild(name=btn_name) if btn is None: btn = pychan.widgets.Button(name=btn_name, text=_("This savegame is missing (click here)")) last_elem = vbox_inner.findChild(name="game_info_last") if last_elem is None: # no last elem -> we are last vbox_inner.addChild(btn) else: vbox_inner.insertChildBefore(btn, last_elem) btn_text = ( _( u"For multiplayer load, it is currently necessary for you to ensure you have the correct savegame file." ) + u"\n" ) btn_text += _(u"This is not nice and we hope to offer a more convenient solution very soon.") + u"\n" btn_text += _( u"Meanwhile, please request the file {path} from the game creator and put it in {map_directory} ." ).format(path=os.path.basename(path), map_directory=os.path.dirname(path)) btn.btn_text = btn_text def show(): self.show_popup(_("Help"), btn_text, size=1) btn.capture(show) else: text = _(u"This is a savegame.") if text: self.current.findChild(name="game_isloaded").text = text textplayers = self.current.findChild(name="game_players") if textplayers is not None: textplayers.text = u", ".join(game.get_players()) vbox_inner.adaptLayout() # inner vbox always exists vbox = self.current.findChild(name="gamedetailsbox") if vbox is not None: vbox.adaptLayout()
def end(self, keep_map=False, remove_savegame=True): """ Clean up temporary files. """ super(SPTestSession, self).end() # Find all islands in the map first savegame_db = SavegameAccessor(self.savegame) if not keep_map: for (island_file, ) in savegame_db('SELECT file FROM island'): if not island_file.startswith('random:'): # random islands don't exist as files os.remove(island_file) # Finally remove savegame savegame_db.close() if remove_savegame: os.remove(self.savegame)
def __send_toggle_ready(self): game = NetworkInterface().get_game() if game.load is not None and \ SavegameAccessor.get_hash(SavegameManager.get_multiplayersave_map(game.mapname)) != game.load: self.__print_event_message("You are fetching savegame data. You must wait for it") return NetworkInterface().send_toggle_ready(NetworkInterface().get_client_name())
def load(self, savegame, players, is_scenario=False, campaign={}): """Loads a map. @param savegame: path to the savegame database. @param players: iterable of dictionaries containing id, name, color and local @param is_scenario: Bool whether the loaded map is a scenario or not """ if is_scenario: # savegame is a yaml file, that contains reference to actual map file self.scenario_eventhandler = ScenarioEventHandler(self, savegame) savegame = os.path.join(SavegameManager.maps_dir, \ self.scenario_eventhandler.get_map_file()) self.campaign = campaign self.log.debug("Session: Loading from %s", savegame) savegame_db = SavegameAccessor(savegame) # Initialize new dbreader try: # load how often the game has been saved (used to know the difference between # a loaded and a new game) self.savecounter = SavegameManager.get_metadata(savegame)['savecounter'] except KeyError: self.savecounter = 0 self.world = World(self) # Load horizons.world module (check horizons/world/__init__.py) self.world._init(savegame_db) self.view.load(savegame_db) # load view if not self.is_game_loaded(): # NOTE: this must be sorted before iteration, cause there is no defined order for # iterating a dict, and it must happen in the same order for mp games. for i in sorted(players): self.world.setup_player(i['id'], i['name'], i['color'], i['local']) center = self.world.init_new_world() self.view.center(center[0], center[1]) else: # try to load scenario data self.scenario_eventhandler.load(savegame_db) self.manager.load(savegame_db) # load the manager (there might me old scheduled ticks). self.ingame_gui.load(savegame_db) # load the old gui positions and stuff for instance_id in savegame_db("SELECT id FROM selected WHERE `group` IS NULL"): # Set old selected instance obj = WorldObject.get_object_by_id(instance_id[0]) self.selected_instances.add(obj) obj.select() for group in xrange(len(self.selection_groups)): # load user defined unit groups for instance_id in savegame_db("SELECT id FROM selected WHERE `group` = ?", group): self.selection_groups[group].add(WorldObject.get_object_by_id(instance_id[0])) # cursor has to be inited last, else player interacts with a not inited world with it. self.cursor = SelectionTool(self) self.cursor.apply_select() # Set cursor correctly, menus might need to be opened. assert hasattr(self.world, "player"), 'Error: there is no human player' """
def load(self, savegame, players): """ Stripped version of the original code. We don't need to load selections, or a scenario, setting up the gui or view. """ self.savegame = savegame self.savegame_db = SavegameAccessor(self.savegame) self.world = World(self) self.world._init(self.savegame_db) for i in sorted(players): self.world.setup_player(i['id'], i['name'], i['color'], i['local'], i['is_ai'], i['difficulty']) self.manager.load(self.savegame_db)
def __update_game_details(self, game=None): """Set map name and other misc data in a widget. Only possible in certain states""" if game == None: game = self.__get_selected_game() #xgettext:python-format self.current.findChild(name="game_map").text = _("Map: {map_name}").format(map_name=game.get_map_name()) self.current.findChild(name="game_name").text = _("Name: {game_name}").format(game_name=game.get_name()) #xgettext:python-format self.current.findChild(name="game_playersnum").text = _("Players: {player_amount}/{player_limit}").format( player_amount=game.get_player_count(), player_limit=game.get_player_limit()) vbox_inner = self.current.findChild(name="game_info") if game.load is not None: # work around limitations of current systems via messages path = SavegameManager.get_multiplayersave_map(game.mapname) btn_name = "save_missing_help_button" btn = vbox_inner.findChild(name=btn_name) if SavegameAccessor.get_hash(path) != game.load: text = "" if btn is None: btn = pychan.widgets.Button(name=btn_name, text=_("This savegame is missing (click here)")) last_elem = vbox_inner.findChild(name="game_info_last") if last_elem is None: # no last elem -> we are last vbox_inner.addChild( btn ) else: vbox_inner.insertChildBefore( btn, last_elem ) btn_text = _(u"For multiplayer load, it is necessary for you to have the correct savegame file.") + u"\n" btn_text += _(u"The file will be downloaded when you join the game.") btn.btn_text = btn_text def show(): self.show_popup(_("Help"), btn_text, size=1) btn.capture(show) else: text = _(u"This is a savegame.") if btn is not None: btn.hide() if text: self.current.findChild(name="game_isloaded").text = text self.__update_players_box(game) vbox_inner.adaptLayout() # inner vbox always exists vbox = self.current.findChild(name="gamedetailsbox") if vbox is not None: vbox.adaptLayout()
def __join_game(self, game=None): """Joins a multiplayer game. Displays lobby for that specific game""" if game == None: game = self.__get_selected_game() if game.load is not None and SavegameAccessor.get_hash(SavegameManager.get_multiplayersave_map(game.mapname)) != game.load: NetworkInterface().send_fetch_game(NetworkInterface().get_clientversion(), game.get_uuid()) if game.get_uuid() == -1: # -1 signals no game AmbientSoundComponent.play_special('error') return if game.get_version() != NetworkInterface().get_clientversion(): self.show_popup(_("Wrong version"), #xgettext:python-format _("The game's version differs from your version. Every player in a multiplayer game must use the same version. This can be fixed by every player updating to the latest version. Game version: {game_version} Your version: {own_version}").format( game_version=game.get_version(), own_version=NetworkInterface().get_clientversion())) return # actual join if game.password: self.__enter_password_dialog(game) else: self.__actual_join(game)
def load(self, savegame, players, trader_enabled, pirate_enabled, natural_resource_multiplier, is_scenario=False, campaign=None, force_player_id=None, disasters_enabled=True): """Loads a map. Key method for starting a game. @param savegame: path to the savegame database. @param players: iterable of dictionaries containing id, name, color, local, ai, and difficulty @param is_scenario: Bool whether the loaded map is a scenario or not @param force_player_id: the worldid of the selected human player or default if None (debug option) """ """ TUTORIAL: Here you see how the vital game elements (and some random things that are also required) are initialised """ if is_scenario: # savegame is a yaml file, that contains reference to actual map file self.scenario_eventhandler = ScenarioEventHandler(self, savegame) # scenario maps can be normal maps or scenario maps: map_filename = self.scenario_eventhandler.get_map_file() savegame = os.path.join(SavegameManager.scenario_maps_dir, map_filename) if not os.path.exists(savegame): savegame = os.path.join(SavegameManager.maps_dir, map_filename) self.campaign = {} if not campaign else campaign self.log.debug("Session: Loading from %s", savegame) savegame_db = SavegameAccessor(savegame) # Initialize new dbreader savegame_data = SavegameManager.get_metadata(savegame) # load how often the game has been saved (used to know the difference between # a loaded and a new game) self.savecounter = 0 if not 'savecounter' in savegame_data else savegame_data['savecounter'] if savegame_data.get('rng_state', None): rng_state_list = json.loads( savegame_data['rng_state'] ) # json treats tuples as lists, but we need tuples here, so convert back def rec_list_to_tuple(x): if isinstance(x, list): return tuple( rec_list_to_tuple(i) for i in x ) else: return x rng_state_tuple = rec_list_to_tuple(rng_state_list) # changing the rng is safe for mp, as all players have to have the same map self.random.setstate( rng_state_tuple ) self.world = World(self) # Load horizons.world module (check horizons/world/__init__.py) self.world._init(savegame_db, force_player_id, disasters_enabled=disasters_enabled) self.view.load(savegame_db) # load view if not self.is_game_loaded(): # NOTE: this must be sorted before iteration, cause there is no defined order for # iterating a dict, and it must happen in the same order for mp games. for i in sorted(players, lambda p1, p2: cmp(p1['id'], p2['id'])): self.world.setup_player(i['id'], i['name'], i['color'], i['local'], i['ai'], i['difficulty']) self.world.set_forced_player(force_player_id) center = self.world.init_new_world(trader_enabled, pirate_enabled, natural_resource_multiplier) self.view.center(center[0], center[1]) else: # try to load scenario data self.scenario_eventhandler.load(savegame_db) self.manager.load(savegame_db) # load the manager (there might me old scheduled ticks). self.world.init_fish_indexer() # now the fish should exist self.ingame_gui.load(savegame_db) # load the old gui positions and stuff for instance_id in savegame_db("SELECT id FROM selected WHERE `group` IS NULL"): # Set old selected instance obj = WorldObject.get_object_by_id(instance_id[0]) self.selected_instances.add(obj) obj.get_component(SelectableComponent).select() for group in xrange(len(self.selection_groups)): # load user defined unit groups for instance_id in savegame_db("SELECT id FROM selected WHERE `group` = ?", group): self.selection_groups[group].add(WorldObject.get_object_by_id(instance_id[0])) # cursor has to be inited last, else player interacts with a not inited world with it. self.current_cursor = 'default' self.cursor = SelectionTool(self) # Set cursor correctly, menus might need to be opened. # Open menus later, they may need unit data not yet inited self.cursor.apply_select() if self.is_game_loaded(): LastActivePlayerSettlementManager().load(savegame_db) Scheduler().before_ticking() savegame_db.close() assert hasattr(self.world, "player"), 'Error: there is no human player' """
class SPTestSession(SPSession): def __init__(self, db, rng_seed=None): """ Unfortunately, right now there is no other way to setup Dummy versions of the GUI, View etc., unless we want to patch the references in the session module. """ super(LivingObject, self).__init__() self.gui = Dummy() self.db = db self.savecounter = 0 # this is a new game. self.is_alive = True WorldObject.reset() NamedObject.reset() AIPlayer.clear_caches() # Game self.current_tick = 0 self.random = self.create_rng(rng_seed) self.timer = self.create_timer() Scheduler.create_instance(self.timer) ExtScheduler.create_instance(Dummy) self.manager = self.create_manager() self.view = Dummy() self.view.renderer = Dummy() Entities.load(self.db) self.scenario_eventhandler = Dummy() self.campaign = {} self.selected_instances = [] # GUI self.gui.session = self self.ingame_gui = Dummy() GAME_SPEED.TICKS_PER_SECOND = 16 def load(self, savegame, players): """ Stripped version of the original code. We don't need to load selections, or a scenario, setting up the gui or view. """ self.savegame = savegame self.savegame_db = SavegameAccessor(self.savegame) self.world = World(self) self.world._init(self.savegame_db) for i in sorted(players): self.world.setup_player(i['id'], i['name'], i['color'], i['local'], i['is_ai'], i['difficulty']) self.manager.load(self.savegame_db) def end(self): """ Clean up temporary files. """ super(SPTestSession, self).end() # Find all islands in the map first random_map = False for (island_file, ) in self.savegame_db('SELECT file FROM island'): if island_file[:7] != 'random:': # random islands don't exist as files os.remove(island_file) else: random_map = True break # Finally remove savegame self.savegame_db.close() if not random_map: os.remove(self.savegame) def run(self, ticks=1, seconds=None): """ Run the scheduler the given count of ticks or (in-game) seconds. Default is 1 tick, if seconds are passed, they will overwrite the tick count. """ if seconds: ticks = self.timer.get_ticks(seconds) for i in range(ticks): Scheduler().tick(self.current_tick) self.current_tick += 1
class SPTestSession(SPSession): def __init__(self, db, rng_seed=None): """ Unfortunately, right now there is no other way to setup Dummy versions of the GUI, View etc., unless we want to patch the references in the session module. """ super(LivingObject, self).__init__() self.gui = Dummy() self.db = db self.savecounter = 0 # this is a new game. self.is_alive = True WorldObject.reset() NamedComponent.reset() AIPlayer.clear_caches() # Game self.random = self.create_rng(rng_seed) self.timer = self.create_timer() Scheduler.create_instance(self.timer) ExtScheduler.create_instance(Dummy) self.manager = self.create_manager() self.view = Dummy() self.view.renderer = Dummy() Entities.load(self.db) self.scenario_eventhandler = Dummy() self.campaign = {} self.message_bus = MessageBus() self.status_icon_manager = StatusIconManager(self) # GUI self.gui.session = self self.ingame_gui = Dummy() LastActivePlayerSettlementManager.create_instance(self) self.selected_instances = set() self.selection_groups = [set()] * 10 # List of sets that holds the player assigned unit groups. GAME_SPEED.TICKS_PER_SECOND = 16 def save(self, *args, **kwargs): """ Wrapper around original save function to fix some things. """ # SavegameManager.write_metadata tries to create a screenshot and breaks when # accessing fife properties with mock.patch('horizons.spsession.SavegameManager'): # We need to covert Dummy() objects to a sensible value that can be stored # in the database with _dbreader_convert_dummy_objects(): return super(SPTestSession, self).save(*args, **kwargs) def load(self, savegame, players): """ Stripped version of the original code. We don't need to load selections, or a scenario, setting up the gui or view. """ self.savegame = savegame self.savegame_db = SavegameAccessor(self.savegame) self.savecounter = 1 self.world = World(self) self.world._init(self.savegame_db) for i in sorted(players): self.world.setup_player(i['id'], i['name'], i['color'], i['local'], i['is_ai'], i['difficulty']) self.manager.load(self.savegame_db) def end(self, keep_map=False, remove_savegame=True): """ Clean up temporary files. """ super(SPTestSession, self).end() # Find all islands in the map first if not keep_map: for (island_file, ) in self.savegame_db('SELECT file FROM island'): if island_file[:7] != 'random:': # random islands don't exist as files os.remove(island_file) self.savegame_db.close() # Finally remove savegame if remove_savegame: os.remove(self.savegame) @classmethod def cleanup(cls): """ If a test uses manual session management, we cannot be sure that session.end was called before a crash, leaving the game in an unclean state. This method should return the game to a valid state. """ Scheduler.destroy_instance() def run(self, ticks=1, seconds=None): """ Run the scheduler the given count of ticks or (in-game) seconds. Default is 1 tick, if seconds are passed, they will overwrite the tick count. """ if seconds: ticks = self.timer.get_ticks(seconds) for i in range(ticks): Scheduler().tick( Scheduler().cur_tick + 1 )