# coding= utf-8 from __future__ import absolute_import #from builtins import map, filter import datetime import time import random import math #import tkinter as tk import Tkinter as tk import ttk import ttkHyperlinkLabel import myNotebook as notebook from config import config from edrconfig import EDRConfig from lrucache import LRUCache from edentities import EDPlanetaryLocation, EDFineOrBounty, EDLocation from edrserver import EDRServer, CommsJammedError from audiofeedback import AudioFeedback from edrlog import EDRLog from ingamemsg import InGameMsg from edrtogglingpanel import EDRTogglingPanel from edrsystems import EDRSystems from edrresourcefinder import EDRResourceFinder from edrbodiesofinterest import EDRBodiesOfInterest from edrcmdrs import EDRCmdrs from edropponents import EDROpponents from randomtips import RandomTips from helpcontent import HelpContent from edtime import EDTime from edrlegalrecords import EDRLegalRecords from edrxzibit import EDRXzibit from edri18n import _, _c, _edr, set_language from clippy import copy EDRLOG = EDRLog() class EDRClient(object): AUDIO_FEEDBACK = AudioFeedback() def __init__(self): edr_config = EDRConfig() set_language(config.get("language")) self.edr_version = edr_config.edr_version() EDRLOG.log(u"Version {}".format(self.edr_version), "INFO") self.enemy_alerts_pledge_threshold = edr_config.enemy_alerts_pledge_threshold() self.system_novelty_threshold = edr_config.system_novelty_threshold() self.place_novelty_threshold = edr_config.place_novelty_threshold() self.ship_novelty_threshold = edr_config.ship_novelty_threshold() self.cognitive_novelty_threshold = edr_config.cognitive_novelty_threshold() self.intel_even_if_clean = edr_config.intel_even_if_clean() self.edr_needs_u_novelty_threshold = edr_config.edr_needs_u_novelty_threshold() self.previous_ad = None self.searching = False self.blips_cache = LRUCache(edr_config.lru_max_size(), edr_config.blips_max_age()) self.cognitive_blips_cache = LRUCache(edr_config.lru_max_size(), edr_config.blips_max_age()) self.traffic_cache = LRUCache(edr_config.lru_max_size(), edr_config.traffic_max_age()) self.scans_cache = LRUCache(edr_config.lru_max_size(), edr_config.scans_max_age()) self.cognitive_scans_cache = LRUCache(edr_config.lru_max_size(), edr_config.blips_max_age()) self.alerts_cache = LRUCache(edr_config.lru_max_size(), edr_config.alerts_max_age()) self.fights_cache = LRUCache(edr_config.lru_max_size(), edr_config.fights_max_age()) self._email = tk.StringVar(value=config.get("EDREmail")) self._password = tk.StringVar(value=config.get("EDRPassword")) # Translators: this is shown on the EDMC's status line self._status = tk.StringVar(value=_(u"not authenticated.")) visual = 1 if config.get("EDRVisualFeedback") == "True" else 0 self.IN_GAME_MSG = InGameMsg() if visual else None self._visual_feedback = tk.IntVar(value=visual) visual_alt = 1 if config.get("EDRVisualAltFeedback") == "True" else 0 self._visual_alt_feedback = tk.IntVar(value=visual_alt) self.ui = EDRTogglingPanel(self._status, self._visual_alt_feedback) audio = 1 if config.get("EDRAudioFeedback") == "True" else 0 self._audio_feedback = tk.IntVar(value=audio) self.server = EDRServer() anonymous_reports = _(u"Auto") self.server.anonymous_reports = None if config.get("EDRRedactMyInfo") in [_(u"Always"), _(U"Never")]: anonymous_reports = config.get("EDRRedactMyInfo") self.server.anonymous_reports = anonymous_reports == _(u"Always") self._anonymous_reports = tk.StringVar(value=anonymous_reports) self.realtime_params = { EDROpponents.OUTLAWS: { "min_bounty": None if config.get("EDROutlawsAlertsMinBounty") == "None" else config.getint("EDROutlawsAlertsMinBounty"), "max_distance": None if config.get("EDROutlawsAlertsMaxDistance") == "None" else config.getint("EDROutlawsAlertsMaxDistance")}, EDROpponents.ENEMIES: { "min_bounty": None if config.get("EDREnemiesAlertsMinBounty") == "None" else config.getint("EDREnemiesAlertsMinBounty"), "max_distance": None if config.get("EDREnemiesAlertsMaxDistance") == "None" else config.getint("EDREnemiesAlertsMaxDistance")} } self.edrsystems = EDRSystems(self.server) self.edrresourcefinder = EDRResourceFinder(self.edrsystems) self.edrcmdrs = EDRCmdrs(self.server) self.edropponents = { EDROpponents.OUTLAWS: EDROpponents(self.server, EDROpponents.OUTLAWS, self._realtime_callback), EDROpponents.ENEMIES: EDROpponents(self.server, EDROpponents.ENEMIES, self._realtime_callback), } self.edrlegal = EDRLegalRecords(self.server) self.mandatory_update = False self.autoupdate_pending = False self.crimes_reporting = True self.motd = [] self.tips = RandomTips() self.help_content = HelpContent() self._throttle_until_timestamp = None self.ui.notify(_(u"Troubleshooting"), [_(u"If the overlay doesn't show up, try one of the following:"), _(u" - In Elite: go to graphics options, and select Borderless or Windowed."), _(" - With Elite and EDR launched, check that EDMCOverlay.exe is running in the task manager"), _(u"If the overlay hurts your FPS, try turning VSYNC off in Elite's graphics options."), u"----", _("Join https://edrecon.com/discord for further technical support.")]) def loud_audio_feedback(self): config.set("EDRAudioFeedbackVolume", "loud") self.AUDIO_FEEDBACK.loud() # Translators: this is shown on EDMC's status bar when a user enables loud audio cues self.status = _(u"loud audio cues.") def soft_audio_feedback(self): config.set("EDRAudioFeedbackVolume", "soft") self.AUDIO_FEEDBACK.soft() # Translators: this is shown on EDMC's status bar when a user enables soft audio cues self.status = _(u"soft audio cues.") def apply_config(self): c_email = config.get("EDREmail") c_password = config.get("EDRPassword") c_visual_feedback = config.get("EDRVisualFeedback") c_visual_alt_feedback = config.get("EDRVisualAltFeedback") c_audio_feedback = config.get("EDRAudioFeedback") c_audio_volume = config.get("EDRAudioFeedbackVolume") c_redact_my_info = config.get("EDRRedactMyInfo") if c_email is None: self._email.set("") else: self._email.set(c_email) if c_password is None: self._password.set("") else: self._password.set(c_password) if c_visual_feedback is None or c_visual_feedback == "False": self._visual_feedback.set(0) else: self._visual_feedback.set(1) if c_visual_alt_feedback is None or c_visual_alt_feedback == "False": self._visual_alt_feedback.set(0) else: self._visual_alt_feedback.set(1) if c_audio_feedback is None or c_audio_feedback == "False": self._audio_feedback.set(0) else: self._audio_feedback.set(1) if c_audio_volume is None or c_audio_volume == "loud": self.loud_audio_feedback() else: self.soft_audio_feedback() if c_redact_my_info is None: self.anonymous_reports = _(u"Auto") elif c_redact_my_info in [_(u"Always"), _(u"Never")]: self.anonymous_reports = c_redact_my_info def check_version(self): version_range = self.server.server_version() self.motd = _edr(version_range["l10n_motd"]) if version_range is None: # Translators: this is shown on EDMC's status bar when the version check fails self.status = _(u"check for version update has failed.") return if self.is_obsolete(version_range["min"]): EDRLOG.log(u"Mandatory update! {version} vs. {min}" .format(version=self.edr_version, min=version_range["min"]), "ERROR") self.mandatory_update = True self.autoupdate_pending = version_range.get("autoupdatable", False) self.__status_update_pending() elif self.is_obsolete(version_range["latest"]): EDRLOG.log(u"EDR update available! {version} vs. {latest}" .format(version=self.edr_version, latest=version_range["latest"]), "INFO") self.mandatory_update = False self.autoupdate_pending = version_range.get("autoupdatable", False) self.__status_update_pending() def is_obsolete(self, advertised_version): client_parts = map(int, self.edr_version.split('.')) advertised_parts = map(int, advertised_version.split('.')) return client_parts < advertised_parts @property def player(self): return self.edrcmdrs.player @property def email(self): return self._email.get() @email.setter def email(self, new_email): self._email.set(new_email) @property def password(self): return self._password.get() @password.setter def password(self, new_password): self._password.set(new_password) @property def status(self): return self._status.get() @status.setter def status(self, new_status): self._status.set(new_status) self.ui.nolink() def linkable_status(self, link, new_status = None): #TODO verify if this needs to be truncated self._status.set(new_status if new_status else link) self.ui.link(link) @property def visual_feedback(self): if self._visual_feedback.get() == 0: return False if not self.IN_GAME_MSG: self.IN_GAME_MSG = InGameMsg() return True @visual_feedback.setter def visual_feedback(self, new_value): self._visual_feedback.set(new_value) @property def visual_alt_feedback(self): return self._visual_alt_feedback.get() == 1 @visual_alt_feedback.setter def visual_alt_feedback(self, new_value): self._visual_alt_feedback.set(new_value) @property def audio_feedback(self): return self._audio_feedback.get() == 1 @audio_feedback.setter def audio_feedback(self, new_value): self._audio_feedback.set(new_value) @property def anonymous_reports(self): return self._anonymous_reports.get() @anonymous_reports.setter def anonymous_reports(self, new_value): self._anonymous_reports.set(new_value) if new_value is None or new_value == _(u"Auto"): self.server.anonymous_reports = None elif new_value in [_(u"Always"), _(u"Never")]: self.server.anonymous_reports = (new_value == _(u"Always")) def player_name(self, name): self.edrcmdrs.set_player_name(name) self.server.set_player_name(name) def game_mode(self, mode, group = None): self.player.game_mode = mode self.player.private_group = group self.server.set_game_mode(mode, group) def pledged_to(self, power, time_pledged=0): if self.server.is_anonymous(): EDRLOG.log(u"Skipping pledged_to call since the user is anonymous.", "INFO") return nodotpower = power.replace(".", "") if power else None if self.edrcmdrs.player_pledged_to(nodotpower, time_pledged): for kind in self.edropponents: self.edropponents[kind].pledged_to(nodotpower, time_pledged) def login(self): self.server.logout() if self.server.login(self.email, self.password): # Translators: this is shown on EDMC's status bar when the authentication succeeds self.status = _(u"authenticated (guest).") if self.is_anonymous() else _(u"authenticated.") return True # Translators: this is shown on EDMC's status bar when the authentication fails self.status = _(u"not authenticated.") return False def is_logged_in(self): return self.server.is_authenticated() def is_anonymous(self): return (self.is_logged_in() and self.server.is_anonymous()) def warmup(self): EDRLOG.log(u"Warming up client.", "INFO") # Translators: this is shown when EDR warms-up via the overlay details = [_(u"Check that Elite still has the focus!")] if self.mandatory_update: # Translators: this is shown when EDR warms-up via the overlay if there is a mandatory update pending details = [_(u"Mandatory update!")] details += self.motd # Translators: this is shown when EDR warms-up via the overlay, the -- are for presentation purpose details.append(_(u"-- Feeling lost? Send !help via the in-game chat --")) details.append(self.tips.tip()) # Translators: this is shown when EDR warms-up via the overlay self.__notify(_(u"EDR v{} by LeKeno").format(self.edr_version), details, clear_before=True) def shutdown(self, everything=False): self.edrcmdrs.persist() self.player.persist() self.edrsystems.persist() self.edrresourcefinder.persist() for kind in self.edropponents: self.edropponents[kind].persist() self.edropponents[kind].shutdown_comms_link() self.edrlegal.persist() if self.IN_GAME_MSG: self.IN_GAME_MSG.shutdown() config.set("EDRVisualAltFeedback", "True" if self.visual_alt_feedback else "False") if not everything: return self.server.logout() def app_ui(self, parent): self.check_version() return self.ui def prefs_ui(self, parent): frame = notebook.Frame(parent) frame.columnconfigure(1, weight=1) # Translators: this is shown in the preferences panel ttkHyperlinkLabel.HyperlinkLabel(frame, text=_(u"EDR website"), background=notebook.Label().cget('background'), url="https://edrecon.com", underline=True).grid(padx=10, sticky=tk.W) ttkHyperlinkLabel.HyperlinkLabel(frame, text=_(u"EDR community"), background=notebook.Label().cget('background'), url="https://edrecon.com/discord", underline=True).grid(padx=10, sticky=tk.W) # Translators: this is shown in the preferences panel notebook.Label(frame, text=_(u'Credentials')).grid(padx=10, sticky=tk.W) ttk.Separator(frame, orient=tk.HORIZONTAL).grid(columnspan=2, padx=10, pady=2, sticky=tk.EW) # Translators: this is shown in the preferences panel cred_label = notebook.Label(frame, text=_(u'Log in with your EDR account for full access (https://edrecon.com/account)')) cred_label.grid(padx=10, columnspan=2, sticky=tk.W) notebook.Label(frame, text=_(u"Email")).grid(padx=10, row=11, sticky=tk.W) notebook.Entry(frame, textvariable=self._email).grid(padx=10, row=11, column=1, sticky=tk.EW) notebook.Label(frame, text=_(u"Password")).grid(padx=10, row=12, sticky=tk.W) notebook.Entry(frame, textvariable=self._password, show=u'*').grid(padx=10, row=12, column=1, sticky=tk.EW) notebook.Label(frame, text=_(u'Sitrep Broadcasts')).grid(padx=10, row=14, sticky=tk.W) ttk.Separator(frame, orient=tk.HORIZONTAL).grid(columnspan=2, padx=10, pady=2, sticky=tk.EW) notebook.Label(frame, text=_("Redact my info")).grid(padx=10, row = 16, sticky=tk.W) choices = { _(u'Auto'),_(u'Always'),_(u'Never')} popupMenu = notebook.OptionMenu(frame, self._anonymous_reports, self.anonymous_reports, *choices) popupMenu.grid(padx=10, row=16, column=1, sticky=tk.EW) popupMenu["menu"].configure(background="white", foreground="black") if self.server.is_authenticated(): if self.is_anonymous(): self.status = _(u"authenticated (guest).") else: self.status = _(u"authenticated.") else: self.status = _(u"not authenticated.") # Translators: this is shown in the preferences panel as a heading for feedback options (e.g. overlay, audio cues) notebook.Label(frame, text=_(u"EDR Feedback:")).grid(padx=10, row=17, sticky=tk.W) ttk.Separator(frame, orient=tk.HORIZONTAL).grid(columnspan=2, padx=10, pady=2, sticky=tk.EW) notebook.Checkbutton(frame, text=_(u"Overlay"), variable=self._visual_feedback).grid(padx=10, row=19, sticky=tk.W) notebook.Checkbutton(frame, text=_(u"Sound"), variable=self._audio_feedback).grid(padx=10, row=20, sticky=tk.W) return frame def __status_update_pending(self): # Translators: this is shown in EDMC's status if self.autoupdate_pending: self.status = _(u"mandatory update pending (relaunch EDMC)") if self.mandatory_update else _(u"update pending (relaunch EDMC to apply)") else: # Translators: this is shown in EDMC's status status = _(u"mandatory EDR update!") if self.mandatory_update else _(u"please update EDR!") link = "https://edrecon.com/latest" self.linkable_status(link, status) def prefs_changed(self): set_language(config.get("language")) if self.mandatory_update: EDRLOG.log(u"Out-of-date client, aborting.", "ERROR") self.__status_update_pending() return config.set("EDREmail", self.email) config.set("EDRPassword", self.password) config.set("EDRVisualFeedback", "True" if self.visual_feedback else "False") config.set("EDRAudioFeedback", "True" if self.audio_feedback else "False") config.set("EDRRedactMyInfo", self.anonymous_reports) EDRLOG.log(u"Audio cues: {}, {}".format(config.get("EDRAudioFeedback"), config.get("EDRAudioFeedbackVolume")), "DEBUG") EDRLOG.log(u"Anonymous reports: {}".format(config.get("EDRRedactMyInfo")), "DEBUG") self.login() def noteworthy_about_system(self, fsdjump_event): if fsdjump_event["SystemSecurity"]: self.player.location_security(fsdjump_event["SystemSecurity"]) self.edrsystems.system_id(fsdjump_event['StarSystem'], may_create=True, coords=fsdjump_event.get("StarPos", None)) facts = self.edrresourcefinder.assess_jump(fsdjump_event, self.player.inventory) header = _('Rare materials in {} (USS-HGE/EE, Mission Rewards)'.format(fsdjump_event['StarSystem'])) if not facts: facts = EDRBodiesOfInterest.bodies_of_interest(fsdjump_event['StarSystem']) header = _('Noteworthy stellar bodies in {}').format(fsdjump_event['StarSystem']) if not facts: if self.player.in_bad_neighborhood(): header = _(u"Anarchy system") facts = [_(u"Crimes will not be reported.")] else: return False self.__notify(header, facts, clear_before = True) return True def noteworthy_about_body(self, star_system, body_name): pois = EDRBodiesOfInterest.points_of_interest(star_system, body_name) if pois: facts = [poi["title"] for poi in pois] self.__notify(_(u'Noteworthy about {}: {} sites').format(body_name, len(facts)), facts, clear_before = True) return True materials_info = self.edrsystems.materials_on(star_system, body_name) facts = self.edrresourcefinder.assess_materials_density(materials_info, self.player.inventory) if facts: self.__notify(_(u'Noteworthy material densities on {}').format(body_name), facts, clear_before = True) def noteworthy_about_scan(self, scan_event): if scan_event["event"] != "Scan" or scan_event["ScanType"] != "Detailed": return if "Materials" not in scan_event or "BodyName" not in scan_event: return facts = self.edrresourcefinder.assess_materials_density(scan_event["Materials"], self.player.inventory) if facts: self.__notify(_(u'Noteworthy about {}').format(scan_event["BodyName"]), facts, clear_before = True) def noteworthy_about_signal(self, fss_event): facts = self.edrresourcefinder.assess_signal(fss_event, self.player.location, self.player.inventory) if not facts: return False header = _(u'Signal Insights (potential outcomes)') self.__notify(header, facts, clear_before = True) return True def process_scan(self, scan_event): if "Materials" not in scan_event: return False self.edrsystems.materials_info(self.player.star_system, scan_event["BodyName"], scan_event["Materials"]) def closest_poi_on_body(self, star_system, body_name, attitude): body = self.edrsystems.body(self.player.star_system, self.player.place) radius = body.get("radius", None) if body else None return EDRBodiesOfInterest.closest_point_of_interest(star_system, body_name, attitude, radius) def navigation(self, latitude, longitude): position = {"latitude": float(latitude), "longitude": float(longitude)} loc = EDPlanetaryLocation(position) if loc.valid(): self.player.planetary_destination = loc self.__notify(_(u'Assisted Navigation'), [_(u"Destination set to {} | {}").format(latitude, longitude), _(u"Guidance will be shown when approaching a stellar body")], clear_before = True) else: self.player.planetary_destination = None self.__notify(_(u'Assisted Navigation'), [_(u"Invalid destination")], clear_before = True) def docking_guidance(self, entry): if entry["event"] == "DockingGranted": station = self.edrsystems.station(self.player.star_system, entry["StationName"], entry["StationType"]) summary = self.IN_GAME_MSG.docking(self.player.star_system, station, entry["LandingPad"]) # TODO only if visual feedback allowed but then summary is missing... if summary: self.ui.notify(summary["header"], summary["body"]) else: self.IN_GAME_MSG.clear_docking() def show_navigation(self): current = self.player.piloted_vehicle.attitude destination = self.player.planetary_destination if not destination or not current: return if not current.valid() or not destination.valid(): return bearing = destination.bearing(current) body = self.edrsystems.body(self.player.star_system, self.player.place) radius = body.get("radius", None) if body else None distance = destination.distance(current, radius) if radius else None if distance <= 1.0: return pitch = destination.pitch(current, distance) if distance and distance <= 700 else None if self.visual_feedback: self.IN_GAME_MSG.navigation(bearing, destination, distance, pitch) self.status = _(u"> {:03} < for Lat:{:.4f} Lon:{:.4f}".format(bearing, destination.latitude, destination.longitude)) def check_system(self, star_system, may_create=False, coords=None): try: EDRLOG.log(u"Check system called: {}".format(star_system), "INFO") details = [] notams = self.edrsystems.active_notams(star_system, may_create, coords) if notams: EDRLOG.log(u"NOTAMs for {}: {}".format(star_system, notams), "DEBUG") details += notams if self.edrsystems.has_sitrep(star_system): if star_system == self.player.star_system and self.player.in_bad_neighborhood(): EDRLOG.log(u"Sitrep system is known to be an anarchy. Crimes aren't reported.", "INFO") # Translators: this is shown via the overlay if the system of interest is an Anarchy (current system or !sitrep <system>) details.append(_c(u"Sitrep|Anarchy: not all crimes are reported.")) if self.edrsystems.has_recent_activity(star_system): summary = self.edrsystems.summarize_recent_activity(star_system, self.player.power) for section in summary: details.append(u"{}: {}".format(section, "; ".join(summary[section]))) if details: # Translators: this is the heading for the sitrep of a given system {}; shown via the overlay header = _(u"SITREP for {}") if self.player.in_open() else _(u"SITREP for {} (Open)") self.__sitrep(header.format(star_system), details) except CommsJammedError: self.__commsjammed() def mining_guidance(self): if self.visual_feedback: self.IN_GAME_MSG.mining_guidance(self.player.mining_stats) self.status = _(u"[Yield: {:.2f}%] [LTD: {} ({:.0f}/hour)]".format(self.player.mining_stats.last["proportion"], self.player.mining_stats.refined_nb, self.player.mining_stats.ltd_per_hour())) def notams(self): summary = self.edrsystems.systems_with_active_notams() if summary: details = [] # Translators: this shows a ist of systems {} with active NOtice To Air Men via the overlay details.append(_(u"Active NOTAMs for: {}").format("; ".join(summary))) # Translators: this is the heading for the active NOTAMs overlay self.__sitrep(_(u"NOTAMs"), details) else: self.__sitrep(_(u"NOTAMs"), [_(u"No active NOTAMs.")]) def notam(self, star_system): summary = self.edrsystems.active_notams(star_system) if summary: EDRLOG.log(u"NOTAMs for {}: {}".format(star_system, summary), "DEBUG") # Translators: this is the heading to show any active NOTAM for a given system {} self.__sitrep(_(u"NOTAM for {}").format(star_system), summary) else: self.__sitrep(_(u"NOTAM for {}").format(star_system), [_(u"No active NOTAMs.")]) def sitreps(self): try: details = [] summary = self.edrsystems.systems_with_recent_activity() for section in summary: details.append(u"{}: {}".format(section, "; ".join(summary[section]))) if details: header = _(u"SITREPS") if self.player.in_open() else _(u"SITREPS (Open)") self.__sitrep(header, details) except CommsJammedError: self.__commsjammed() def cmdr_id(self, cmdr_name): try: profile = self.cmdr(cmdr_name, check_inara_server=False) if not (profile is None or profile.cid is None): EDRLOG.log(u"Cmdr {cmdr} known as id={cid}".format(cmdr=cmdr_name, cid=profile.cid), "DEBUG") return profile.cid EDRLOG.log(u"Failed to retrieve/create cmdr {}".format(cmdr_name), "ERROR") return None except CommsJammedError: self.__commsjammed() return None def cmdr(self, cmdr_name, autocreate=True, check_inara_server=False): try: return self.edrcmdrs.cmdr(cmdr_name, autocreate, check_inara_server) except CommsJammedError: self.__commsjammed() return None def eval_build(self, eval_type): canonical_commands = ["power"] synonym_commands = ["priority", "pp", "priorities"] supported_commands = set(canonical_commands + synonym_commands) if eval_type not in supported_commands: self.__notify(_(u"EDR Evals"), [_(u"Yo dawg, I don't do evals for '{}'").format(eval_type), _(u"Try {} instead.").format(", ".join(canonical_commands))], clear_before=True) return vehicle = self.player.mothership if not vehicle.modules: self.__notify(_(u"Basic Power Assessment"), [_(u"Yo dawg, U sure that you got modules on this?")], clear_before=True) return if vehicle.module_info_timestamp and vehicle.slots_timestamp < vehicle.module_info_timestamp: self.__notify(_(u"Basic Power Assessment"), [_(u"Yo dawg, the info I got from FDev might be stale."), _(u"Try again later after a bunch of random actions."), _(u"Or try this: relog, look at your modules, try again.")], clear_before=True) return build_master = EDRXzibit(vehicle) assessment = build_master.assess_power_priorities() if not assessment: self.__notify(_(u"Basic Power Assessment"), [_(u"Yo dawg, sorry but I can't help with dat.")], clear_before=True) return formatted_assessment = [] grades = [u'F', u'E', u'D', u'C', u'B-', u'B', u'B+', u'A-', u'A', u'A+'] for fraction in sorted(assessment): grade = grades[int(assessment[fraction]["grade"]*(len(grades)-1))] powered = _(u"⚡: {}").format(assessment[fraction]["annotation"]) if assessment[fraction]["annotation"] else u"" formatted_assessment.append(_(u"{}: {}\t{}").format(grade, assessment[fraction]["situation"], powered)) recommendation = _(u" ⚑: {}").format(assessment[fraction]["recommendation"]) if "recommendation" in assessment[fraction] else u"" praise = _(u" ✓: {}").format(assessment[fraction]["praise"]) if "praise" in assessment[fraction] else u"" formatted_assessment.append(_(u"{}{}").format(recommendation, praise)) self.__notify(_(u"Basic Power Assessment (β; oddities? relog, look at your modules)"), formatted_assessment, clear_before=True) def evict_system(self, star_system): self.edrsystems.evict(star_system) def __novel_enough_situation(self, new, old, cognitive = False): if old is None: return True delta = new["timestamp"] - old["timestamp"] if cognitive: return (new["starSystem"] != old["starSystem"] or new["place"] != old["place"]) or delta > self.cognitive_novelty_threshold if new["starSystem"] != old["starSystem"]: return delta > self.system_novelty_threshold if new["place"] != old["place"]: return (old["place"] == "" or old["place"] == "Unknown" or delta > self.place_novelty_threshold) if new["ship"] != old["ship"]: return (old["ship"] == "" or old["ship"] == "Unknown" or delta > self.ship_novelty_threshold) if "wanted" in new: return ("wanted" not in old or new["wanted"] > old["wanted"]) if "bounty" in new: return ("bounty" not in old or new["bounty"] > old["bounty"]) return False def novel_enough_alert(self, alert_for_cmdr, alert): last_alert = self.alerts_cache.get(alert_for_cmdr) return self.__novel_enough_situation(alert, last_alert) def novel_enough_blip(self, cmdr_id, blip, cognitive = False): last_blip = self.cognitive_blips_cache.get(cmdr_id) if cognitive else self.blips_cache.get(cmdr_id) return self.__novel_enough_situation(blip, last_blip, cognitive) def novel_enough_scan(self, cmdr_id, scan, cognitive = False): last_scan = self.cognitive_scans_cache.get(cmdr_id) if cognitive else self.scans_cache.get(cmdr_id) novel_situation = self.__novel_enough_situation(scan, last_scan, cognitive) return novel_situation or (scan["wanted"] != last_scan["wanted"]) or (scan["bounty"] != last_scan["bounty"]) def novel_enough_traffic_report(self, sighted_cmdr, report): last_report = self.traffic_cache.get(sighted_cmdr) return self.__novel_enough_situation(report, last_report) def __novel_enough_fight(self, new, old): if old is None: return True target_cmdr = new.get('target', None) if not target_cmdr: return False if self.player.is_wingmate(target_cmdr["cmdr"]): return False if self.edrcmdrs.is_friend(target_cmdr["cmdr"]): return False if self.edrcmdrs.is_ally(target_cmdr["cmdr"]): return False if abs(new["ship"]["hullHealth"].get("value", 0) - old["ship"]["hullHealth"].get("value", 0)) >= 20: return True if new["ship"]["shieldUp"] != old["ship"]["shieldUp"]: return True return self.__novel_enough_combat_contact(new["target"]) def __novel_enough_combat_contact(self, contact): contact_old_state = self.fights_cache.get(contact["cmdr"].lower()) if not contact_old_state: return True ship_new_state = contact["ship"] ship_old_state = contact_old_state["ship"] if abs(ship_new_state["hullHealth"].get("value", 0) - ship_old_state["hullHealth"].get("value", 0)) >= 20: return True if abs(ship_new_state["shieldHealth"].get("value", 0) - ship_old_state["shieldHealth"].get("value", 0)) >= 50: return True return False def novel_enough_fight(self, involved_cmdr, fight): last_fight = self.fights_cache.get(involved_cmdr) return self.__novel_enough_fight(fight, last_fight) def outlaws_alerts_enabled(self, silent=True): return self._alerts_enabled(EDROpponents.OUTLAWS, silent) def enemies_alerts_enabled(self, silent=True): return self._alerts_enabled(EDROpponents.ENEMIES, silent) def _alerts_enabled(self, kind, silent=True): enabled = self.edropponents[kind].is_comms_link_up() if not silent: details = [_(u"{} alerts enabled").format(_(kind)) if enabled else _(u"{} alerts disabled").format(_(kind))] if self.realtime_params[kind]["max_distance"]: details.append(_(u" <={max_distance}ly").format(max_distance=self.realtime_params[kind]["max_distance"])) if self.realtime_params[kind]["min_bounty"]: details.append(_(u" >={min_bounty}cr").format(min_bounty=self.realtime_params[kind]["min_bounty"])) self.notify_with_details(_(u"EDR Alerts"), details) return enabled def enable_outlaws_alerts(self, silent=False): self._enable_alerts(EDROpponents.OUTLAWS, silent) def enable_enemies_alerts(self, silent=False): self._enable_alerts(EDROpponents.ENEMIES, silent) def _enable_alerts(self, kind, silent=False): try: details = "" if self._alerts_enabled(kind, silent=True): details = _(u"{} alerts already enabled").format(_(kind)) elif kind == EDROpponents.ENEMIES: if self.is_anonymous(): details = _(u"Request an EDR account to access enemy alerts (https://edrecon.com/account)") elif not self.player.power: details = _(u"Pledge to a power to access enemy alerts") elif self.player.time_pledged < self.enemy_alerts_pledge_threshold: details = _(u"Remain loyal for at least {} days to access enemy alerts").format(int(self.enemy_alerts_pledge_threshold // (24*60*60))) else: details = _(u"Enabling Enemy alerts") if self.edropponents[kind].establish_comms_link() else _(u"Couldn't enable Enemy alerts") else: details = _(u"Enabling {kind} alerts").format(kind=_(kind)) if self.edropponents[kind].establish_comms_link() else _(u"Couldn't enable {kind} alerts").format(kind=_(kind)) if not silent: if self.realtime_params[kind]["max_distance"]: details += _(u" <={max_distance}ly").format(max_distance=self.realtime_params[kind]["max_distance"]) if self.realtime_params[kind]["min_bounty"]: details += _(u" >={min_bounty}cr").format(min_bounty=self.realtime_params[kind]["min_bounty"]) self.notify_with_details(_(u"EDR Alerts"), [details]) except CommsJammedError: self.__commsjammed() def disable_outlaws_alerts(self, silent=False): self._disable_alerts(EDROpponents.OUTLAWS, silent) def disable_enemies_alerts(self, silent=False): self._disable_alerts(EDROpponents.ENEMIES, silent) def _disable_alerts(self, kind, silent=False): details = "" if self.edropponents[kind].is_comms_link_up(): self.edropponents[kind].shutdown_comms_link() details = _(u"Disabling {} alerts").format(_(kind)) else: details = _(u"{} alerts already disabled").format(_(kind)) if not silent: self.notify_with_details(_(u"EDR Alerts"), [details]) def min_bounty_outlaws_alerts(self, min_bounty): self._min_bounty_alerts(EDROpponents.OUTLAWS, min_bounty) def min_bounty_enemies_alerts(self, min_bounty): self._min_bounty_alerts(EDROpponents.ENEMIES, min_bounty) def _min_bounty_alerts(self, kind, min_bounty): new_value = self.realtime_params[kind]["min_bounty"] if min_bounty: try: new_value = int(min_bounty) self.notify_with_details(_(u"EDR Alerts"), [_(u"minimum bounty set to {min_bounty} cr for {kind}").format(min_bounty=EDFineOrBounty(new_value).pretty_print(), kind=_(kind))]) except ValueError: self.notify_with_details(_(u"EDR Alerts"), [_(u"invalid value for minimum bounty")]) new_value = None else: self.notify_with_details(_(u"EDR Alerts"), [_(u"no minimum bounty required")]) self.realtime_params[kind]["min_bounty"] = new_value if new_value is None: config.set("EDR{}AlertsMinBounty".format(kind), "None") else: config.set("EDR{}AlertsMinBounty".format(kind), new_value) def max_distance_outlaws_alerts(self, max_distance): self._max_distance_alerts(EDROpponents.OUTLAWS, max_distance) def max_distance_enemies_alerts(self, max_distance): self._max_distance_alerts(EDROpponents.ENEMIES, max_distance) def _max_distance_alerts(self, kind, max_distance): new_value = self.realtime_params[kind]["max_distance"] if max_distance: try: new_value = int(max_distance) self.notify_with_details(_(u"EDR Alerts"), [_(u"maximum distance set to {max_distance} ly for {kind}").format(max_distance=new_value, kind=_(kind))]) except ValueError: self.notify_with_details(_(u"EDR Alerts"), [_(u"invalid value, removing maximal distance")]) new_value = None else: self.notify_with_details(_(u"Outlaws Alerts"), [_(u"no limits on distance")]) self.realtime_params[kind]["max_distance"] = new_value if new_value is None: config.set("EDR{}AlertsMaxDistance".format(kind), "None") else: config.set("EDR{}AlertsMaxDistance".format(kind), new_value) def _realtime_callback(self, kind, events): summary = [] if kind not in [EDROpponents.OUTLAWS, EDROpponents.ENEMIES]: return if events in ["cancel", "auth_revoked"]: summary = [_(u"Comms link interrupted. Send '?{} on' to re-establish.").format(kind.lower())] else: summary = self._summarize_realtime_alert(kind, events) if summary: self.notify_with_details(_(u"EDR Alerts"), summary) def _worthy_alert(self, kind, event): self_uid = self.server.uid() if event["uid"] == self_uid: return False if self.realtime_params[kind]["max_distance"]: try: origin = self.player.star_system distance = self.edrsystems.distance(origin, event["starSystem"]) threshold = self.realtime_params[kind]["max_distance"] if distance > threshold: EDRLOG.log(u"EDR alert not worthy. Distance {} between systems {} and {} exceeds threshold {}".format(distance, origin, event["starSystem"], threshold), "DEBUG") return False except ValueError: EDRLOG.log(u"Can't compute distance between systems {} and {}: unknown system(s)".format(self.player.star_system, event["starSystem"]), "WARNING") pass if self.realtime_params[kind]["min_bounty"]: if "bounty" not in event: return False if event["bounty"] < self.realtime_params[kind]["min_bounty"]: EDRLOG.log(u"EDR alert not worthy. Bounty {} does not exceeds threshold {}".format(event["bounty"], self.realtime_params[kind]["min_bounty"]), "DEBUG") return False return self.novel_enough_alert(event["cmdr"].lower(), event) def _summarize_realtime_alert(self, kind, event): summary = [] EDRLOG.log(u"realtime {} alerts, handling {}".format(kind, event), "DEBUG") if not self._worthy_alert(kind, event): EDRLOG.log(u"Skipped realtime {} event because it wasn't worth alerting about: {}.".format(kind, event), "DEBUG") else: location = EDLocation(event["starSystem"], event["place"]) copy(event["starSystem"]) distance = None try: distance = self.edrsystems.distance(self.player.star_system, location.star_system) except ValueError: pass oneliner = _(u"{cmdr} ({ship}) sighted in {location}") if kind is EDROpponents.ENEMIES and event.get("enemy", None): oneliner = _(u"Enemy {cmdr} ({ship}) sighted in {location}") elif kind is EDROpponents.OUTLAWS: oneliner = _(u"Outlaw {cmdr} ({ship}) sighted in {location}") oneliner = oneliner.format(cmdr=event["cmdr"], ship=event["ship"], location=location.pretty_print()) if distance: oneliner += _(u" [{distance:.3g} ly]").format(distance=distance) if distance < 50.0 else _(u" [{distance} ly]").format(distance=int(distance)) if event.get("wanted", None): if event["bounty"] > 0: oneliner += _(u" wanted for {bounty} cr").format(bounty=EDFineOrBounty(event["bounty"]).pretty_print()) else: oneliner += _(u" wanted somewhere") if oneliner: summary.append(oneliner) self.alerts_cache.set(event["cmdr"].lower(), event) return summary def who(self, cmdr_name, autocreate=False): try: profile = self.cmdr(cmdr_name, autocreate, check_inara_server=True) if profile: self.status = _(u"got info about {}").format(cmdr_name) EDRLOG.log(u"Who {} : {}".format(cmdr_name, profile.short_profile(self.player.powerplay)), "INFO") legal = self.edrlegal.summarize(profile.cid) details = [profile.short_profile(self.player.powerplay)] if legal: details.append(legal["overview"]) self.__intel(_(u"Intel about {}").format(cmdr_name), details, clear_before=True, legal=legal) else: EDRLOG.log(u"Who {} : no info".format(cmdr_name), "INFO") self.__intel(_(u"Intel about {}").format(cmdr_name), [_("No info").format(cmdr=cmdr_name)], clear_before=True) except CommsJammedError: self.__commsjammed() def distance(self, from_system, to_system): details = [] distance = None try: distance = self.edrsystems.distance(from_system, to_system) except ValueError: pass if distance: pretty_dist = _(u"{distance:.3g}").format(distance=distance) if distance < 50.0 else _(u"{distance}").format(distance=int(distance)) details.append(_(u"{dist}ly from {from_sys} to {to_sys}").format(dist=pretty_dist, from_sys=from_system, to_sys=to_system)) taxi_jump_range = 50 jumping_time = self.edrsystems.jumping_time(from_system, to_system, taxi_jump_range) transfer_time = self.edrsystems.transfer_time(from_system, to_system) details.append(_(u"Taxi time ({}LY): {}").format(taxi_jump_range, EDTime.pretty_print_timespan(jumping_time))) details.append(_(u"Transfer time: {}").format(EDTime.pretty_print_timespan(transfer_time))) self.status = _(u"distance: {dist}ly").format(dist=pretty_dist) else: self.status = _(u"distance failed") details.append(_(u"Couldn't calculate a distance. Invalid or unknown system names?")) self.__notify("Distance", details, clear_before = True) def blip(self, cmdr_name, blip): if self.player.in_solo(): EDRLOG.log(u"Skipping blip since the user is in solo (unexpected).", "INFO") return False cmdr_id = self.cmdr_id(cmdr_name) if cmdr_id is None: self.status = _(u"no cmdr id (contact).") EDRLOG.log(u"Can't submit blip (no cmdr id for {}).".format(cmdr_name), "ERROR") its_actually_fine = self.is_anonymous() return its_actually_fine profile = self.cmdr(cmdr_name, check_inara_server=True) legal = self.edrlegal.summarize(profile.cid) if profile and (self.player.name != cmdr_name) and profile.is_dangerous(self.player.powerplay): self.status = _(u"{} is bad news.").format(cmdr_name) if self.novel_enough_blip(cmdr_id, blip, cognitive = True): details = [profile.short_profile(self.player.powerplay)] if legal: details.append(legal["overview"]) self.__warning(_(u"[Caution!] Intel about {}").format(cmdr_name), details, clear_before=True, legal=legal) self.cognitive_blips_cache.set(cmdr_id, blip) if self.player.in_open() and self.is_anonymous() and profile.is_dangerous(self.player.powerplay): self.advertise_full_account(_("You could have helped other EDR users by reporting this outlaw.")) elif self.player.in_open() and self.is_anonymous(): self.advertise_full_account(_("You could have helped other EDR users by reporting this enemy.")) else: EDRLOG.log(u"Skipping warning since a warning was recently shown.", "INFO") if not self.novel_enough_blip(cmdr_id, blip): self.status = u"skipping blip (not novel enough)." EDRLOG.log(u"Blip is not novel enough to warrant reporting", "INFO") return True if self.is_anonymous(): EDRLOG.log("Skipping blip since the user is anonymous.", "INFO") self.blips_cache.set(cmdr_id, blip) return True success = self.server.blip(cmdr_id, blip) if success: self.status = u"blip reported for {}.".format(cmdr_name) self.blips_cache.set(cmdr_id, blip) return success def scanned(self, cmdr_name, scan): if self.player.in_solo(): EDRLOG.log(u"Skipping scanned since the user is in solo (unexpected).", "INFO") return False cmdr_id = self.cmdr_id(cmdr_name) if cmdr_id is None: self.status = _(u"cmdr unknown to EDR.") EDRLOG.log(u"Can't submit scan (no cmdr id for {}).".format(cmdr_name), "ERROR") its_actually_fine = self.is_anonymous() return its_actually_fine if self.novel_enough_scan(cmdr_id, scan, cognitive = True): profile = self.cmdr(cmdr_name, check_inara_server=True) legal = self.edrlegal.summarize(profile.cid) bounty = EDFineOrBounty(scan["bounty"]) if scan["bounty"] else None if profile and (self.player.name != cmdr_name): if profile.is_dangerous(self.player.powerplay): # Translators: this is shown via EDMC's EDR status line upon contact with a known outlaw self.status = _(u"{} is bad news.").format(cmdr_name) details = [profile.short_profile(self.player.powerplay)] status = "" if scan["enemy"]: status += _(u"PP Enemy (weapons free). ") if scan["bounty"]: status += _(u"Wanted for {} cr").format(EDFineOrBounty(scan["bounty"]).pretty_print()) elif scan["wanted"]: status += _(u"Wanted somewhere. A Kill-Warrant-Scan will reveal their highest bounty.") if status: details.append(status) if legal: details.append(legal["overview"]) self.__warning(_(u"[Caution!] Intel about {}").format(cmdr_name), details, clear_before=True, legal=legal) elif self.intel_even_if_clean or (scan["wanted"] and bounty.is_significant()): self.status = _(u"Intel for cmdr {}.").format(cmdr_name) details = [profile.short_profile(self.player.powerplay)] if bounty: details.append(_(u"Wanted for {} cr").format(EDFineOrBounty(scan["bounty"]).pretty_print())) elif scan["wanted"]: details.append(_(u"Wanted somewhere but it could be minor offenses.")) if legal: details.append(legal["overview"]) self.__intel(_(u"Intel about {}").format(cmdr_name), details, clear_before=True, legal=legal) if not self.player.in_solo() and (self.is_anonymous() and (profile.is_dangerous(self.player.powerplay) or (scan["wanted"] and bounty.is_significant()))): # Translators: this is shown to users who don't yet have an EDR account self.advertise_full_account(_(u"You could have helped other EDR users by reporting this outlaw.")) elif not self.player.in_solo() and self.is_anonymous() and scan["enemy"] and self.player.power: # Translators: this is shown to users who don't yet have an EDR account self.advertise_full_account(_(u"You could have helped other {power} pledges by reporting this enemy.").format(self.player.power)) self.cognitive_scans_cache.set(cmdr_id, scan) if not self.novel_enough_scan(cmdr_id, scan): self.status = _(u"not novel enough (scan).") EDRLOG.log(u"Scan is not novel enough to warrant reporting", "INFO") return True if self.is_anonymous(): EDRLOG.log("Skipping reporting scan since the user is anonymous.", "INFO") self.scans_cache.set(cmdr_id, scan) return True success = self.server.scanned(cmdr_id, scan) if success: self.status = _(u"scan reported for {}.").format(cmdr_name) self.scans_cache.set(cmdr_id, scan) return success def traffic(self, star_system, traffic): if self.player.in_solo(): EDRLOG.log(u"Skipping traffic since the user is in solo (unexpected).", "INFO") return False try: if self.is_anonymous(): EDRLOG.log(u"Skipping traffic report since the user is anonymous.", "INFO") return True sigthed_cmdr = traffic["cmdr"] if not self.novel_enough_traffic_report(sigthed_cmdr, traffic): self.status = _(u"not novel enough (traffic).") EDRLOG.log(u"Traffic report is not novel enough to warrant reporting", "INFO") return True sid = self.edrsystems.system_id(star_system, may_create=True) if sid is None: EDRLOG.log(u"Failed to report traffic for system {} : no id found.".format(star_system), "DEBUG") return False success = self.server.traffic(sid, traffic) if success: self.status = _(u"traffic reported.") self.traffic_cache.set(sigthed_cmdr, traffic) return success except CommsJammedError: self.__commsjammed() return False def crime(self, star_system, crime): if self.player.in_solo(): EDRLOG.log(u"Skipping crime since the user is in solo (unexpected).", "INFO") return False if not self.crimes_reporting: EDRLOG.log(u"Crimes reporting is off (!crimes on to re-enable).", "INFO") self.status = _(u"Crimes reporting is off (!crimes on to re-enable)") return True if self.player.in_bad_neighborhood(): EDRLOG.log(u"Crime not being reported because the player is in an anarchy.", "INFO") self.status = _(u"Anarchy system (crimes not reported).") return True if self.is_anonymous(): EDRLOG.log(u"Skipping crime report since the user is anonymous.", "INFO") if crime["victim"] == self.player.name: self.advertise_full_account(_(u"You could have helped other EDR users or get help by reporting this crime!")) return True sid = self.edrsystems.system_id(star_system, may_create=True) if sid is None: EDRLOG.log(u"Failed to report crime in system {} : no id found.".format(star_system), "DEBUG") return False if self.server.crime(sid, crime): self.status = _(u"crime reported!") return True return False def fight(self, fight): if self.player.in_solo(): EDRLOG.log(u"Skipping fight since the user is in solo (unexpected).", "INFO") return False if not self.crimes_reporting: EDRLOG.log(u"Crimes reporting is off (!crimes on to re-enable).", "INFO") self.status = _(u"Crimes reporting is off (!crimes on to re-enable)") return if self.player.in_bad_neighborhood(): EDRLOG.log(u"Fight not being reported because the player is in an anarchy.", "INFO") self.status = _(u"Anarchy system (fights not reported).") return if self.is_anonymous(): EDRLOG.log(u"Skipping fight report since the user is anonymous.", "INFO") return if not self.player.recon_box.forced: outlaws_presence = self.player.instance.presence_of_outlaws(self.edrcmdrs, ignorables=self.player.wing_and_crew()) if outlaws_presence: self.player.recon_box.activate() self.__notify(_(u"EDR Central"), [_(u"Fight reporting enabled"), _(u"Reason: presence of outlaws"), _(u"Turn it off: flash your lights twice, or leave this area, or escape danger and retract hardpoints.")], clear_before=True) if not self.player.recon_box.active: if not self.player.recon_box.advertised: self.__notify(_(u"Need assistance?"), [_(u"Flash your lights twice to report a PvP fight to enforcers."), _(u"Send '!crimes off' to make EDR go silent.")], clear_before=True) self.player.recon_box.advertised = True return if not self.novel_enough_fight(fight['cmdr'].lower(), fight): EDRLOG.log(u"Skipping fight report (not novel enough).", "INFO") return star_system = fight["starSystem"] sid = self.edrsystems.system_id(star_system, may_create=True) if sid is None: EDRLOG.log(u"Failed to report fight in system {} : no id found.".format(star_system), "DEBUG") return instance_changes = self.player.instance.noteworthy_changes_json() if instance_changes: instance_changes["players"] = filter(lambda x: self.__novel_enough_combat_contact(x), instance_changes["players"]) fight["instance"] = instance_changes fight["codeword"] = self.player.recon_box.keycode if self.server.fight(sid, fight): self.status = _(u"fight reported!") self.fights_cache.set(fight["cmdr"].lower(), fight) if fight.get("target", None): self.fights_cache.set(fight["target"]["cmdr"].lower(), fight["target"]) if fight.get("instance", None) and fight["instance"].get("players", None): for change in fight["instance"]["players"]: self.fights_cache.set(change["cmdr"].lower(), change) def crew_report(self, report): if self.player.in_solo(): EDRLOG.log(u"Skipping crew report since the user is in solo (unexpected).", "INFO") return False if self.is_anonymous(): EDRLOG.log(u"Skipping crew report since the user is anonymous.", "INFO") if report["captain"] == self.player.name and (report["crimes"] or report["kicked"]): self.advertise_full_account(_(u"You could have helped other EDR users by reporting this problematic crew member!")) return False crew_id = self.cmdr_id(report["crew"]) if crew_id is None: self.status = _(u"{} is unknown to EDR.".format(report["crew"])) EDRLOG.log(u"Can't submit crew report (no cmdr id for {}).".format(report["crew"]), "ERROR") return False if self.server.crew_report(crew_id, report): self.status = _(u"multicrew session reported (cmdr {name}).").format(name=report["crew"]) return True return False def __throttling_duration(self): now_epoch = EDTime.py_epoch_now() if now_epoch > self._throttle_until_timestamp: return 0 return self._throttle_until_timestamp - now_epoch def call_central(self, service, info): if self.is_anonymous(): EDRLOG.log(u"Skipping EDR Central call since the user is anonymous.", "INFO") self.advertise_full_account(_(u"Sorry, this feature only works with an EDR account."), passive=False) return False throttling = self.__throttling_duration() if throttling: self.status = _(u"Message not sent. Try again in {duration}.").format(duration=EDTime.pretty_print_timespan(throttling)) self.__notify(_(u"EDR central"), [self.status], clear_before = True) return False star_system = info["starSystem"] sid = self.edrsystems.system_id(star_system, may_create=True) if sid is None: EDRLOG.log(u"Failed to call central from system {} : no id found.".format(star_system), "DEBUG") return False info["codeword"] = self.player.recon_box.gen_keycode() if self.server.call_central(service, sid, info): details = [_(u"Message sent with codeword '{}'.").format(info["codeword"]), _(u"Ask the codeword to identify trusted commanders.")] if service in ["fuel", "repair"]: fuel_service = random.choice([{"name": "Fuel Rats", "url": "https://fuelrats.com/"}, {"name": "Repair Corgis", "url": "https://candycrewguild.space/"}]) attachment = [_(u"For good measure, also reach out to these folks with the info below:"), fuel_service["url"]] fuel_info = "Fuel: {:.1f}/{:.0f}".format(info["ship"]["fuelLevel"], info["ship"]["fuelCapacity"]) if info["ship"].get("fuelLevel") else "" hull_info = "Hull: {:.0f}%".format(info["ship"]["hullHealth"]["value"]) if info["ship"].get("hullHealth") else "" info = u"{} ({}) in {}, {} - {} {}\nInfo provided by EDR.".format(info["cmdr"], info["ship"]["type"], info["starSystem"], info["place"], fuel_info, hull_info) copy(info) attachment.append(info) self.ui.notify(fuel_service["name"], attachment) details.append(_(u"Check ED Market Connector for instructions about other options")) status = _(u"Sent to EDR central - Also try: {}").format(fuel_service["name"]) link = fuel_service["url"] self.linkable_status(link, status) else: self.status = _(u"Message sent to EDR central") self.__notify(_(u"EDR central"), details, clear_before = True) self._throttle_until_timestamp = EDTime.py_epoch_now() + 60*5 #TODO parameterize return True return False def tag_cmdr(self, cmdr_name, tag): if self.is_anonymous(): EDRLOG.log(u"Skipping tag cmdr since the user is anonymous.", "INFO") self.advertise_full_account(_(u"Sorry, this feature only works with an EDR account."), passive=False) return False if tag in ["enemy", "ally"]: if not self.player.squadron: EDRLOG.log(u"Skipping squadron tag since the user isn't a member of a squadron.", "INFO") self.notify_with_details(_(u"Squadron Dex"), [_(u"You need to join a squadron on https://inara.cz to use this feature."), _(u"Then, reboot EDR to reflect these changes.")]) return False elif not self.player.is_empowered_by_squadron(): EDRLOG.log(u"Skipping squadron tag since the user isn't trusted.", "INFO") self.notify_with_details(_(u"Squadron Dex"), [_(u"You need to reach {} to tag enemies or allies.").format(self.player.squadron_empowered_rank())]) return False success = self.edrcmdrs.tag_cmdr(cmdr_name, tag) dex_name = _(u"Squadron Dex") if tag in ["enemy", "ally"] else _(u"Cmdr Dex") if success: self.__notify(dex_name, [_(u"Successfully tagged cmdr {name} with {tag}").format(name=cmdr_name, tag=tag)], clear_before = True) else: self.__notify(dex_name, [_(u"Could not tag cmdr {name} with {tag}").format(name=cmdr_name, tag=tag)], clear_before = True) return success def memo_cmdr(self, cmdr_name, memo): if self.is_anonymous(): EDRLOG.log(u"Skipping memo cmdr since the user is anonymous.", "INFO") self.advertise_full_account(_(u"Sorry, this feature only works with an EDR account."), passive=False) return False success = self.edrcmdrs.memo_cmdr(cmdr_name, memo) if success: self.__notify(_(u"Cmdr Dex"), [_(u"Successfully attached a memo to cmdr {}").format(cmdr_name)], clear_before = True) else: self.__notify(_(u"Cmdr Dex"), [_(u"Failed to attach a memo to cmdr {}").format(cmdr_name)], clear_before = True) return success def clear_memo_cmdr(self, cmdr_name): if self.is_anonymous(): EDRLOG.log(u"Skipping clear_memo_cmdr since the user is anonymous.", "INFO") self.advertise_full_account(_(u"Sorry, this feature only works with an EDR account."), passive=False) return False success = self.edrcmdrs.clear_memo_cmdr(cmdr_name) if success: self.__notify(_(u"Cmdr Dex"),[_(u"Successfully removed memo from cmdr {}").format(cmdr_name)], clear_before = True) else: self.__notify(_(u"Cmdr Dex"), [_(u"Failed to remove memo from cmdr {}").format(cmdr_name)], clear_before = True) return success def untag_cmdr(self, cmdr_name, tag): if self.is_anonymous(): EDRLOG.log(u"Skipping untag cmdr since the user is anonymous.", "INFO") self.advertise_full_account(_(u"Sorry, this feature only works with an EDR account."), passive=False) return False if tag in ["enemy", "ally"]: if not self.player.squadron: EDRLOG.log(u"Skipping squadron untag since the user isn't a member of a squadron.", "INFO") self.notify_with_details(_(u"Squadron Dex"), [_(u"You need to join a squadron on https://inara.cz to use this feature."), _(u"Then, reboot EDR to reflect these changes.")]) return False elif not self.player.is_empowered_by_squadron(): EDRLOG.log(u"Skipping squadron untag since the user isn't trusted.", "INFO") self.notify_with_details(_(u"Squadron Dex"), [_(u"You need to reach {} to tag enemies or allies.").format(self.player.squadron_empowered_rank())]) return False success = self.edrcmdrs.untag_cmdr(cmdr_name, tag) dex_name = _(u"Squadron Dex") if tag in ["enemy", "ally"] else _(u"Cmdr Dex") if success: if tag is None: self.__notify(dex_name, [_(u"Successfully removed all tags from cmdr {}").format(cmdr_name)], clear_before = True) else: self.__notify(dex_name, [_(u"Successfully removed tag {} from cmdr {}").format(tag, cmdr_name)], clear_before = True) else: self.__notify(dex_name, [_(u"Could not remove tag(s) from cmdr {}").format(cmdr_name)], clear_before = True) return success def where(self, cmdr_name): report = {} try: for kind in self.edropponents: candidate_report = self.edropponents[kind].where(cmdr_name) if candidate_report and (not report or report["timestamp"] < candidate_report["timestamp"]): report = candidate_report if report: self.status = _(u"got info about {}").format(cmdr_name) header = _(u"Intel about {}") if self.player.in_open() else _(u"Intel about {} (Open)") self.__intel(header.format(cmdr_name), report["readable"], clear_before=True) else: EDRLOG.log(u"Where {} : no info".format(cmdr_name), "INFO") self.status = _(u"no info about {}").format(cmdr_name) header = _(u"Intel about {}") if self.player.in_open() else _(u"Intel about {} (Open)") self.__intel(header.format(cmdr_name), [_(u"Not recently sighted or not an outlaw.")], clear_before=True) except CommsJammedError: self.__commsjammed() def where_ship(self, name_or_type): results = self.player.fleet.where(name_or_type) if results: hits = [] random.shuffle(results) in_clipboard = False for hit in results: transit = _(u" @ {}").format(EDTime.t_plus_py(hit[3])) if hit[3] else u"" if hit[0]: hits.append(_(u"'{}' ({}): {}{}").format(hit[0], hit[1], hit[2], transit)) else: hits.append(_(u"{}: {}{}").format(hit[1], hit[2], transit)) if not in_clipboard and hit[2]: copy(hit[2]) in_clipboard = True self.__notify(_(u"Ship locator"), hits, clear_before = True) elif results == False: self.__notify(_(u"Ship locator"), [_(u"No info about your fleet."), _(u"Visit a shipyard to update your fleet info.")], clear_before = True) else: self.__notify(_(u"Ship locator"), [_(u"Couldn't find anything")], clear_before = True) def outlaws(self): try: return self._opponents(EDROpponents.OUTLAWS) except CommsJammedError: self.__commsjammed() return False def enemies(self): try: return self._opponents(EDROpponents.ENEMIES) except CommsJammedError: self.__commsjammed() return False def _opponents(self, kind): if kind is EDROpponents.ENEMIES and not self.player.power: EDRLOG.log(u"Not pledged to any power, can't have enemies.", "INFO") self.__notify(_(u"Recently Sighted {kind}").format(kind=_(kind)), [_(u"You need to be pledged to a power.")], clear_before = True) return False opponents_report = self.edropponents[kind].recent_sightings() if not opponents_report: EDRLOG.log(u"No recently sighted {}".format(kind), "INFO") header = _(u"Recently Sighted {kind}") if self.player.in_open() else _(u"Recently Sighted {kind} (Open)") self.__sitrep(header.format(kind=_(kind)), [_(u"No {kind} sighted in the last {timespan}").format(kind=_(kind).lower(), timespan=EDTime.pretty_print_timespan(self.edropponents[kind].timespan))]) return False self.status = _(u"recently sighted {kind}").format(kind=_(kind)) EDRLOG.log(u"Got recently sighted {}".format(kind), "INFO") header = _(u"Recently Sighted {kind}") if self.player.in_open() else _(u"Recently Sighted {kind} (Open)") self.__sitrep(header.format(kind=_(kind)), opponents_report) def help(self, section): content = self.help_content.get(section) if not content: return False if self.visual_feedback: EDRLOG.log(u"Show help for {} with header: {} and details: {}".format(section, content["header"], content["details"][0]), "DEBUG") self.IN_GAME_MSG.help(content["header"], content["details"]) EDRLOG.log(u"[Alt] Show help for {} with header: {} and details: {}".format(section, content["header"], content["details"][0]), "DEBUG") self.ui.help(_(content["header"]), _(content["details"])) return True def clear(self): if self.visual_feedback: self.IN_GAME_MSG.clear() self.ui.clear() def __sitrep(self, header, details): if self.audio_feedback: self.AUDIO_FEEDBACK.notify() if self.visual_feedback: EDRLOG.log(u"sitrep with header: {}; details: {}".format(header, details[0]), "DEBUG") self.IN_GAME_MSG.clear_sitrep() self.IN_GAME_MSG.sitrep(header, details) EDRLOG.log(u"[Alt] sitrep with header: {}; details: {}".format(header, details[0]), "DEBUG") self.ui.sitrep(header, details) def __intel(self, header, details, clear_before=False, legal=None): if self.audio_feedback: self.AUDIO_FEEDBACK.notify() if self.visual_feedback: EDRLOG.log(u"Intel; details: {}".format(details[0]), "DEBUG") if clear_before: self.IN_GAME_MSG.clear_intel() self.IN_GAME_MSG.intel(header, details, legal) EDRLOG.log(u"[Alt] Intel; details: {}".format(details[0]), "DEBUG") self.ui.intel(header, details) def __warning(self, header, details, clear_before=False, legal=None): if self.audio_feedback: self.AUDIO_FEEDBACK.warn() if self.visual_feedback: EDRLOG.log(u"Warning; details: {}".format(details[0]), "DEBUG") if clear_before: self.IN_GAME_MSG.clear_warning() self.IN_GAME_MSG.warning(header, details, legal) EDRLOG.log(u"[Alt] Warning; details: {}".format(details[0]), "DEBUG") self.ui.warning(header, details) def __notify(self, header, details, clear_before=False): if self.audio_feedback: self.AUDIO_FEEDBACK.notify() if self.visual_feedback: EDRLOG.log(u"Notify about {}; details: {}".format(header, details[0]), "DEBUG") if clear_before: self.IN_GAME_MSG.clear_notice() self.IN_GAME_MSG.notify(header, details) EDRLOG.log(u"[Alt] Notify about {}; details: {}".format(header, details[0]), "DEBUG") self.ui.notify(header, details) def __commsjammed(self): self.__notify(_(u"Comms Link Error"), [_(u"EDR Central can't be reached at the moment"), _(u"Try again later. Join https://edrecon.com/discord or contact Cmdr LeKeno if it keeps failing")]) def notify_with_details(self, notice, details): self.__notify(notice, details) def warn_with_details(self, warning, details): self.__warning(warning, details) def advertise_full_account(self, context, passive=True): now = datetime.datetime.now() now_epoch = time.mktime(now.timetuple()) if passive and self.previous_ad: if (now_epoch - self.previous_ad) <= self.edr_needs_u_novelty_threshold: return False self.__notify(_(u"EDR needs you!"), [context, u"--", _(u"Apply for an account at https://edrecon.com/account"), _(u"It's free, no strings attached.")], clear_before=True) self.previous_ad = now_epoch return True def interstellar_factors_near(self, star_system, override_sc_distance = None): if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return try: self.edrsystems.search_interstellar_factors(star_system, self.__staoi_found, with_large_pad=self.player.needs_large_landing_pad(), override_sc_distance = override_sc_distance) self.searching = True self.status = _(u"I.Factors: searching...") self.__notify(_(u"EDR Search"), [_(u"Interstellar Factors: searching...")], clear_before = True) except ValueError: self.status = _(u"I.Factors: failed") self.notify_with_details(_(u"EDR Search"), [_(u"Unknown system")]) def raw_material_trader_near(self, star_system, override_sc_distance = None): if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return try: self.edrsystems.search_raw_trader(star_system, self.__staoi_found, with_large_pad=self.player.needs_large_landing_pad(), override_sc_distance = override_sc_distance) self.searching = True self.status = _(u"Raw mat. trader: searching...") self.__notify(_(u"EDR Search"), [_(u"Raw material trader: searching...")], clear_before = True) except ValueError: self.status = _(u"Raw mat. trader: failed") self.notify_with_details(_(u"EDR Search"), [_(u"Unknown system")]) def encoded_material_trader_near(self, star_system, override_sc_distance = None): if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return try: self.edrsystems.search_encoded_trader(star_system, self.__staoi_found, with_large_pad=self.player.needs_large_landing_pad(), override_sc_distance = override_sc_distance) self.searching = True self.status = _(u"Encoded data trader: searching...") self.__notify(_(u"EDR Search"), [_(u"Encoded data trader: searching...")], clear_before = True) except ValueError: self.status = _(u"Encoded data trader: failed") self.notify_with_details(_(u"EDR Search"), [_(u"Unknown system")]) def manufactured_material_trader_near(self, star_system, override_sc_distance = None): if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return try: self.edrsystems.search_manufactured_trader(star_system, self.__staoi_found, with_large_pad=self.player.needs_large_landing_pad(), override_sc_distance = override_sc_distance) self.searching = True self.status = _(u"Manufactured mat. trader: searching...") self.__notify(_(u"EDR Search"), [_(u"Manufactured material trader: searching...")], clear_before = True) except ValueError: self.status = _(u"Manufactured mat. trader: failed") self.__notify(_(u"EDR Search"), [_(u"Unknown system")], clear_before = True) def staging_station_near(self, star_system, override_sc_distance = None): if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return try: self.edrsystems.search_staging_station(star_system, self.__staoi_found) self.searching = True self.status = _(u"Staging station: searching...") self.__notify(_(u"EDR Search"), [_(u"Staging station: searching...")], clear_before = True) except ValueError: self.status = _(u"Staging station: failed") self.__notify(_(u"EDR Search"), [_(u"Unknown system")], clear_before = True) def human_tech_broker_near(self, star_system, override_sc_distance = None): if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return try: self.edrsystems.search_human_tech_broker(star_system, self.__staoi_found, with_large_pad=self.player.needs_large_landing_pad(), override_sc_distance = override_sc_distance) self.searching = True self.status = _(u"Human tech broker: searching...") self.__notify(_(u"EDR Search"), [_(u"Human tech broker: searching...")], clear_before = True) except ValueError: self.status = _(u"Human tech broker: failed") self.__notify(_(u"EDR Search"), [_(u"Unknown system")], clear_before = True) def guardian_tech_broker_near(self, star_system, override_sc_distance = None): if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return try: self.edrsystems.search_guardian_tech_broker(star_system, self.__staoi_found, with_large_pad=self.player.needs_large_landing_pad(), override_sc_distance = override_sc_distance) self.searching = True self.status = _(u"Guardian tech broker: searching...") self.__notify(_(u"EDR Search"), [_(u"Guardian tech broker: searching...")], clear_before = True) except ValueError: self.status = _(u"Guardian tech broker: failed") self.__notify(_(u"EDR Search"), [_(u"Unknown system")], clear_before = True) def __staoi_found(self, reference, radius, sc, soi_checker, result): self.searching = False details = [] if result: sc_distance = result['station']['distanceToArrival'] distance = result['distance'] pretty_dist = _(u"{dist:.3g}LY").format(dist=distance) if distance < 50.0 else _(u"{dist}LY").format(dist=int(distance)) pretty_sc_dist = _(u"{dist}LS").format(dist=int(sc_distance)) details.append(_(u"{system}, {dist}").format(system=result['name'], dist=pretty_dist)) details.append(_(u"{station} ({type}), {sc_dist}").format(station=result['station']['name'], type=result['station']['type'], sc_dist=pretty_sc_dist)) details.append(_(u"as of {date} {ci}").format(date=result['station']['updateTime']['information'],ci=result.get('comment', ''))) self.status = u"{item}: {system}, {dist} - {station} ({type}), {sc_dist}".format(item=soi_checker.name, system=result['name'], dist=pretty_dist, station=result['station']['name'], type=result['station']['type'], sc_dist=pretty_sc_dist) copy(result["name"]) else: self.status = _(u"{}: nothing within [{}LY, {}LS] of {}".format(soi_checker.name, int(radius), int(sc), reference)) checked = _("checked {} systems").format(soi_checker.systems_counter) if soi_checker.stations_counter: checked = _("checked {} systems and {} stations").format(soi_checker.systems_counter, soi_checker.stations_counter) details.append(_(u"nothing found within [{}LY, {}LS], {}.").format(int(radius), int(sc), checked)) if soi_checker.hint: details.append(soi_checker.hint) self.__notify(_(u"{} near {}").format(soi_checker.name, reference), details, clear_before = True) def search_resource(self, resource): star_system = self.player.star_system if not star_system: return if self.searching: self.__notify(_(u"EDR Search"), [_(u"Already searching for something, please wait...")], clear_before = True) return if not (self.edrsystems.in_bubble(star_system) or self.edrsystems.in_colonia(star_system)): self.__notify(_(u"EDR Search"), [_(u"Search features only work in the bubble or Colonia.")], clear_before = True) return cresource = self.edrresourcefinder.canonical_name(resource) if cresource is None: self.status = _(u"{}: not supported.").format(resource) self.__notify(_(u"EDR Search"), [_(u"{}: not supported.").format(resource), _(u"To learn how to use the feature, send: !help search")], clear_before = True) return try: outcome = self.edrresourcefinder.resource_near(resource, star_system, self.__resource_found) if outcome == True: self.searching = True self.status = _(u"{}: searching...".format(cresource)) self.__notify(_(u"EDR Search"), [_(u"{}: searching...").format(cresource)], clear_before = True) elif outcome == False or outcome == None: self.status = _(u"{}: failed...").format(cresource) self.__notify(_(u"EDR Search"), [_(u"{}: failed...").format(cresource), _(u"To learn how to use the feature, send: !help search")], clear_before = True) else: self.status = _(u"{}: found").format(cresource) self.__notify(u"{}".format(cresource), outcome, clear_before = True) except ValueError: self.status = _(u"{}: failed...").format(cresource) self.__notify(_(u"EDR Search"), [_(u"{}: failed...").format(cresource), _(u"To learn how to use the feature, send: !help search")], clear_before = True) def __resource_found(self, resource, reference, radius, checker, result, grade): self.searching = False details = [] if result: distance = result['distance'] pretty_dist = _(u"{dist:.3g}").format(dist=distance) if distance < 50.0 else _(u"{dist}").format(dist=int(distance)) details.append(_(u"{} ({}LY, {})").format(result['name'], pretty_dist, '+' * grade)) edt = EDTime() if 'updateTime' in result: edt.from_js_epoch(result['updateTime'] * 1000) details.append(_(u"as of {}").format(edt.as_date())) if checker.hint(): details.append(checker.hint()) self.status = u"{}: {} ({}LY)".format(checker.name, result['name'], pretty_dist) copy(result["name"]) else: self.status = _(u"{}: nothing within [{}LY] of {}".format(checker.name, int(radius), reference)) checked = _("checked {} systems").format(checker.systems_counter) if checker.stations_counter: checked = _("checked {} systems").format(checker.systems_counter) details.append(_(u"nothing found within {}LY, {}.").format(int(radius), checked)) if checker.hint(): details.append(checker.hint()) self.__notify(_(u"{} near {}").format(checker.name, reference), details, clear_before = True)