class RandomMapWidget(object): """Create a random map, influence map generation with multiple sliders.""" map_sizes = [50, 100, 150, 200, 250] water_percents = [20, 30, 40, 50, 60, 70, 80] island_sizes = [30, 40, 50, 60, 70] island_size_deviations = [5, 10, 20, 30, 40] def __init__(self, windows, singleplayer_menu, aidata): self._windows = windows self._singleplayer_menu = singleplayer_menu self._aidata = aidata self._gui = load_uh_widget('sp_random.xml') self._map_parameters = {} # stores the current values from the sliders self._game_settings = GameSettingsWidget() # Map preview self._last_map_parameters = None self._preview_process = None self._preview_output = None self._map_preview = None def end(self): if self._preview_process: self._preview_process.kill() self._preview_process = None ExtScheduler().rem_all_classinst_calls(self) def get_widget(self): return self._gui def act(self, player_name, player_color): self.end() map_file = generate_random_map(*self._get_map_parameters()) options = StartGameOptions.create_start_map(map_file) options.set_human_data(player_name, player_color) options.ai_players = self._aidata.get_ai_players() options.trader_enabled = self._game_settings.free_trader options.pirate_enabled = self._game_settings.pirates options.disasters_enabled = self._game_settings.disasters options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier horizons.main.start_singleplayer(options) def show(self): seed_string_field = self._gui.findChild(name='seed_string_field') seed_string_field.capture(self._on_random_parameter_changed) seed_string_field.text = generate_random_seed(seed_string_field.text) parameters = ( ('map_size', self.map_sizes, _('Map size:'), 'RandomMapSize'), ('water_percent', self.water_percents, _('Water:'), 'RandomMapWaterPercent'), ('max_island_size', self.island_sizes, _('Max island size:'), 'RandomMapMaxIslandSize'), ('preferred_island_size', self.island_sizes, _('Preferred island size:'), 'RandomMapPreferredIslandSize'), ('island_size_deviation', self.island_size_deviations, _('Island size deviation:'), 'RandomMapIslandSizeDeviation'), ) for param, __, __, setting_name in parameters: self._map_parameters[param] = horizons.globals.fife.get_uh_setting(setting_name) def make_on_change(param, values, text, setting_name): # When a slider is changed, update the value displayed in the label, save the value # in the settings and store the value in self._map_parameters def on_change(): slider = self._gui.findChild(name=param + '_slider') self._gui.findChild(name=param + '_lbl').text = text + u' ' + unicode(values[int(slider.value)]) horizons.globals.fife.set_uh_setting(setting_name, slider.value) horizons.globals.fife.save_settings() self._on_random_parameter_changed() self._map_parameters[param] = values[int(slider.value)] return on_change for param, values, text, setting_name in parameters: slider = self._gui.findChild(name=param + '_slider') on_change = make_on_change(param, values, text, setting_name) slider.capture(on_change) slider.value = horizons.globals.fife.get_uh_setting(setting_name) on_change() self._gui.findChild(name='game_settings_box').addChild(self._game_settings.get_widget()) self._game_settings.show() self._aidata.show() def _get_map_parameters(self): return ( self._gui.findChild(name='seed_string_field').text, self._map_parameters['map_size'], self._map_parameters['water_percent'], self._map_parameters['max_island_size'], self._map_parameters['preferred_island_size'], self._map_parameters['island_size_deviation'] ) def _on_random_parameter_changed(self): self._update_map_preview() # Map preview def _on_preview_click(self, event, drag): seed_string_field = self._gui.findChild(name='seed_string_field') seed_string_field.text = generate_random_seed(seed_string_field.text) self._on_random_parameter_changed() def _update_map_preview(self): """Start a new process to generate a map preview.""" current_parameters = self._get_map_parameters() if self._last_map_parameters == current_parameters: # nothing changed, don't generate a new preview return self._last_map_parameters = current_parameters if self._preview_process: self._preview_process.kill() # process exists, therefore up is scheduled already # launch process in background to calculate minimap data minimap_icon = self._gui.findChild(name='map_preview_minimap') params = json.dumps(((minimap_icon.width, minimap_icon.height), current_parameters)) args = [sys.executable, sys.argv[0], "--generate-minimap", params] # We're running UH in a new process, make sure fife is setup correctly if horizons.main.command_line_arguments.fife_path: args.extend(["--fife-path", horizons.main.command_line_arguments.fife_path]) handle, self._preview_output = tempfile.mkstemp() os.close(handle) self._preview_process = subprocess.Popen(args=args, stdout=open(self._preview_output, "w")) self._set_map_preview_status(u"Generating preview…") ExtScheduler().add_new_object(self._poll_preview_process, self, 0.5) def _poll_preview_process(self): """This will be called regularly to see if the process ended. If the process has not yet finished, schedule a new callback to this function. Otherwise use the data to update the minimap. """ if not self._preview_process: return self._preview_process.poll() if self._preview_process.returncode is None: # not finished ExtScheduler().add_new_object(self._poll_preview_process, self, 0.1) return elif self._preview_process.returncode != 0: self._preview_process = None self._set_map_preview_status(u"An unknown error occurred while generating the map preview") return with open(self._preview_output, 'r') as f: data = f.read() os.unlink(self._preview_output) self._preview_process = None if self._map_preview: self._map_preview.end() self._map_preview = Minimap( self._gui.findChild(name='map_preview_minimap'), session=None, view=None, world=None, targetrenderer=horizons.globals.fife.targetrenderer, imagemanager=horizons.globals.fife.imagemanager, cam_border=False, use_rotation=False, tooltip=_("Click to generate a different random map"), on_click=self._on_preview_click, preview=True) self._map_preview.draw_data(data) self._set_map_preview_status(u"") def _set_map_preview_status(self, text): self._gui.findChild(name="map_preview_status_label").text = text
class MapPreview(object): """Semiprivate class dealing with the map preview icon""" def __init__(self, get_widget): """ @param get_widget: returns the current widget (self.current) """ self.minimap = None self.calc_proc = None # handle to background calculation process self.get_widget = get_widget def update_map(self, map_file): """Direct map preview update. Only use for existing maps, it's too slow for random maps""" if self.minimap is not None: self.minimap.end() world = self._load_raw_world(map_file) self.minimap = Minimap(self._get_map_preview_icon(), session=None, view=None, world=world, targetrenderer=horizons.main.fife.targetrenderer, imagemanager=horizons.main.fife.imagemanager, cam_border=False, use_rotation=False, tooltip=None, on_click=None, preview=True) self.minimap.draw() def update_random_map(self, map_params, on_click): """Called when a random map parameter has changed. @param map_params: _get_random_map() output @param on_click: handler for clicks""" def check_calc_process(): # checks up on calc process (see below) if self.calc_proc is not None: state = self.calc_proc.poll() if state is None: # not finished ExtScheduler().add_new_object(check_calc_process, self, 0.1) elif state != 0: self._set_map_preview_status(u"An unknown error occured while generating the map preview") else: # done data = open(self.calc_proc.output_filename, "r").read() os.unlink(self.calc_proc.output_filename) self.calc_proc = None icon = self._get_map_preview_icon() if icon is None: return # dialog already gone tooltip = _("Click to generate a different random map") if self.minimap is not None: self.minimap.end() self.minimap = Minimap(icon, session=None, view=None, world=None, targetrenderer=horizons.main.fife.targetrenderer, imagemanager=horizons.main.fife.imagemanager, cam_border=False, use_rotation=False, tooltip=tooltip, on_click=on_click, preview=True) self.minimap.draw_data( data ) icon.show() self._set_map_preview_status(u"") if self.calc_proc is not None: self.calc_proc.kill() # process exists, therefore up is scheduled already else: ExtScheduler().add_new_object(check_calc_process, self, 0.5) # launch process in background to calculate minimap data minimap_icon = self._get_map_preview_icon() params = json.dumps(((minimap_icon.width, minimap_icon.height), map_params)) args = (sys.executable, sys.argv[0], "--generate-minimap", params) handle, outfilename = tempfile.mkstemp() os.close(handle) self.calc_proc = subprocess.Popen(args=args, stdout=open(outfilename, "w")) self.calc_proc.output_filename = outfilename # attach extra info self._set_map_preview_status(u"Generating preview...") @classmethod 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() @classmethod def _load_raw_world(cls, map_file): WorldObject.reset() world = World(session=None) world.inited = True world.load_raw_map( SavegameAccessor( map_file ), preview=True ) return world def _get_map_preview_icon(self): """Returns pychan icon for map preview""" return self.get_widget().findChild(name="map_preview_minimap") def _set_map_preview_status(self, text): """Sets small status label next to map preview""" wdg = self.get_widget().findChild(name="map_preview_status_label") if wdg: # might show next dialog already wdg.text = text
class RandomMapWidget: """Create a random map, influence map generation with multiple sliders.""" def __init__(self, windows, singleplayer_menu, aidata): self._windows = windows self._singleplayer_menu = singleplayer_menu self._aidata = aidata self._gui = load_uh_widget('sp_random.xml') self._map_parameters = {} # stores the current values from the sliders self._game_settings = GameSettingsWidget() # Map preview self._last_map_parameters = None self._preview_process = None self._preview_output = None self._map_preview = None def end(self): if self._preview_process: self._preview_process.kill() self._preview_process = None ExtScheduler().rem_all_classinst_calls(self) def get_widget(self): return self._gui def act(self, player_name, player_color): self.end() map_file = generate_random_map(*self._get_map_parameters()) options = StartGameOptions.create_start_map(map_file) options.set_human_data(player_name, player_color) options.ai_players = self._aidata.get_ai_players() options.trader_enabled = self._game_settings.free_trader options.pirate_enabled = self._game_settings.pirates options.disasters_enabled = self._game_settings.disasters options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier horizons.main.start_singleplayer(options) def show(self): seed_string_field = self._gui.findChild(name='seed_string_field') seed_string_field.capture(self._on_random_parameter_changed) seed_string_field.text = generate_random_seed(seed_string_field.text) parameters = ( ('map_size', T('Map size:'), 'RandomMapSize'), ('water_percent', T('Water:'), 'RandomMapWaterPercent'), ('max_island_size', T('Max island size:'), 'RandomMapMaxIslandSize'), ('preferred_island_size', T('Preferred island size:'), 'RandomMapPreferredIslandSize'), ('island_size_deviation', T('Island size deviation:'), 'RandomMapIslandSizeDeviation'), ) for param, __, setting_name in parameters: self._map_parameters[param] = int( horizons.globals.fife.get_uh_setting(setting_name)) def make_on_change(param, text, setting_name): # When a slider is changed, update the value displayed in the label, save the value # in the settings and store the value in self._map_parameters def on_change(): slider = self._gui.findChild(name=param + '_slider') self._gui.findChild( name=param + '_lbl').text = text + ' ' + str(int(slider.value)) horizons.globals.fife.set_uh_setting(setting_name, slider.value) horizons.globals.fife.save_settings() self._map_parameters[param] = int(slider.value) self._on_random_parameter_changed() return on_change for param, text, setting_name in parameters: slider = self._gui.findChild(name=param + '_slider') on_change = make_on_change(param, text, setting_name) slider.capture(on_change) slider.value = horizons.globals.fife.get_uh_setting(setting_name) on_change() self._gui.findChild(name='game_settings_box').addChild( self._game_settings.get_widget()) self._game_settings.show() self._aidata.show() def _get_map_parameters(self): return (self._gui.findChild(name='seed_string_field').text, self._map_parameters['map_size'], self._map_parameters['water_percent'], self._map_parameters['max_island_size'], self._map_parameters['preferred_island_size'], self._map_parameters['island_size_deviation']) def _on_random_parameter_changed(self): self._update_map_preview() # Map preview def _on_preview_click(self, event, drag): seed_string_field = self._gui.findChild(name='seed_string_field') seed_string_field.text = generate_random_seed(seed_string_field.text) self._on_random_parameter_changed() def _update_map_preview(self): """Start a new process to generate a map preview.""" current_parameters = self._get_map_parameters() if self._last_map_parameters == current_parameters: # nothing changed, don't generate a new preview return self._last_map_parameters = current_parameters if self._preview_process: self._preview_process.kill( ) # process exists, therefore up is scheduled already # launch process in background to calculate minimap data minimap_icon = self._gui.findChild(name='map_preview_minimap') params = json.dumps( ((minimap_icon.width, minimap_icon.height), current_parameters)) args = [sys.executable, sys.argv[0], "--generate-minimap", params] # We're running UH in a new process, make sure fife is setup correctly if horizons.main.command_line_arguments.fife_path: args.extend([ "--fife-path", horizons.main.command_line_arguments.fife_path ]) handle, self._preview_output = tempfile.mkstemp() os.close(handle) self._preview_process = subprocess.Popen(args=args, stdout=open( self._preview_output, "w")) self._set_map_preview_status("Generating preview…") ExtScheduler().add_new_object(self._poll_preview_process, self, 0.5) def _poll_preview_process(self): """This will be called regularly to see if the process ended. If the process has not yet finished, schedule a new callback to this function. Otherwise use the data to update the minimap. """ if not self._preview_process: return self._preview_process.poll() if self._preview_process.returncode is None: # not finished ExtScheduler().add_new_object(self._poll_preview_process, self, 0.1) return elif self._preview_process.returncode != 0: self._preview_process = None self._set_map_preview_status( "An unknown error occurred while generating the map preview") return with open(self._preview_output, 'r') as f: data = f.read() # Sometimes the subprocess outputs more then the minimap data, e.g. debug # information. Since we just read from its stdout, parse out the data that # is relevant to us. data = re.findall(r'^DATA (\[\[.*\]\]) ENDDATA$', data, re.MULTILINE)[0] data = json.loads(data) os.unlink(self._preview_output) self._preview_process = None if self._map_preview: self._map_preview.end() self._map_preview = Minimap( self._gui.findChild(name='map_preview_minimap'), session=None, view=None, world=None, targetrenderer=horizons.globals.fife.targetrenderer, imagemanager=horizons.globals.fife.imagemanager, cam_border=False, use_rotation=False, tooltip=T("Click to generate a different random map"), on_click=self._on_preview_click, preview=True) self._map_preview.draw_data(data) self._set_map_preview_status("") def _set_map_preview_status(self, text): self._gui.findChild(name="map_preview_status_label").text = text
class MapPreview(object): """Semiprivate class dealing with the map preview icon""" def __init__(self, get_widget): """ @param get_widget: returns the current widget (self.current) """ self.minimap = None self.calc_proc = None # handle to background calculation process self.get_widget = get_widget self._last_random_map_params = None def update_map(self, map_file): """Direct map preview update. Only use for existing maps, it's too slow for random maps""" if self.minimap is not None: self.minimap.end() world = self._load_raw_world(map_file) self.minimap = Minimap( self._get_map_preview_icon(), session=None, view=None, world=world, targetrenderer=horizons.globals.fife.targetrenderer, imagemanager=horizons.globals.fife.imagemanager, cam_border=False, use_rotation=False, tooltip=None, on_click=None, preview=True) self.minimap.draw() def update_random_map(self, map_params, on_click): """Called when a random map parameter has changed. @param map_params: _get_random_map() output @param on_click: handler for clicks""" if self._last_random_map_params == map_params: return # we already display this, happens on spurious slider events such as hover self._last_random_map_params = map_params def check_calc_process(): # checks up on calc process (see below) if self.calc_proc is not None: state = self.calc_proc.poll() if state is None: # not finished ExtScheduler().add_new_object(check_calc_process, self, 0.1) elif state != 0: self._set_map_preview_status( u"An unknown error occured while generating the map preview" ) else: # done data = open(self.calc_proc.output_filename, "r").read() os.unlink(self.calc_proc.output_filename) self.calc_proc = None icon = self._get_map_preview_icon() if icon is None: return # dialog already gone tooltip = _("Click to generate a different random map") if self.minimap is not None: self.minimap.end() self.minimap = Minimap( icon, session=None, view=None, world=None, targetrenderer=horizons.globals.fife.targetrenderer, imagemanager=horizons.globals.fife.imagemanager, cam_border=False, use_rotation=False, tooltip=tooltip, on_click=on_click, preview=True) self.minimap.draw_data(data) icon.show() self._set_map_preview_status(u"") if self.calc_proc is not None: self.calc_proc.kill( ) # process exists, therefore up is scheduled already else: ExtScheduler().add_new_object(check_calc_process, self, 0.5) # launch process in background to calculate minimap data minimap_icon = self._get_map_preview_icon() params = json.dumps( ((minimap_icon.width, minimap_icon.height), map_params)) args = (sys.executable, sys.argv[0], "--generate-minimap", params) handle, outfilename = tempfile.mkstemp() os.close(handle) self.calc_proc = subprocess.Popen(args=args, stdout=open(outfilename, "w")) self.calc_proc.output_filename = outfilename # attach extra info self._set_map_preview_status(u"Generating preview...") @classmethod 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() @classmethod def _load_raw_world(cls, map_file): WorldObject.reset() world = World(session=None) world.inited = True world.load_raw_map(SavegameAccessor(map_file, True), preview=True) return world def _get_map_preview_icon(self): """Returns pychan icon for map preview""" return self.get_widget().findChild(name="map_preview_minimap") def _set_map_preview_status(self, text): """Sets small status label next to map preview""" wdg = self.get_widget().findChild(name="map_preview_status_label") if wdg: # might show next dialog already wdg.text = text