def disable(self): """Due to the way the minimap works, there isn't really a show/hide, but you can disable it with this and enable again with draw(). Stops all updates.""" ExtScheduler().rem_all_classinst_calls(self) if self.view is not None and self.view.has_change_listener(self.update_cam): self.view.remove_change_listener(self.update_cam) if self in self.__class__._instances: self.__class__._instances.remove(self)
def set_network_port(self, port): """Sets a new value for client network port""" # port is saved as string due to pychan limitations try: # 0 is not a valid port, but a valid value here (used for default) parse_port(port) except ValueError: headline = _("Invalid network port") descr = _( "The port you specified is not valid. It must be a number between 1 and 65535." ) advice = _( "Please check the port you entered and make sure it is in the specified range." ) horizons.main._modules.gui.show_error_popup( headline, descr, advice) # reset value and reshow settings dlg self.engine.set_uh_setting("NetworkPort", u"0") ExtScheduler().add_new_object(self._setting.onOptionsPress, self.engine, 0) else: # port is valid try: if NetworkInterface() is None: NetworkInterface.create_instance() NetworkInterface().network_data_changed() except Exception as e: headline = _(u"Failed to apply new network settings.") descr = _( "Network features could not be initialized with the current configuration." ) advice = _( "Check the settings you specified in the network section.") if 0 < parse_port(port) < 1024: #i18n This is advice for players seeing a network error with the current config advice += u" " + \ _("Low port numbers sometimes require special access privileges, try 0 or a number greater than 1024.") details = unicode(e) horizons.main._modules.gui.show_error_popup( headline, descr, advice, details) ExtScheduler().add_new_object(self._setting.onOptionsPress, self.engine, 0)
def _schedule_refresh(self): """Schedule a refresh soon, dropping all other refresh request, that appear until then. This saves a lot of CPU time, if you have a huge island, or play on high speed.""" if not self._refresh_scheduled: self._refresh_scheduled = True def unset_flag(): # set the flag here and not in refresh() since we can't be sure whether # refresh() of this class will be reached or a subclass will not call super() self._refresh_scheduled = False ExtScheduler().add_new_object(Callback.ChainedCallbacks(unset_flag, self.refresh), self, run_in=self.__class__.scheduled_update_delay)
def show(self): run_in = PLAYER.STATS_UPDATE_FREQUENCY / GAME_SPEED.TICKS_PER_SECOND ExtScheduler().add_new_object(Callback(self._refresh_tick), self, run_in=run_in, loops=-1) if not self._initialised: self._initialised = True self._init_gui() self.refresh() self._gui.show()
def start(self, interval, loops): """Starts the animation. @param interval: seconds @param loops: number of loops or -1 for infininte """ self.interval = interval self._next() ExtScheduler().add_new_object(self._next, self, run_in=interval, loops=loops)
def end(self): self.log.debug("Ending session") self.is_alive = False self.gui.session = None # Has to be done here, cause the manager uses Scheduler! self.end_production_finished_icon_manager() Scheduler().rem_all_classinst_calls(self) ExtScheduler().rem_all_classinst_calls(self) if horizons.globals.fife.get_fife_setting("PlaySounds"): for emitter in horizons.globals.fife.sound.emitter['ambient'][:]: emitter.stop() horizons.globals.fife.sound.emitter['ambient'].remove(emitter) horizons.globals.fife.sound.emitter['effects'].stop() horizons.globals.fife.sound.emitter['speech'].stop() if hasattr(self, "cursor"): # the line below would crash uglily on ^C self.cursor.remove() if hasattr(self, 'cursor') and self.cursor is not None: self.cursor.end() # these will call end() if the attribute still exists by the LivingObject magic self.ingame_gui = None # keep this before world LastActivePlayerSettlementManager().remove() # keep after ingame_gui LastActivePlayerSettlementManager.destroy_instance() self.cursor = None self.world.end() # must be called before the world ref is gone self.world = None self.keylistener = None self.view = None self.manager = None self.timer = None self.scenario_eventhandler = None Scheduler().end() Scheduler.destroy_instance() self.selected_instances = None self.selection_groups = None self.status_icon_manager.end() self.status_icon_manager = None horizons.main._modules.session = None self._clear_caches() # subscriptions shouldn't survive listeners (except the main Gui) self.gui.unsubscribe() AutosaveIntervalChanged.unsubscribe(self._on_autosave_interval_changed) MessageBus().reset() self.gui.subscribe()
def show_text(self, index): """Shows the text for a button. @param index: index of button""" assert isinstance(index, int) ExtScheduler().rem_call( self, self.hide_text) # stop hiding if a new text has been shown label = self.text_widget.findChild(name='text') label.text = unicode(self.active_messages[self.position + index].message) label.adaptLayout() self.text_widget.show()
def _apply_layout_hack(self): # pychan layouting depends on time, it's usually in a better mood later. # this introduces some flickering, but fixes #916 from horizons.extscheduler import ExtScheduler def do_apply_hack(): # just query widget when executing, since if lazy loading is used, the widget # does not exist yet in the outer function self.current_tab.widget.adaptLayout() ExtScheduler().add_new_object(do_apply_hack, self, run_in=0)
def set_settlement(self, settlement): """Sets the city name at top center of screen. Show/Hide is handled automatically """ if self._settlement: self._settlement.remove_change_listener(self._update_settlement) self._settlement = settlement if not settlement: # Hide the widget (after some seconds). # Delayed to allow players scrolling away to click on the widget. # This often happens when moving mouse up from island to reach it. ExtScheduler().add_new_object(self.hide, self, run_in=GUI.CITYINFO_UPDATE_DELAY) else: # Cancel previously scheduled hide if a settlement is hovered again. ExtScheduler().rem_call(self, self.hide) self._update_settlement() # This calls show()! settlement.add_change_listener(self._update_settlement)
def end(self): self._remove_listeners() self._remove_building_instances() self._remove_coloring() self._buildable_tiles = None self._modified_objects = None self.buildings = None if self.gui is not None: self.session.view.remove_change_listener(self.draw_gui) self.gui.hide() ExtScheduler().rem_all_classinst_calls(self) super(BuildingTool, self).end()
def _show_stats(self): """Show data below gold icon when balance label is clicked""" if self.stats_gui is None: self._init_stats_gui() self._update_stats() self.stats_gui.show() ExtScheduler().add_new_object(self._update_stats, self, run_in=Player.STATS_UPDATE_INTERVAL, loops=-1)
def end(self): self.set_inventory_instance( None, force_update=True ) self.current_instance = weakref.ref(self) ExtScheduler().rem_all_classinst_calls(self) self.resource_configurations.clear() self.hide() self.gold_gui = None self.gui = None self.stats_gui = None self._custom_default_resources = None NewPlayerSettlementHovered.unsubscribe(self._on_different_settlement) TabWidgetChanged.unsubscribe(self._on_tab_widget_changed)
def __init__(self, session): from horizons.session import Session assert isinstance(session, Session) self.session = session # special slot because of special properties self.gold_gui = load_uh_widget(self.__class__.GOLD_ENTRY_GUI_FILE, style=self.__class__.STYLE) self.gold_gui.balance_visible = False self.gold_gui.child_finder = PychanChildFinder(self.gold_gui) gold_icon = self.gold_gui.child_finder("res_icon") gold_icon.image = get_res_icon_path(RES.GOLD) gold_icon.max_size = gold_icon.min_size = gold_icon.size = (32, 32) self.gold_gui.mapEvents({ "resbar_gold_container/mouseClicked/stats": self._toggle_stats, }) self.gold_gui.helptext = T("Click to show statistics") self.stats_gui = None self.gui = [] # list of slots self.resource_configurations = weakref.WeakKeyDictionary() self.current_instance = weakref.ref(self) # can't weakref to None self.construction_mode = False self._last_build_costs = None self._do_show_dummy = False self._update_default_configuration() NewPlayerSettlementHovered.subscribe(self._on_different_settlement) TabWidgetChanged.subscribe(self._on_tab_widget_changed) # set now and then every few sec ExtScheduler().add_new_object(self._update_balance_display, self, run_in=0) ExtScheduler().add_new_object(self._update_balance_display, self, run_in=Player.STATS_UPDATE_INTERVAL, loops=-1)
def draw(self): """Recalculates and draws the whole minimap of self.session.world or world. The world you specified is reused for every operation until the next draw(). @param recalculate: do a full recalculation """ if self.world is None and self.session.world is not None: self.world = self.session.world # in case minimap has been constructed before the world self._update_world_to_minimap_ratio() if not self.world.inited: return # don't draw while loading self.__class__._instances.append(self) # update cam when view updates if self.view is not None and not self.view.has_change_listener( self.update_cam): self.view.add_change_listener(self.update_cam) if not hasattr(self, "icon"): # add to global generic renderer with id specific to this instance self.renderer.removeAll("minimap_image" + self._id) self.minimap_image.reset() # NOTE: this is for the generic renderer interface, the offrenderer has slightly different methods node = fife.RendererNode( fife.Point(self.location.center.x, self.location.center.y)) self.renderer.addImage("minimap_image" + self._id, node, self.minimap_image.image, False) else: # attach image to pychan icon (recommended) self.minimap_image.reset() self.icon.image = fife.GuiImage(self.minimap_image.image) self.update_cam() self._recalculate() if not self.preview: self._timed_update(force=True) ExtScheduler().rem_all_classinst_calls(self) ExtScheduler().add_new_object(self._timed_update, self, self.SHIP_DOT_UPDATE_INTERVAL, -1)
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 wait_for_task(): """ Continuously check whether the thread is done. """ if thread.is_alive(): ExtScheduler().add_new_object(wait_for_task, None) else: if result: gui = horizons.main._modules.gui window = VersionHint(gui.windows, result) gui.windows.open(window) else: # couldn't retrieve file or nothing relevant in there pass
def position_tooltip(self, event): if (event.getButton() == fife.MouseEvent.MIDDLE): return widget_position = self.getAbsolutePos() screen_width = horizons.main.fife.engine_settings.getScreenWidth() self.gui.y = widget_position[1] + event.getY() + 5 if (widget_position[0] + event.getX() +self.gui.size[0] + 10) <= screen_width: self.gui.x = widget_position[0] + event.getX() + 10 else: self.gui.x = widget_position[0] + event.getX() - self.gui.size[0] - 5 if not self.tooltip_shown: ExtScheduler().add_new_object(self.show_tooltip, self, run_in=0.3, loops=0) self.tooltip_shown = True else: self.gui.show()
def __init__(self): self.__setup_client() # cbs means callbacks self.cbs_game_details_changed = [] self.cbs_game_starts = [] self.cbs_p2p_ready = [] self.cbs_error = [] # cbs with 1 parameter which is an Exception instance self._client.register_callback("lobbygame_join", self._cb_game_details_changed) self._client.register_callback("lobbygame_leave", self._cb_game_details_changed) self._client.register_callback("lobbygame_starts", self._cb_game_starts) self._client.register_callback("p2p_ready", self._cb_p2p_ready) self._client.register_callback("p2p_data", self._cb_p2p_data) self.received_packets = [] ExtScheduler().add_new_object(self.ping, self, self.PING_INTERVAL, -1)
def _on_damage(self, caller=None): """Called when health has changed""" if not self._instance: # dead # it is sometimes hard to avoid this being called after the unit has died, # e.g. when it's part of a list of changelisteners, and one of the listeners executed before kills the unit return health_was_displayed_before = self._health_displayed # always update self.draw_health() if health_was_displayed_before: return # don't schedule removal # remember that it has been drawn automatically self._last_draw_health_call_on_damage = True # remove later (but only in case there's no manual interference) ExtScheduler().add_new_object(Callback(self.draw_health, auto_remove=True), self, self.__class__.AUTOMATIC_HEALTH_DISPLAY_TIMEOUT)
def start_single(self): """ Starts a single player horizons. """ assert self.current is self.widgets['singleplayermenu'] playername = self.current.playerdata.get_player_name() if not playername: self.show_popup(_("Invalid player name"), _("You entered an invalid playername.")) return playercolor = self.current.playerdata.get_player_color() self._save_player_name() if self.current.collectData('random'): map_file = self._get_random_map_file() else: assert self.active_right_side.collectData('maplist') != -1 map_file = self._get_selected_map() is_scenario = bool(self.current.collectData('scenario')) if not is_scenario: ai_players = int(self.current.aidata.get_ai_players()) horizons.globals.fife.set_uh_setting("AIPlayers", ai_players) horizons.globals.fife.save_settings() self.show_loading_screen() if is_scenario: try: options = StartGameOptions.create_start_scenario(map_file) options.set_human_data(playername, playercolor) horizons.main.start_singleplayer(options) except InvalidScenarioFileFormat as e: self._show_invalid_scenario_file_popup(e) self._select_single(show='scenario') else: # free play/random map options = StartGameOptions.create_start_map(map_file) options.set_human_data(playername, playercolor) options.ai_players = ai_players options.trader_enabled = self.widgets['game_settings'].findChild( name='free_trader').marked options.pirate_enabled = self.widgets['game_settings'].findChild( name='pirates').marked options.disasters_enabled = self.widgets[ 'game_settings'].findChild(name='disasters').marked options.natural_resource_multiplier = self._get_natural_resource_multiplier( ) horizons.main.start_singleplayer(options) ExtScheduler().rem_all_classinst_calls(self)
def _show_stats(self): """Show data below gold icon when balance label is clicked""" if self.stats_gui is None: reference_icon = self.gold_gui.child_finder("balance_background") self.stats_gui = load_uh_widget(self.__class__.STATS_GUI_FILE) self.stats_gui.child_finder = PychanChildFinder(self.stats_gui) self.stats_gui.position = (reference_icon.x + self.gold_gui.x, reference_icon.y + self.gold_gui.y) self.stats_gui.mapEvents({ 'resbar_stats_container/mouseClicked/stats': self._toggle_stats }) images = [ # these must correspond to the entries in _update_stats "content/gui/images/resbar_stats/expense.png", "content/gui/images/resbar_stats/income.png", "content/gui/images/resbar_stats/buy.png", "content/gui/images/resbar_stats/sell.png", "content/gui/images/resbar_stats/scales_icon.png", ] for num, image in enumerate(images): # keep in sync with comment there until we can use that data: # ./content/gui/xml/ingame/hud/resource_overview_bar_stats.xml box = HBox(padding=0, min_size=(70, 0), name="resbar_stats_line_%s" % num) box.addChild(Icon(image=image)) box.addSpacer(Spacer()) box.addChild(Label(name="resbar_stats_entry_%s" % num)) # workaround for fife font bug, probably http://fife.trac.cloudforge.com/engine/ticket/666 box.addChild(Label(text=u" ")) if num < len(images) - 1: # regular one self.stats_gui.child_finder("entries_box").addChild(box) else: # last one self.stats_gui.child_finder("bottom_box").addChild(box) self.stats_gui.child_finder("bottom_box").stylize( 'resource_bar') self._update_stats() self.stats_gui.show() ExtScheduler().add_new_object(self._update_stats, self, run_in=Player.STATS_UPDATE_INTERVAL, loops=-1)
def show_text(self, index): """Shows the text for a button. @param index: index of button""" assert isinstance(index, int) # stop hiding if a new text has been shown ExtScheduler().rem_call(self, self.hide_text) try: text = self.active_messages[index].message except IndexError: # Something went wrong, try to find out what. Also see #2273. self.log.error( u'Tried to access message at index %s, only have %s. Messages:', index, len(self.active_messages)) self.log.error(u'\n'.join( unicode(m) for m in self.active_messages)) text = (u'Error trying to access message!\n' u'Please report a bug. Thanks!') text = text.replace(r'\n', self.CHARS_PER_LINE * ' ') text = text.replace('[br]', self.CHARS_PER_LINE * ' ') text = textwrap.fill(text, self.CHARS_PER_LINE) self.bg_middle = self.text_widget.findChild(name='msg_bg_middle') self.bg_middle.removeAllChildren() line_count = len(text.splitlines()) - 1 for i in xrange(line_count * self.LINE_HEIGHT // self.IMG_HEIGHT): middle_icon = Icon(image=self.BG_IMAGE_MIDDLE) self.bg_middle.addChild(middle_icon) button = self.widget.findChild(name=str(index)) # y position relative to parent button_y = button.position[1] # Show text next to corresponding icon x, y = self.reference_text_widget_position self.text_widget.position = (x, y + button_y) message_container = self.text_widget.findChild(name='message') message_container.size = (300, 21 + self.IMG_HEIGHT * line_count + 21) self.bg_middle.adaptLayout() label = self.text_widget.findChild(name='text') label.text = text label.adaptLayout() self.text_widget.show()
def __init__(self, session, x, y): super(LivingObject, self).__init__() self.session = session self.x_pos, self.y_pos = x, y self.active_messages = [] # for displayed messages self.archive = [] # messages, that aren't displayed any more self.widget = load_xml_translated('hud_messages.xml') self.widget.position = ( 5, horizons.main.fife.engine_settings.getScreenHeight()/2 - self.widget.size[1]/2) self.text_widget = load_xml_translated('hud_messages_text.xml') self.text_widget.position = (self.widget.x + self.widget.width, self.widget.y) self.widget.show() self.current_tick = 0 self.position = 0 # number of current message ExtScheduler().add_new_object(self.tick, self, loops=-1)
def position_tooltip(self, event): """Calculates a nice position for the tooltip. @param event: mouse event from fife or tuple screenpoint """ # TODO: think about nicer way of handling the polymorphism here, # e.g. a position_tooltip_event and a position_tooltip_tuple where = event # fife forces this to be called event, but here it can also be a tuple if isinstance(where, tuple): x, y = where else: if where.getButton() == fife.MouseEvent.MIDDLE: return x, y = where.getX(), where.getY() if self.gui is None: self.__init_gui() widget_position = self.getAbsolutePos() # Sometimes, we get invalid events from pychan, it is probably related to changing the # gui when the mouse hovers on gui elements. # Random tests have given evidence to believe that pychan indicates invalid events # by setting the top container's position to 0, 0. # Since this position is currently unused, it can serve as invalid flag, # and dropping these events seems to lead to the desired placements def get_top(w): return get_top(w.parent) if w.parent else w top_pos = get_top(self).position if top_pos == (0, 0): return screen_width = horizons.globals.fife.engine_settings.getScreenWidth() self.gui.y = widget_position[1] + y + 5 offset = x + 10 if (widget_position[0] + self.gui.size[0] + offset) > screen_width: # right screen edge, position to the left of cursor instead offset = x - self.gui.size[0] - 5 self.gui.x = widget_position[0] + offset if not self.tooltip_shown: ExtScheduler().add_new_object(self.show_tooltip, self, run_in=0.3, loops=0) self.tooltip_shown = True
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 _add_message(self, message, sound = None): """Internal function for adding messages. Do not call directly. @param message: Message instance @param sound: path tosoundfile""" self.active_messages.insert(0, message) if len(self.active_messages) > self.MAX_MESSAGES: self.active_messages.remove(self.active_messages[self.MAX_MESSAGES]) if sound: horizons.main.fife.play_sound('speech', sound) else: # play default msg sound AmbientSound.play_special('message') self.draw_widget() self.show_text(0) ExtScheduler().add_new_object(self.hide_text, self, self.SHOW_NEW_MESSAGE_TEXT)
def high(i=0): i += 1 render_name = self._get_render_name("highlight")+str(tup) self.minimap_image.set_drawing_enabled() self.minimap_image.rendertarget.removeAll(render_name) if i > STEPS: if finish_callback: finish_callback() return part = i # grow bigger if i > STEPS // 2: # after the first half part = STEPS-i # become smaller radius = MIN_RAD + int(( float(part) / (STEPS // 2) ) * (MAX_RAD - MIN_RAD) ) for x, y in Circle( Point(*tup), radius=radius ).get_border_coordinates(): self.minimap_image.rendertarget.addPoint(render_name, fife.Point(x, y), *color) ExtScheduler().add_new_object(lambda : high(i), self, INTERVAL, loops=1)
def remove(self): self.session.ingame_gui.resource_overview.close_construction_mode() WorldObjectDeleted.unsubscribe(self._on_worldobject_deleted) self._remove_listeners() self._remove_building_instances() self._remove_coloring() self._build_logic.remove(self.session) self._buildable_tiles = None self._transparencified_instances = None self._related_buildings_selected_tiles = None self._related_buildings = None self._highlighted_buildings = None self._build_logic = None self.buildings = None if self.__class__.gui is not None: self.__class__.gui.hide() ExtScheduler().rem_all_classinst_calls(self) SettlementInventoryUpdated.discard(self.update_preview) PlayerInventoryUpdated.discard(self.update_preview) super(BuildingTool, self).remove()
def __init__(self, session): super(MessageWidget, self).__init__() self.session = session self.active_messages = [] # for displayed messages self.archive = [] # messages, that aren't displayed any more self.chat = [] # chat messages sent by players self.msgcount = itertools.count() # sort to preserve order after loading self.widget = load_uh_widget(self.ICON_TEMPLATE) screenheight = horizons.globals.fife.engine_settings.getScreenHeight() self.widget.position = (5, (screenheight // 2) - (self.widget.size[1] // 2)) self.text_widget = load_uh_widget(self.MSG_TEMPLATE) self.reference_text_widget_position = (self.widget.x + self.widget.width, self.widget.y) self.widget.show() ExtScheduler().add_new_object(self.tick, self, loops=-1) # buttons to toggle through messages self._last_message = {} # used to detect fast subsequent messages in add() self.draw_widget()
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"")