Exemple #1
0
 def __init__(self,
              config_file='config/igm_config.v7.ini',
              user_config_file=[
                  'config/user_igm_config.v7.ini',
                  'config/user_igm_config.v6.ini'
              ]):
     self.config = cp.ConfigParser()
     self.fallback_config = cp.ConfigParser()
     self.fallback_config.read(utils2to3.abspathmaker(
         __file__, config_file))
     user_cfg_path = utils2to3.abspathmaker(__file__, user_config_file[0])
     if os.path.exists(user_cfg_path):
         EDRLog().log(
             u"Using user defined layout at {}.".format(
                 user_config_file[0]), "INFO")
         self.config.read(user_cfg_path)
     else:
         EDRLog().log(
             u"No user defined layout at {}, using {} instead.".format(
                 user_config_file[0], user_config_file[1]), "INFO")
         user_cfg_path = utils2to3.abspathmaker(__file__,
                                                user_config_file[1])
         if os.path.exists(user_cfg_path):
             EDRLog().log(
                 u"Using user defined layout at {}.".format(
                     user_config_file[1]), "INFO")
             self.config.read(user_cfg_path)
         else:
             EDRLog().log(
                 u"No user defined layout at {} or {}, using {} instead.".
                 format(user_config_file[0], user_config_file[1],
                        config_file), "INFO")
             self.config = self.fallback_config
Exemple #2
0
 def loud(self):
     self.snd_warn = NSSound.alloc(
     ).initWithContentsOfFile_byReference_(
         utils2to3.abspathmaker(__file__, 'sounds', 'snd_warn.wav'),
         False)
     self.snd_notify = NSSound.alloc(
     ).initWithContentsOfFile_byReference_(
         utils2to3.abspathmaker(__file__, 'sounds', 'snd_notify.wav'),
         False)
Exemple #3
0
 def __init__(self, tips_file=None):
     global DEFAULT_TIPS
     if tips_file:
         self.tips = json.loads(
             open(utils2to3.abspathmaker(__file__, tips_file)).read())
     else:
         self.tips = DEFAULT_TIPS
Exemple #4
0
 def __init__(self):
     path = utils2to3.abspathmaker(__file__, 'db', 'rawdepletables')
     try:
         self.db = sqlite3.connect(path)
         cursor = self.db.cursor()
         cursor.execute('''CREATE TABLE IF NOT EXISTS
                         hotspots(id INTEGER PRIMARY KEY, name TEXT, planet TEXT, gravity REAL, distance_to_arrival INTEGER, type TEXT, confirmed INTEGER DEFAULT 0, last_visit INTEGER DEFAULT 0)
                         ''')
         cursor.execute('''CREATE TABLE IF NOT EXISTS
                         concentrations(id INTEGER PRIMARY KEY, hotspotid INTEGER SECONDARY KEY, resource TEXT, concentration REAL)
                         ''')
         for hotspot in EDRRawDepletables.HOTSPOTS:
             check = cursor.execute(
                 "SELECT name, planet from hotspots where name=? and planet=?",
                 (hotspot[0:2]))
             test = check.fetchone()
             if not test:
                 cursor.execute(
                     'insert into hotspots(name, planet, gravity, distance_to_arrival, type, confirmed) values (?,?,?,?,?,?)',
                     hotspot)
         for concentration in EDRRawDepletables.CONCENTRATIONS:
             check = cursor.execute(
                 "SELECT hotspotid from concentrations where hotspotid=?",
                 (concentration[0:1]))
             if not check.fetchone():
                 cursor.execute(
                     'insert into concentrations(hotspotid, resource, concentration) values (?,?,?)',
                     concentration)
         self.db.commit()
     except:
         EDRLOG.log(u"Couldn't open/create the depletables database",
                    "ERROR")
         self.db = None
Exemple #5
0
class EDRLandables(object):
    MAPS = json.loads(
        open(utils2to3.abspathmaker(__file__, 'data',
                                    'landable-maps.json')).read())

    @staticmethod
    def map_for(star_system, location_name, location_type):
        if not star_system or star_system.lower() not in EDRLandables.MAPS:
            star_system = "*"
        c_star_system = star_system.lower()
        c_location_name = location_name.lower()
        c_location_type = location_type.lower()

        locations = EDRLandables.MAPS.get(c_star_system, {})
        if c_location_name not in locations:
            c_location_name = "*"

        landables = locations.get(c_location_name, {})
        the_map = landables.get(c_location_type, {})
        if not the_map:
            locations = EDRLandables.MAPS.get("*", {})
            landables = locations.get("*", {})
            the_map = landables.get(c_location_type,
                                    landables.get("soon tm", {}))
        return the_map
Exemple #6
0
 def __init__(self):
     path = utils2to3.abspathmaker(__file__, 'db', 'fleet')
     try:
         self.db = sqlite3.connect(path)
         cursor = self.db.cursor()
         cursor.execute('''CREATE TABLE IF NOT EXISTS
                     ships(id INTEGER PRIMARY KEY, type TEXT, localised TEXT, name TEXT,
                     star_system TEXT, ship_market_id INTEGER, value INTEGER, hot INTEGER, piloted INTEGER DEFAULT 0, eta INTEGER DEFAULT 0)'''
                        )
         cursor.execute('''CREATE TABLE IF NOT EXISTS
                     transits(id INTEGER PRIMARY KEY AUTOINCREMENT, ship_id INTEGER, eta INTEGER, source_system TEXT,
                     destination_system TEXT, source_market_id INTEGER, destination_market_id INTEGER)'''
                        )
         self.db.commit()
     except:
         EDRLOG.log(u"Couldn't open/create the fleet database", "ERROR")
         self.db = None
Exemple #7
0
class RESTFirebaseAuth(object):
    FIREBASE_ANON_AUTH_CACHE = utils2to3.abspathmaker(__file__, 'private',
                                                      'fbaa.v2.p')

    def __init__(self):
        self.email = ""
        self.password = ""
        self.auth = None
        self.anonymous = True
        try:
            with open(self.FIREBASE_ANON_AUTH_CACHE, 'rb') as handle:
                self.refresh_token = pickle.load(handle)
        except:
            self.refresh_token = None
        self.timestamp = None
        self.api_key = ""

    def authenticate(self):
        if self.api_key == "":
            EDRLOG.log(u"can't authenticate: empty api key.", "ERROR")
            return False

        if not self.__login():
            EDRLOG.log(u"Authentication failed (login)", "ERROR")
            self.__reset()
            return False

        if not self.__refresh_fb_token():
            EDRLOG.log(u"Authentication failed (FB token)", "ERROR")
            self.__reset()
            return False
        return True

    def __login(self):
        payload = {"returnSecureToken": True}

        endpoint = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key={}".format(
            self.api_key)
        self.anonymous = True

        if self.email != "" and self.password != "":
            payload["email"] = self.email
            payload["password"] = self.password
            self.anonymous = False
            self.refresh_token = None
            endpoint = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={}".format(
                self.api_key)

        if self.refresh_token:
            return True

        requestTime = datetime.datetime.now()
        resp = requests.post(endpoint, json=payload)
        if resp.status_code != requests.codes.ok:
            return False

        self.timestamp = requestTime
        auth = json.loads(resp.content)
        self.refresh_token = auth['refreshToken']
        if self.anonymous:
            try:
                with open(self.FIREBASE_ANON_AUTH_CACHE, 'wb') as handle:
                    pickle.dump(self.refresh_token,
                                handle,
                                protocol=pickle.HIGHEST_PROTOCOL)
            except:
                return False
        return True

    def __refresh_fb_token(self):
        payload = {
            "grant_type": "refresh_token",
            "refresh_token": self.refresh_token
        }
        endpoint = "https://securetoken.googleapis.com/v1/token?key={}".format(
            self.api_key)
        requestTime = datetime.datetime.now()
        resp = requests.post(endpoint, data=payload)
        if resp.status_code != requests.codes.ok:
            EDRLOG.log(
                u"Refresh of FB token failed. Status code={code}, content={content}"
                .format(code=resp.status_code, content=resp.content), "ERROR")
            return False

        self.auth = json.loads(resp.content)
        self.timestamp = requestTime
        self.refresh_token = self.auth["refresh_token"]

        return True

    def is_valid_auth_token(self):
        return (self.auth and 'expires_in' in self.auth
                and 'id_token' in self.auth)

    def is_auth_expiring(self):
        if not self.is_valid_auth_token():
            return True

        now = datetime.datetime.now()
        near_expiration = datetime.timedelta(
            seconds=int(self.auth['expires_in']) - 30)
        return (now - self.timestamp) > near_expiration

    def renew_auth_if_needed(self):
        if self.api_key == "":
            return False

        if self.is_auth_expiring():
            EDRLOG.log(
                u"Renewing authentication since the token will expire soon.",
                "INFO")
            self.clear_authentication()
            return self.authenticate()
        return True

    def id_token(self):
        if not self.renew_auth_if_needed():
            return None

        if not self.is_valid_auth_token():
            return None

        return self.auth['id_token']

    def uid(self):
        if not self.renew_auth_if_needed():
            return None

        if not self.is_valid_auth_token():
            return None

        return self.auth['user_id']

    def clear_authentication(self):
        self.auth = None
        self.timestamp = None

    def __reset(self):
        self.clear_authentication()
        try:
            with open(self.FIREBASE_ANON_AUTH_CACHE, 'rb') as handle:
                self.refresh_token = pickle.load(handle)
        except:
            self.refresh_token = None
Exemple #8
0
class EDRLegalRecords(object):
    EDR_LEGAL_RECORDS_CACHE = utils2to3.abspathmaker(__file__, 'cache', 'legal_records.v3.p')
    
    def __init__(self, server):
        self.server = server
        
        self.timespan = None
        self.records_check_interval = None
        config = EDRConfig()
        try:
            with open(self.EDR_LEGAL_RECORDS_CACHE, 'rb') as handle:
                self.records = pickle.load(handle)
        except:
            self.records = LRUCache(config.lru_max_size(), config.legal_records_max_age())
        
        self.timespan = config.legal_records_recent_threshold()
        self.records_check_interval = config.legal_records_check_interval()
    
    def persist(self):
        with open(self.EDR_LEGAL_RECORDS_CACHE, 'wb') as handle:
            pickle.dump(self.records, handle, protocol=pickle.HIGHEST_PROTOCOL)
    
    def summarize(self, cmdr_id):
        if not cmdr_id:
            EDRLOG.log(u"No cmdr_id, no records for {}".format(cmdr_id), "INFO")
            return None
        self.__update_records_if_stale(cmdr_id)
        records = self.records.get(cmdr_id)["records"] if self.records.has_key(cmdr_id) else None
        if not records:
            EDRLOG.log(u"No legal records for {}".format(cmdr_id), "INFO")
            return None
        
        EDRLOG.log(u"Got legal records for {}".format(cmdr_id), "INFO")
        overview = None
        (clean, wanted, bounties, recent_stats) = self.__process(records)
        timespan = EDTime.pretty_print_timespan(self.timespan, short=True, verbose=True)
        maxB = u""
        lastB = u""
        if recent_stats["maxBounty"]:
            max_bounty = EDFineOrBounty(recent_stats["maxBounty"]).pretty_print()
            maxB = _(u", max={} cr").format(max_bounty)

        if "last" in recent_stats and recent_stats["last"].get("value", None) and (recent_stats["last"].get("starSystem", "") not in ["", "unknown", "Unknown"]):
            tminus = EDTime.t_minus(recent_stats["last"]["timestamp"], short=True)
            last_bounty = EDFineOrBounty(recent_stats["last"]["value"]).pretty_print()
            lastB = _(u", last: {} cr in {} {}").format(last_bounty, recent_stats["last"]["starSystem"], tminus)
        
        # Translators: this is an overview of a cmdr's recent legal history for the 'last {}' days, number of clean and wanted scans, and optionally max and last bounties
        overview = _(u"[Past {}] clean:{} / wanted:{}{}{}").format(timespan, recent_stats["clean"], recent_stats["wanted"], maxB, lastB)
        return {"overview": overview, "clean": clean, "wanted": wanted, "bounties": bounties}

    def __are_records_stale_for_cmdr(self, cmdr_id):
        if self.records.get(cmdr_id) is None:
            return True
        last_updated = self.records.get(cmdr_id)["last_updated"]
        now = datetime.datetime.now()
        epoch_now = time.mktime(now.timetuple())
        epoch_updated = time.mktime(last_updated.timetuple())
        return (epoch_now - epoch_updated) > self.records_check_interval

    
    def __update_records_if_stale(self, cmdr_id):
        updated = False
        if self.__are_records_stale_for_cmdr(cmdr_id):
            now = datetime.datetime.now() 
            records = self.server.legal_stats(cmdr_id)
            self.records.set(cmdr_id, {"last_updated": now, "records": records})
            updated = True
        return updated

    def __process(self, legal_stats):
        last = self.__emptyMonthlyBag()
        clean = []
        wanted = []
        bounties = []       
        recent_stats = {"clean": 0, "wanted": 0, "maxBounty": 0, "last": {"value": 0, "timestamp": None, "starSystem": None}}
        now_date = datetime.datetime.now()
        currentYear = now_date.year
        currentMonth = now_date.month
        orderly = self.__orderlyMonthNo()
        monthSpan = int(min(12, round(1 + (self.timespan / (60*60*24) - now_date.day)/30)))
        
        for m in orderly:
            if (m not in legal_stats):
                clean.append(0)
                wanted.append(0)
                bounties.append(0)
                continue

            wayTooOld = int(legal_stats[m]["year"]) < currentYear-1
            tooOld = (int(legal_stats[m]["year"]) == currentYear-1) and int(m) <= currentMonth
            if (wayTooOld or tooOld):
                clean.append(0)
                wanted.append(0)
                bounties.append(0)
                continue
            
            if (m in orderly[12-monthSpan:]):
                recent_stats["clean"] += legal_stats[m]["clean"]
                recent_stats["wanted"] += legal_stats[m]["wanted"]
                if legal_stats[m]["max"]:
                    recent_stats["maxBounty"] = max(recent_stats["maxBounty"], legal_stats[m]["max"].get("value", 0))
                if legal_stats[m]["last"] and legal_stats[m]["last"].get("value", 0) >= recent_stats["last"]["value"]:
                    recent_stats["last"] = legal_stats[m]["last"]

            clean.append(legal_stats[m]["clean"])
            wanted.append(legal_stats[m]["wanted"])
            last[m] = legal_stats[m]["last"]
            bounties.append(legal_stats[m]["max"]["value"])

        return (clean, wanted, bounties, recent_stats)

    @staticmethod
    def __orderlyMonthNo():
        currentMonthIDX0 = datetime.datetime.now().month-1
        return [ str((((currentMonthIDX0 - i) %12) + 12)%12)  for i in range(11,-1,-1)]
    
    @staticmethod
    def __emptyMonthlyBag():
        return {
            '0': None,
            '1': None,
            '2': None,
            '3': None,
            '4': None,
            '5': None,
            '6': None,
            '7': None,
            '8': None,
            '9': None,
            '10': None,
            '11': None
        }
Exemple #9
0
class EDRSystems(object):
    EDR_SYSTEMS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                               'systems.v4.p')
    EDR_RAW_MATERIALS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                     'raw_materials.v1.p')
    EDSM_BODIES_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                               'edsm_bodies.v1.p')
    EDSM_SYSTEMS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                'edsm_systems.v3.p')
    EDSM_STATIONS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                 'edsm_stations.v1.p')
    EDSM_SYSTEMS_WITHIN_RADIUS_CACHE = utils2to3.abspathmaker(
        __file__, 'cache', 'edsm_systems_radius.v2.p')
    EDSM_FACTIONS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                 'edsm_factions.v1.p')
    EDSM_TRAFFIC_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                'edsm_traffic.v1.p')
    EDSM_DEATHS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                               'edsm_deaths.v1.p')
    EDR_NOTAMS_CACHE = utils2to3.abspathmaker(__file__, 'cache', 'notams.v2.p')
    EDR_SITREPS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                               'sitreps.v3.p')
    EDR_TRAFFIC_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                               'traffic.v2.p')
    EDR_CRIMES_CACHE = utils2to3.abspathmaker(__file__, 'cache', 'crimes.v2.p')

    def __init__(self, server):
        self.reasonable_sc_distance = 1500
        self.reasonable_hs_radius = 50
        edr_config = edrconfig.EDRConfig()

        try:
            with open(self.EDR_SYSTEMS_CACHE, 'rb') as handle:
                self.systems_cache = pickle.load(handle)
        except:
            self.systems_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.systems_max_age())

        try:
            with open(self.EDR_RAW_MATERIALS_CACHE, 'rb') as handle:
                self.materials_cache = pickle.load(handle)
        except:
            self.materials_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.materials_max_age())

        try:
            with open(self.EDR_NOTAMS_CACHE, 'rb') as handle:
                self.notams_cache = pickle.load(handle)
        except:
            self.notams_cache = lrucache.LRUCache(edr_config.lru_max_size(),
                                                  edr_config.notams_max_age())

        try:
            with open(self.EDR_SITREPS_CACHE, 'rb') as handle:
                self.sitreps_cache = pickle.load(handle)
        except:
            self.sitreps_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.sitreps_max_age())

        try:
            with open(self.EDR_CRIMES_CACHE, 'rb') as handle:
                self.crimes_cache = pickle.load(handle)
        except:
            self.crimes_cache = lrucache.LRUCache(edr_config.lru_max_size(),
                                                  edr_config.crimes_max_age())

        try:
            with open(self.EDR_TRAFFIC_CACHE, 'rb') as handle:
                self.traffic_cache = pickle.load(handle)
        except:
            self.traffic_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.traffic_max_age())

        try:
            with open(self.EDSM_SYSTEMS_CACHE, 'rb') as handle:
                self.edsm_systems_cache = pickle.load(handle)
        except:
            self.edsm_systems_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.edsm_systems_max_age())

        try:
            with open(self.EDSM_BODIES_CACHE, 'rb') as handle:
                self.edsm_bodies_cache = pickle.load(handle)
        except:
            self.edsm_bodies_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.edsm_bodies_max_age())

        try:
            with open(self.EDSM_STATIONS_CACHE, 'rb') as handle:
                self.edsm_stations_cache = pickle.load(handle)
        except:
            self.edsm_stations_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.edsm_stations_max_age())

        try:
            with open(self.EDSM_FACTIONS_CACHE, 'rb') as handle:
                self.edsm_factions_cache = pickle.load(handle)
        except:
            self.edsm_factions_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.edsm_factions_max_age())

        try:
            with open(self.EDSM_SYSTEMS_WITHIN_RADIUS_CACHE, 'rb') as handle:
                self.edsm_systems_within_radius_cache = pickle.load(handle)
        except:
            self.edsm_systems_within_radius_cache = lrucache.LRUCache(
                edr_config.edsm_within_radius_max_size(),
                edr_config.edsm_systems_max_age())

        try:
            with open(self.EDSM_TRAFFIC_CACHE, 'rb') as handle:
                self.edsm_traffic_cache = pickle.load(handle)
        except:
            self.edsm_traffic_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.edsm_traffic_max_age())

        try:
            with open(self.EDSM_DEATHS_CACHE, 'rb') as handle:
                self.edsm_deaths_cache = pickle.load(handle)
        except:
            self.edsm_deaths_cache = lrucache.LRUCache(
                edr_config.lru_max_size(), edr_config.edsm_deaths_max_age())

        self.reports_check_interval = edr_config.reports_check_interval()
        self.notams_check_interval = edr_config.notams_check_interval()
        self.timespan = edr_config.sitreps_timespan()
        self.timespan_notams = edr_config.notams_timespan()
        self.server = server
        self.edsm_server = edsmserver.EDSMServer()

    def system_id(self, star_system, may_create=False, coords=None):
        if not star_system:
            return None
        system = self.systems_cache.get(star_system.lower())
        cached = self.systems_cache.has_key(star_system.lower())
        if cached and system is None:
            EDRLOG.log(
                u"Temporary entry for System {} in the cache".format(
                    star_system), "DEBUG")
            return None

        if cached and system:
            sid = list(system)[0]
            if may_create and coords and not "coords" in system[sid]:
                EDRLOG.log(
                    u"System {} is in the cache with id={} but missing coords".
                    format(star_system, sid), "DEBUG")
                system = self.server.system(star_system, may_create, coords)
                if system:
                    self.systems_cache.set(star_system.lower(), system)
                sid = list(system)[0]
            return sid

        system = self.server.system(star_system, may_create, coords)
        if system:
            self.systems_cache.set(star_system.lower(), system)
            sid = list(system)[0]
            EDRLOG.log(u"Cached {}'s info with id={}".format(star_system, sid),
                       "DEBUG")
            return sid

        self.systems_cache.set(star_system.lower(), None)
        EDRLOG.log(
            u"No match on EDR. Temporary entry to be nice on EDR's server.",
            "DEBUG")
        return None

    def are_stations_stale(self, star_system):
        if not star_system:
            return False
        return self.edsm_stations_cache.is_stale(star_system.lower())

    def station(self, star_system, station_name, station_type):
        stations = self.stations_in_system(star_system)
        for station in stations:
            if station["name"] == station_name:
                return station
        return None

    def stations_in_system(self, star_system):
        if not star_system:
            return None
        stations = self.edsm_stations_cache.get(star_system.lower())
        cached = self.edsm_stations_cache.has_key(star_system.lower())
        if cached or stations:
            EDRLOG.log(
                u"Stations for system {} are in the cache.".format(
                    star_system), "DEBUG")
            return stations

        stations = self.edsm_server.stations_in_system(star_system)
        if stations:
            self.edsm_stations_cache.set(star_system.lower(), stations)
            EDRLOG.log(u"Cached {}'s stations".format(star_system), "DEBUG")
            return stations

        self.edsm_stations_cache.set(star_system.lower(), None)
        EDRLOG.log(
            u"No match on EDSM. Temporary entry to be nice on EDSM's server.",
            "DEBUG")
        return None

    def persist(self):
        with open(self.EDR_SYSTEMS_CACHE, 'wb') as handle:
            pickle.dump(self.systems_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_RAW_MATERIALS_CACHE, 'wb') as handle:
            pickle.dump(self.materials_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_NOTAMS_CACHE, 'wb') as handle:
            pickle.dump(self.notams_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_SITREPS_CACHE, 'wb') as handle:
            pickle.dump(self.sitreps_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_TRAFFIC_CACHE, 'wb') as handle:
            pickle.dump(self.traffic_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_CRIMES_CACHE, 'wb') as handle:
            pickle.dump(self.crimes_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDSM_SYSTEMS_CACHE, 'wb') as handle:
            pickle.dump(self.edsm_systems_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDSM_BODIES_CACHE, 'wb') as handle:
            pickle.dump(self.edsm_bodies_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDSM_STATIONS_CACHE, 'wb') as handle:
            pickle.dump(self.edsm_stations_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDSM_SYSTEMS_WITHIN_RADIUS_CACHE, 'wb') as handle:
            pickle.dump(self.edsm_systems_within_radius_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDSM_FACTIONS_CACHE, 'wb') as handle:
            pickle.dump(self.edsm_factions_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDSM_TRAFFIC_CACHE, 'wb') as handle:
            pickle.dump(self.edsm_traffic_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDSM_DEATHS_CACHE, 'wb') as handle:
            pickle.dump(self.edsm_deaths_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

    def distance(self, source_system, destination_system):
        if source_system == destination_system:
            return 0
        source = self.system(source_system)
        destination = self.system(destination_system)

        if source and destination:
            source_coords = source[0]["coords"]
            dest_coords = destination[0]["coords"]
            return sqrt((dest_coords["x"] - source_coords["x"])**2 +
                        (dest_coords["y"] - source_coords["y"])**2 +
                        (dest_coords["z"] - source_coords["z"])**2)
        raise ValueError('Unknown system')

    def distance_with_coords(self, source_system, dest_coords):
        source = self.system(source_system)

        if source:
            source_coords = source[0]["coords"]
            return sqrt((dest_coords["x"] - source_coords["x"])**2 +
                        (dest_coords["y"] - source_coords["y"])**2 +
                        (dest_coords["z"] - source_coords["z"])**2)
        raise ValueError('Unknown system')

    def system(self, name):
        if not name:
            return None

        the_system = self.edsm_systems_cache.get(name.lower())
        if the_system:
            return the_system

        the_system = self.edsm_server.system(name)
        if the_system:
            self.edsm_systems_cache.set(name.lower(), the_system)
            return the_system

        return None

    def materials_info(self, system_name, body_name, info):
        if not system_name or not body_name:
            return None

        self.materials_cache.set(
            u"{}:{}".format(system_name.lower(), body_name.lower()), info)

    def materials_on(self, system_name, body_name):
        if not system_name or not body_name:
            return None

        materials = self.materials_cache.get(u"{}:{}".format(
            system_name.lower(), body_name.lower()))
        if not materials:
            # TODO it would be nice to obtain data from other cmdrs...
            return None
        return materials

    def body(self, system_name, body_name):
        if not system_name or not body_name:
            return None

        bodies = self.edsm_bodies_cache.get(system_name.lower())
        if not bodies:
            bodies = self.edsm_server.bodies(system_name)
            if bodies:
                self.edsm_bodies_cache.set(system_name.lower(), bodies)

        if not bodies:
            return None

        for body in bodies:
            if body.get("name", "").lower() == body_name.lower():
                return body
        return None

    def are_factions_stale(self, star_system):
        if not star_system:
            return False
        return self.edsm_factions_cache.is_stale(star_system.lower())

    def __factions(self, star_system):
        if not star_system:
            return None
        factions = self.edsm_factions_cache.get(star_system.lower())
        cached = self.edsm_factions_cache.has_key(star_system.lower())
        if cached or factions:
            EDRLOG.log(
                u"Factions for system {} are in the cache.".format(
                    star_system), "DEBUG")
            return factions

        EDRLOG.log(
            u"Factions for system {} are NOT in the cache.".format(
                star_system), "DEBUG")
        factions = self.edsm_server.factions_in_system(star_system)
        if factions:
            self.edsm_factions_cache.set(star_system.lower(), factions)
            EDRLOG.log(u"Cached {}'s factions".format(star_system), "DEBUG")
            return factions

        self.edsm_factions_cache.set(star_system.lower(), None)
        EDRLOG.log(
            u"No match on EDSM. Temporary entry to be nice on EDSM's server.",
            "DEBUG")
        return None

    def system_state(self, star_system):
        factions = self.__factions(star_system)
        if not factions:
            return (None, None)

        if not factions.get('controllingFaction', None) or not factions.get(
                'factions', None):
            EDRLOG.log(
                u"Badly formed factions data for system {}.".format(
                    star_system), "INFO")
            return (None, None)

        controlling_faction_id = factions['controllingFaction']['id']
        all_factions = factions['factions']
        state = None
        updated = None
        for faction in all_factions:
            if faction['id'] == controlling_faction_id:
                state = faction['state']
                updated = faction['lastUpdate']
                break

        return (state, updated)

    def system_allegiance(self, star_system):
        factions = self.__factions(star_system)
        if not factions:
            return None

        if not factions.get('controllingFaction', None):
            EDRLOG.log(
                u"Badly formed factions data for system {}.".format(
                    star_system), "INFO")
            return None

        return factions['controllingFaction'].get('allegiance', None)

    def transfer_time(self, origin, destination):
        dist = self.distance(origin, destination)
        return int(ceil(dist * 9.75 + 300))

    def jumping_time(self,
                     origin,
                     destination,
                     jump_range,
                     seconds_per_jump=55):
        dist = self.distance(origin, destination)
        return int(ceil(dist / jump_range) * seconds_per_jump)

    def timespan_s(self):
        return edtime.EDTime.pretty_print_timespan(self.timespan,
                                                   short=True,
                                                   verbose=True)

    def crimes_t_minus(self, star_system):
        if self.has_sitrep(star_system):
            system_reports = self.sitreps_cache.get(
                self.system_id(star_system))
            if "latestCrime" in system_reports:
                return edtime.EDTime.t_minus(system_reports["latestCrime"])
        return None

    def traffic_t_minus(self, star_system):
        if self.has_sitrep(star_system):
            system_reports = self.sitreps_cache.get(
                self.system_id(star_system))
            if "latestTraffic" in system_reports:
                return edtime.EDTime.t_minus(system_reports["latestTraffic"])
        return None

    def has_sitrep(self, star_system):
        if not star_system:
            return False
        self.__update_if_stale()
        sid = self.system_id(star_system)
        return self.sitreps_cache.has_key(sid)

    def has_notams(self, star_system, may_create=False, coords=None):
        self.__update_if_stale()
        sid = self.system_id(star_system, may_create, coords)
        return self.notams_cache.has_key(sid)

    def __has_active_notams(self, system_id):
        self.__update_if_stale()
        if not self.notams_cache.has_key(system_id):
            return False
        return len(self.__active_notams_for_sid(system_id)) > 0

    def active_notams(self, star_system, may_create=False, coords=None):
        if self.has_notams(star_system, may_create, coords=None):
            return self.__active_notams_for_sid(self.system_id(star_system))
        return None

    def __active_notams_for_sid(self, system_id):
        active_notams = []
        entry = self.notams_cache.get(system_id)
        all_notams = entry.get("NOTAMs", {})
        js_epoch_now = edtime.EDTime.js_epoch_now()
        for notam in all_notams:
            active = True
            if "from" in notam:
                active &= notam["from"] <= js_epoch_now
            if "until" in notam:
                active &= js_epoch_now <= notam["until"]
            if active and "text" in notam:
                EDRLOG.log(u"Active NOTAM: {}".format(notam["text"]), "DEBUG")
                active_notams.append(_edr(notam["text"]))
            elif active and "l10n" in notam:
                EDRLOG.log(
                    u"Active NOTAM: {}".format(notam["l10n"]["default"]),
                    "DEBUG")
                active_notams.append(_edr(notam["l10n"]))
        return active_notams

    def systems_with_active_notams(self):
        summary = []
        self.__update_if_stale()
        systems_ids = list(self.notams_cache.keys()).copy()
        for sid in systems_ids:
            entry = self.notams_cache.get(sid)
            if not entry:
                continue
            star_system = entry.get("name", None)
            if star_system and self.__has_active_notams(sid):
                summary.append(star_system)

        return summary

    def has_recent_activity(self, system_name, pledged_to=None):
        return self.has_recent_traffic(system_name) or self.has_recent_crimes(
            system_name) or self.has_recent_outlaws(
                system_name) or pledged_to and self.has_recent_enemies(
                    system_name, pledged_to)

    def systems_with_recent_activity(self, pledged_to=None):
        systems_with_recent_crimes = {}
        systems_with_recent_traffic = {}
        systems_with_recent_outlaws = {}
        systems_with_recent_enemies = {}
        self.__update_if_stale()
        systems_ids = (list(self.sitreps_cache.keys())).copy()
        for sid in systems_ids:
            sitrep = self.sitreps_cache.get(sid)
            star_system = sitrep.get("name", None) if sitrep else None
            if self.has_recent_outlaws(star_system):
                systems_with_recent_outlaws[star_system] = sitrep[
                    "latestOutlaw"]
            elif pledged_to and self.has_recent_enemies(
                    star_system, pledged_to):
                latestEnemy = "latestEnemy_{}".format(
                    self.server.nodify(pledged_to))
                systems_with_recent_enemies[star_system] = sitrep[latestEnemy]
            elif self.has_recent_crimes(star_system):
                systems_with_recent_crimes[star_system] = sitrep["latestCrime"]
            elif self.has_recent_traffic(star_system):
                systems_with_recent_traffic[star_system] = sitrep[
                    "latestTraffic"]

        summary = {}
        summary_outlaws = []
        systems_with_recent_outlaws = sorted(
            systems_with_recent_outlaws.items(),
            key=lambda t: t[1],
            reverse=True)
        for system in systems_with_recent_outlaws:
            summary_outlaws.append(u"{} {}".format(
                system[0], edtime.EDTime.t_minus(system[1], short=True)))
        if summary_outlaws:
            # Translators: this is for the sitreps feature; it's the title of a section to show systems with sighted outlaws
            summary[_c(u"sitreps section|✪ Outlaws")] = summary_outlaws

        if pledged_to:
            summary_enemies = []
            systems_with_recent_enemies = sorted(
                systems_with_recent_enemies.items(),
                key=lambda t: t[1],
                reverse=True)
            for system in systems_with_recent_enemies:
                summary_enemies.append(u"{} {}".format(
                    system[0], edtime.EDTime.t_minus(system[1], short=True)))
            if summary_enemies:
                # Translators: this is for the sitreps feature; it's the title of a section to show systems with sighted enemies (powerplay)
                summary[_c(u"sitreps section|✪ Enemies")] = summary_enemies

        summary_crimes = []
        systems_with_recent_crimes = sorted(systems_with_recent_crimes.items(),
                                            key=lambda t: t[1],
                                            reverse=True)
        for system in systems_with_recent_crimes:
            summary_crimes.append(u"{} {}".format(
                system[0], edtime.EDTime.t_minus(system[1], short=True)))
        if summary_crimes:
            # Translators: this is for the sitreps feature; it's the title of a section to show systems with reported crimes
            summary[_c(u"sitreps section|✪ Crimes")] = summary_crimes

        summary_traffic = []
        systems_with_recent_traffic = sorted(
            systems_with_recent_traffic.items(),
            key=lambda t: t[1],
            reverse=True)
        for system in systems_with_recent_traffic:
            summary_traffic.append(u"{} {}".format(
                system[0], edtime.EDTime.t_minus(system[1], short=True)))
        if summary_traffic:
            # Translators: this is for the sitreps feature; it's the title of a section to show systems with traffic
            summary[_c(u"sitreps section|✪ Traffic")] = summary_traffic

        return summary

    def has_recent_crimes(self, star_system):
        if self.has_sitrep(star_system):
            system_reports = self.sitreps_cache.get(
                self.system_id(star_system))
            if system_reports is None or "latestCrime" not in system_reports:
                return False

            edr_config = edrconfig.EDRConfig()
            return self.is_recent(system_reports["latestCrime"],
                                  edr_config.crimes_recent_threshold())
        return False

    def has_recent_outlaws(self, star_system):
        if self.has_sitrep(star_system):
            system_reports = self.sitreps_cache.get(
                self.system_id(star_system))
            if system_reports is None or "latestOutlaw" not in system_reports:
                return False

            edr_config = edrconfig.EDRConfig()
            return self.is_recent(
                system_reports["latestOutlaw"],
                edr_config.opponents_recent_threshold("outlaws"))
        return False

    def has_recent_enemies(self, star_system, pledged_to):
        if self.has_sitrep(star_system):
            system_reports = self.sitreps_cache.get(
                self.system_id(star_system))
            latestEnemy = "latestEnemy_{}".format(
                self.server.nodify(pledged_to))
            if system_reports is None or latestEnemy not in system_reports:
                return False

            edr_config = edrconfig.EDRConfig()
            return self.is_recent(
                system_reports[latestEnemy],
                edr_config.opponents_recent_threshold("enemies"))
        return False

    def recent_crimes(self, star_system):
        sid = self.system_id(star_system)
        if not sid:
            return None
        recent_crimes = None
        if self.has_recent_crimes(star_system):
            if not self.crimes_cache.has_key(sid) or (
                    self.crimes_cache.has_key(sid)
                    and self.crimes_cache.is_stale(sid)):
                recent_crimes = self.server.recent_crimes(sid, self.timespan)
                if recent_crimes:
                    self.crimes_cache.set(sid, recent_crimes)
            else:
                recent_crimes = self.crimes_cache.get(sid)
        return recent_crimes

    def has_recent_traffic(self, star_system):
        if self.has_sitrep(star_system):
            system_reports = self.sitreps_cache.get(
                self.system_id(star_system))
            if system_reports is None or "latestTraffic" not in system_reports:
                return False

            edr_config = edrconfig.EDRConfig()
            return self.is_recent(system_reports["latestTraffic"],
                                  edr_config.traffic_recent_threshold())
        return False

    def recent_traffic(self, star_system):
        sid = self.system_id(star_system)
        if not sid:
            return None
        recent_traffic = None
        if self.has_recent_traffic(star_system):
            if not self.traffic_cache.has_key(sid) or (
                    self.traffic_cache.has_key(sid)
                    and self.traffic_cache.is_stale(sid)):
                recent_traffic = self.server.recent_traffic(sid, self.timespan)
                if recent_traffic:
                    self.traffic_cache.set(sid, recent_traffic)
            else:
                recent_traffic = self.traffic_cache.get(sid)
        return recent_traffic

    def summarize_deaths_traffic(self, star_system):
        if not star_system:
            return None

        traffic = self.edsm_traffic_cache.get(star_system.lower())
        if traffic is None:
            traffic = self.edsm_server.traffic(star_system)
        self.edsm_traffic_cache.set(star_system.lower(), traffic)

        deaths = self.edsm_deaths_cache.get(star_system.lower())
        if deaths is None:
            deaths = self.edsm_server.deaths(star_system)
        self.edsm_deaths_cache.set(star_system.lower(), traffic)

        if not deaths and not traffic:
            return None

        zero = {"total": 0, "week": 0, "day": 0}
        deaths = {
            s: self.__pretty_print_number(v)
            for s, v in deaths.get("deaths", zero).items()
        }
        traffic = {
            s: self.__pretty_print_number(v)
            for s, v in traffic.get("traffic", {}).items()
        }

        if traffic == {}:
            return None

        return "Deaths / Traffic: [Day {}/{}]   [Week {}/{}]  [All {}/{}]".format(
            deaths.get("day", 0), traffic.get("day"), deaths.get("week", 0),
            traffic.get("week"), deaths.get("total"), traffic.get("total"))

    @staticmethod
    def __pretty_print_number(number):
        #TODO move out and dedup bounty's code.
        readable = u""
        if number >= 10000000000:
            # Translators: this is a short representation for a bounty >= 10 000 000 000 credits (b stands for billion)
            readable = _(u"{} b").format(number // 1000000000)
        elif number >= 1000000000:
            # Translators: this is a short representation for a bounty >= 1 000 000 000 credits (b stands for billion)
            readable = _(u"{:.1f} b").format(number / 1000000000.0)
        elif number >= 10000000:
            # Translators: this is a short representation for a bounty >= 10 000 000 credits (m stands for million)
            readable = _(u"{} m").format(number // 1000000)
        elif number > 1000000:
            # Translators: this is a short representation for a bounty >= 1 000 000 credits (m stands for million)
            readable = _(u"{:.1f} m").format(number / 1000000.0)
        elif number >= 10000:
            # Translators: this is a short representation for a bounty >= 10 000 credits (k stands for kilo, i.e. thousand)
            readable = _(u"{} k").format(number // 1000)
        elif number >= 1000:
            # Translators: this is a short representation for a bounty >= 1000 credits (k stands for kilo, i.e. thousand)
            readable = _(u"{:.1f} k").format(number / 1000.0)
        else:
            # Translators: this is a short representation for a bounty < 1000 credits (i.e. shows the whole bounty, unabbreviated)
            readable = _(u"{}").format(number)
        return readable

    def summarize_recent_activity(self, star_system, powerplay=None):
        #TODO refactor/simplify this mess ;)
        summary = {}
        wanted_cmdrs = {}
        enemies = {}
        if self.has_recent_traffic(star_system):
            summary_sighted = []
            recent_traffic = self.recent_traffic(star_system)
            if recent_traffic is not None:  # Should always be true... simplify. TODO
                summary_traffic = collections.OrderedDict()
                for traffic in recent_traffic:
                    previous_timestamp = summary_traffic.get(
                        traffic["cmdr"], 0)
                    if traffic["timestamp"] < previous_timestamp:
                        continue
                    karma = traffic.get("karma", 0)
                    if not karma > 0:
                        karma = min(karma, traffic.get("dkarma", 0))
                    bounty = EDFineOrBounty(traffic.get("bounty", 0))
                    enemy = traffic.get("enemy", False)
                    by_pledge = traffic.get("byPledge", None)
                    if karma < 0 or bounty.is_significant():
                        wanted_cmdrs[traffic["cmdr"]] = [
                            traffic["timestamp"], karma
                        ]
                    elif powerplay and enemy and powerplay == by_pledge:
                        enemies[traffic["cmdr"]] = [
                            traffic["timestamp"], karma
                        ]
                    else:
                        summary_traffic[traffic["cmdr"]] = traffic["timestamp"]
                for cmdr in summary_traffic:
                    summary_sighted.append(u"{} {}".format(
                        cmdr,
                        edtime.EDTime.t_minus(summary_traffic[cmdr],
                                              short=True)))
                if summary_sighted:
                    # Translators: this is for the sitrep feature; it's a section to show sighted cmdrs in the system of interest
                    summary[_c(u"sitrep section|✪ Sighted")] = summary_sighted

        if self.has_recent_crimes(star_system):
            summary_interdictors = []
            summary_destroyers = []
            recent_crimes = self.recent_crimes(star_system)
            if recent_crimes is not None:  # Should always be true... simplify. TODO
                summary_crimes = collections.OrderedDict()
                for crime in recent_crimes:
                    lead_name = crime["criminals"][0]["name"]
                    if lead_name not in summary_crimes or crime[
                            "timestamp"] > summary_crimes[lead_name][0]:
                        summary_crimes[lead_name] = [
                            crime["timestamp"], crime["offence"]
                        ]
                        for criminal in crime["criminals"]:
                            previous_timestamp = wanted_cmdrs[
                                criminal["name"]][0] if criminal[
                                    "name"] in wanted_cmdrs else 0
                            previous_timestamp = max(
                                previous_timestamp, enemies[criminal["name"]]
                                [0]) if criminal["name"] in enemies else 0
                            if previous_timestamp > crime["timestamp"]:
                                continue
                            karma = criminal.get("karma", 0)
                            if not karma > 0:
                                karma = min(karma, criminal.get("dkarma", 0))
                            bounty = EDFineOrBounty(traffic.get("bounty", 0))
                            enemy = traffic.get("enemy", False)
                            by_pledge = traffic.get("byPledge", None)
                            if karma < 0 or bounty.is_significant():
                                wanted_cmdrs[criminal["name"]] = [
                                    crime["timestamp"], karma
                                ]
                            elif powerplay and enemy and powerplay == by_pledge:
                                enemies[traffic["cmdr"]] = [
                                    traffic["timestamp"], karma
                                ]
                for criminal in summary_crimes:
                    if summary_crimes[criminal][1] == "Murder":
                        summary_destroyers.append(u"{} {}".format(
                            criminal,
                            edtime.EDTime.t_minus(summary_crimes[criminal][0],
                                                  short=True)))
                    elif summary_crimes[criminal][1] in [
                            "Interdicted", "Interdiction"
                    ]:
                        summary_interdictors.append(u"{} {}".format(
                            criminal,
                            edtime.EDTime.t_minus(summary_crimes[criminal][0],
                                                  short=True)))
                if summary_interdictors:
                    # Translators: this is for the sitrep feature; it's a section to show cmdrs who have been reported as interdicting another cmdr in the system of interest
                    summary[_c(u"sitrep section|✪ Interdictors"
                               )] = summary_interdictors
                if summary_destroyers:
                    # Translators: this is for the sitrep feature; it's a section to show cmdrs who have been reported as responsible for destroying the ship of another cmdr in the system of interest; use a judgement-neutral term
                    summary[_c(
                        u"sitreps section|✪ Destroyers")] = summary_destroyers

        wanted_cmdrs = sorted(wanted_cmdrs.items(),
                              key=operator.itemgetter(1),
                              reverse=True)
        if wanted_cmdrs:
            summary_wanted = []
            for wanted in wanted_cmdrs:
                summary_wanted.append(u"{} {}".format(
                    wanted[0], edtime.EDTime.t_minus(wanted[1][0],
                                                     short=True)))
            if summary_wanted:
                # Translators: this is for the sitrep feature; it's a section to show wanted cmdrs who have been sighted in the system of interest
                summary[_c(u"sitreps section|✪ Outlaws")] = summary_wanted

        enemies = sorted(enemies.items(),
                         key=operator.itemgetter(1),
                         reverse=True)
        if enemies:
            summary_enemies = []
            for enemy in enemies:
                summary_enemies.append(u"{} {}".format(
                    enemies[0], edtime.EDTime.t_minus(enemies[1][0],
                                                      short=True)))
            if summary_enemies:
                # Translators: this is for the sitrep feature; it's a section to show enemy cmdrs who have been sighted in the system of interest
                summary[_c(u"sitreps section|✪ Enemies")] = summary_enemies

        return summary

    def search_interstellar_factors(self,
                                    star_system,
                                    callback,
                                    with_large_pad=True,
                                    override_radius=None,
                                    override_sc_distance=None,
                                    permits=[]):
        checker = edrservicecheck.EDRStationServiceCheck(
            'Interstellar Factors Contact')
        checker.name = 'Interstellar Factors Contact'
        checker.hint = 'Look for low security systems, or stations run by an anarchy faction regardless of system security'
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_raw_trader(self,
                          star_system,
                          callback,
                          with_large_pad=True,
                          override_radius=None,
                          override_sc_distance=None,
                          permits=[]):
        checker = edrservicecheck.EDRRawTraderCheck()
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_encoded_trader(self,
                              star_system,
                              callback,
                              with_large_pad=True,
                              override_radius=None,
                              override_sc_distance=None,
                              permits=[]):
        checker = edrservicecheck.EDREncodedTraderCheck()
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_manufactured_trader(self,
                                   star_system,
                                   callback,
                                   with_large_pad=True,
                                   override_radius=None,
                                   override_sc_distance=None,
                                   permits=[]):
        checker = edrservicecheck.EDRManufacturedTraderCheck()
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_black_market(self,
                            star_system,
                            callback,
                            with_large_pad=True,
                            override_radius=None,
                            override_sc_distance=None,
                            permits=[]):
        checker = edrservicecheck.EDRBlackMarketCheck()
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_staging_station(self, star_system, callback, permits=[]):
        checker = edrservicecheck.EDRStagingCheck(15)
        self.__search_a_service(star_system,
                                callback,
                                checker,
                                with_large_pad=True,
                                override_radius=15,
                                permits=permits)

    def search_shipyard(self,
                        star_system,
                        callback,
                        with_large_pad=True,
                        override_radius=None,
                        override_sc_distance=None,
                        permits=[]):
        checker = edrservicecheck.EDRStationFacilityCheck('Shipyard')
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_outfitting(self,
                          star_system,
                          callback,
                          with_large_pad=True,
                          override_radius=None,
                          override_sc_distance=None,
                          permits=[]):
        checker = edrservicecheck.EDRStationFacilityCheck('Outfitting')
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_market(self,
                      star_system,
                      callback,
                      with_large_pad=True,
                      override_radius=None,
                      override_sc_distance=None,
                      permits=[]):
        checker = edrservicecheck.EDRStationFacilityCheck('Market')
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_human_tech_broker(self,
                                 star_system,
                                 callback,
                                 with_large_pad=True,
                                 override_radius=None,
                                 override_sc_distance=None,
                                 permits=[]):
        checker = edrservicecheck.EDRHumanTechBrokerCheck()
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def search_guardian_tech_broker(self,
                                    star_system,
                                    callback,
                                    with_large_pad=True,
                                    override_radius=None,
                                    override_sc_distance=None,
                                    permits=[]):
        checker = edrservicecheck.EDRGuardianTechBrokerCheck()
        self.__search_a_service(star_system, callback, checker, with_large_pad,
                                override_radius, override_sc_distance, permits)

    def __search_a_service(self,
                           star_system,
                           callback,
                           checker,
                           with_large_pad=True,
                           override_radius=None,
                           override_sc_distance=None,
                           permits=[]):
        sc_distance = override_sc_distance or self.reasonable_sc_distance
        sc_distance = max(250, sc_distance)
        radius = override_radius or self.reasonable_hs_radius
        radius = min(60, radius)

        finder = edrservicefinder.EDRServiceFinder(star_system, checker, self,
                                                   callback)
        finder.with_large_pad(with_large_pad)
        finder.within_radius(radius)
        finder.within_supercruise_distance(sc_distance)
        finder.permits_in_possesion(permits)
        finder.start()

    def systems_within_radius(self, star_system, override_radius=None):
        if not star_system:
            return None

        radius = override_radius or self.reasonable_hs_radius
        key = u"{}@{}".format(star_system.lower(), radius)
        systems = self.edsm_systems_within_radius_cache.get(key)
        cached = self.edsm_systems_within_radius_cache.has_key(key)
        if cached:
            if not systems:
                EDRLOG.log(
                    u"Systems within {} of system {} are not available for a while."
                    .format(radius, star_system), "DEBUG")
                return None
            else:
                EDRLOG.log(
                    u"Systems within {} of system {} are in the cache.".format(
                        radius, star_system), "DEBUG")
                return sorted(systems, key=lambda i: i['distance'])

        systems = self.edsm_server.systems_within_radius(star_system, radius)
        if systems:
            systems = sorted(systems, key=lambda i: i['distance'])
            self.edsm_systems_within_radius_cache.set(key, systems)
            EDRLOG.log(
                u"Cached systems within {}LY of {}".format(
                    radius, star_system), "DEBUG")
            return systems

        self.edsm_systems_within_radius_cache.set(key, None)
        EDRLOG.log(
            u"No results from EDSM. Temporary entry to be nice on EDSM's server.",
            "DEBUG")
        return None

    def is_recent(self, timestamp, max_age):
        if timestamp is None:
            return False
        return (edtime.EDTime.js_epoch_now() - timestamp) / 1000 <= max_age

    def evict(self, star_system):
        try:
            del self.systems_cache[star_system]
        except KeyError:
            pass

    def __are_reports_stale(self):
        return self.__is_stale(self.sitreps_cache.last_updated,
                               self.reports_check_interval)

    def __are_notams_stale(self):
        return self.__is_stale(self.notams_cache.last_updated,
                               self.notams_check_interval)

    def __is_stale(self, updated_at, max_age):
        if updated_at is None:
            return True
        now = datetime.datetime.now()
        epoch_now = time.mktime(now.timetuple())
        epoch_updated = time.mktime(updated_at.timetuple())

        return (epoch_now - epoch_updated) > max_age

    def __update_if_stale(self):
        updated = False
        if self.__are_reports_stale():
            missing_seconds = self.timespan
            now = datetime.datetime.now()
            if self.sitreps_cache.last_updated:
                missing_seconds = min(
                    self.timespan,
                    (now - self.sitreps_cache.last_updated).total_seconds())
            sitreps = self.server.sitreps(missing_seconds)
            if sitreps:
                for system_id in sitreps:
                    self.sitreps_cache.set(system_id, sitreps[system_id])
            self.sitreps_cache.last_updated = now
            updated = True

        if self.__are_notams_stale():
            missing_seconds = self.timespan_notams
            now = datetime.datetime.now()
            if self.notams_cache.last_updated:
                missing_seconds = min(
                    self.timespan_notams,
                    (now - self.notams_cache.last_updated).total_seconds())

            notams = self.server.notams(missing_seconds)
            if notams:
                for system_id in notams:
                    self.notams_cache.set(system_id, notams[system_id])
            self.notams_cache.last_updated = now
            updated = True

        return updated

    def closest_destination(self,
                            sysAndSta1,
                            sysAndSta2,
                            override_sc_distance=None):
        if not sysAndSta1:
            return sysAndSta2

        if not sysAndSta2:
            return sysAndSta1

        sc_distance = override_sc_distance or self.reasonable_sc_distance

        if sysAndSta1['station'][
                'distanceToArrival'] > sc_distance and sysAndSta2['station'][
                    'distanceToArrival'] > sc_distance:
            if abs(sysAndSta1['distance'] - sysAndSta2['distance']) < 5:
                return sysAndSta1 if sysAndSta1['station'][
                    'distanceToArrival'] < sysAndSta2['station'][
                        'distanceToArrival'] else sysAndSta2
            else:
                return sysAndSta1 if sysAndSta1['distance'] < sysAndSta2[
                    'distance'] else sysAndSta2

        if sysAndSta1['station']['distanceToArrival'] > sc_distance:
            return sysAndSta2

        if sysAndSta2['station']['distanceToArrival'] > sc_distance:
            return sysAndSta1

        return sysAndSta1 if sysAndSta1['distance'] < sysAndSta2[
            'distance'] else sysAndSta2

    def in_bubble(self, system_name):
        try:
            return self.distance(system_name, 'Sol') <= 500
        except ValueError:
            return False

    def in_colonia(self, system_name):
        try:
            return self.distance(system_name, 'Colonia') <= 500
        except ValueError:
            return False
Exemple #10
0
from __future__ import absolute_import

import json
import re
import os

from edrlog import EDRLog
import utils2to3

EDRLOG = EDRLog()
POWER_DATA = json.loads(open(utils2to3.abspathmaker(__file__, 'data', 'modules_power_data.json')).read())

class EDModule(object):
    def __init__(self, module):
        self.power_draw = EDModule.__get_power_draw(module)
        self.power_generation = EDModule.__get_power_gen(module) 
        self.priority = EDModule.__get_priority(module)
        self.cname = module["Item"].lower() if "Item" in module else None
        self.on = module.get("On", False)

    def update(self, module):
        prev_power_draw = self.power_draw
        prev_priority = self.priority
        prev_cname = self.cname
        prev_on = self.on

        EDRLOG.log(u"before: {}, {}, {}, {}".format(prev_cname, prev_power_draw, prev_priority, prev_on), "DEBUG")

        self.power_draw = module["Power"] if "Power" in module else EDModule.__get_power_draw(module)
        self.priority = EDModule.__get_priority(module)
        self.cname = module["Item"].lower() if "Item" in module else None
Exemple #11
0
 def loud(self):
     self.snd_warn = utils2to3.abspathmaker(__file__, 'sounds',
                                            'snd_warn.wav')
     self.snd_notify = utils2to3.abspathmaker(__file__, 'sounds',
                                              'snd_notify.wav')
Exemple #12
0
class EDROpponents(object):
    OUTLAWS = "Outlaws"
    ENEMIES = "Enemies"

    EDR_OPPONENTS_SIGHTINGS_CACHES = {
        "Outlaws":
        utils2to3.abspathmaker(__file__, 'cache', 'outlaws_sigthings.v2.p'),
        "Enemies":
        utils2to3.abspathmaker(__file__, 'cache', 'enemies_sigthings.v2.p')
    }

    EDR_OPPONENTS_RECENTS_CACHES = {
        "Outlaws":
        utils2to3.abspathmaker(__file__, 'cache', 'outlaws_recents.v2.p'),
        "Enemies":
        utils2to3.abspathmaker(__file__, 'cache', 'enemies_recents.v2.p')
    }

    def __init__(self, server, opponent_kind, client_callback):
        self.server = server
        self.kind = opponent_kind
        self.powerplay = None
        self.realtime_callback = client_callback
        self.realtime = None

        config = edrconfig.EDRConfig()
        try:
            with open(self.EDR_OPPONENTS_SIGHTINGS_CACHES[opponent_kind],
                      'rb') as handle:
                self.sightings = pickle.load(handle)
        except:
            self.sightings = lrucache.LRUCache(
                config.lru_max_size(), config.opponents_max_age(self.kind))

        try:
            with open(self.EDR_OPPONENTS_RECENTS_CACHES[opponent_kind],
                      'rb') as handle:
                self.recents = pickle.load(handle)
        except:
            self.recents = deque(
                maxlen=config.opponents_max_recents(self.kind))

        self.timespan = config.opponents_recent_threshold(self.kind)
        self.reports_check_interval = config.reports_check_interval()

    def persist(self):
        with open(self.EDR_OPPONENTS_SIGHTINGS_CACHES[self.kind],
                  'wb') as handle:
            pickle.dump(self.sightings,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)
        with open(self.EDR_OPPONENTS_RECENTS_CACHES[self.kind],
                  'wb') as handle:
            pickle.dump(self.recents, handle, protocol=pickle.HIGHEST_PROTOCOL)

    def pledged_to(self, power, time_pledged):
        if self.kind is not EDROpponents.ENEMIES:
            return
        if not power or self.powerplay is not power:
            config = edrconfig.EDRConfig()
            self.recents = deque(
                maxlen=config.opponents_max_recents(self.kind))
            self.sightings = lrucache.LRUCache(
                config.lru_max_size(), config.opponents_max_age(self.kind))
        self.powerplay = power

    def is_comms_link_up(self):
        return self.realtime and self.realtime.is_live()

    def establish_comms_link(self):
        self.shutdown_comms_link()
        if self._invalid_state():
            return False
        endpoint = "{}{}".format(self.server.EDR_SERVER, self._node())
        self.realtime = edrrealtime.EDRRealtimeUpdates(self.realtime_callback,
                                                       self.kind, endpoint,
                                                       self.server.auth_token)
        if self.server.preflight_realtime(self.kind):
            self.realtime.start()
            return True
        return False

    def shutdown_comms_link(self):
        if self.realtime and self.realtime.is_live():
            self.realtime.shutdown()

    def where(self, cmdr_name):
        if self._invalid_state():
            return None
        self.__update_opponents_if_stale()
        cname = cmdr_name.lower()
        report = self.sightings.get(cname)
        if not report:
            report = self.server.where(cmdr_name, self.powerplay)
            if report:
                self.sightings.set(cname, report)
        if report:
            return {
                "timestamp": report.get("timestamp", None),
                "readable": self.__readable_opponent_sighting(report)
            }
        else:
            return None

    def recent_sightings(self):
        self.__update_opponents_if_stale()
        if not self.recents:
            EDRLOG.log(u"No recently sighted {}".format(self.kind), "INFO")
            return None

        EDRLOG.log(u"Got recently sighted {}".format(self.kind), "INFO")
        summary = []
        now = datetime.datetime.now()
        js_epoch_now = int(1000 * time.mktime(now.timetuple()))
        processed = []
        for sighting in self.recents:
            if (js_epoch_now - sighting["timestamp"]) // 1000 > self.timespan:
                continue
            if sighting["cmdr"] not in processed:
                summary.append(
                    self.__readable_opponent_sighting(sighting,
                                                      one_liner=True))
                processed.append(sighting["cmdr"])
        return summary

    def __readable_opponent_sighting(self, sighting, one_liner=False):
        EDRLOG.log(u"sighting: {}".format(sighting), "DEBUG")
        if not sighting:
            return None
        t_minus = edtime.EDTime.t_minus(sighting["timestamp"], short=True)
        if one_liner:
            cmdr = (sighting["cmdr"][:29] +
                    u'…') if len(sighting["cmdr"]) > 30 else sighting["cmdr"]
            starSystem = (sighting["starSystem"][:50] + u'…') if len(
                sighting["starSystem"]) > 50 else sighting["starSystem"]
            if sighting.get("bounty", 0) > 0:
                neat_bounty = EDFineOrBounty(sighting["bounty"]).pretty_print()
                # Translators: this is a one-liner for the recently sighted opponents; Keep it short! T{t:<2} is to show how long ago e.g. T-4H (4 hours ago)
                return _(
                    u"{t:<2}: {name} in {system}, wanted for {bounty}").format(
                        t=t_minus,
                        name=cmdr,
                        system=starSystem,
                        bounty=neat_bounty)
            else:
                # Translators: this is a one-liner for the recently sighted opponents; Keep it short! T{t:<2} is to show how long ago e.g. T-4H (4 hours ago)
                return _(u"{t:<2}: {name} in {system}").format(
                    t=t_minus, name=cmdr, system=starSystem)

        readable = []

        # Translators: this is for a recently sighted outlaw; T{t} is to show how long ago, e.g. T-2h43m
        location = _(u"{t} {name} sighted in {system}").format(
            t=t_minus, name=sighting["cmdr"], system=sighting["starSystem"])
        if sighting["place"] and sighting["place"] != sighting["starSystem"]:
            if sighting["place"].startswith(sighting["starSystem"] + " "):
                # Translators: this is a continuation of the previous item (location of recently sighted outlaw) and shows a place in the system (e.g. supercruise, Cleve Hub)
                location += _(u", {place}").format(
                    place=sighting["place"].partition(sighting["starSystem"] +
                                                      " ")[2])
            else:
                location += _(u", {place}").format(place=sighting["place"])
        readable.append(location)
        if sighting["ship"] != "Unknown":
            # Translators: this is for the recently sighted outlaw feature; it shows which ship they were flying at the time
            readable.append(_(u"Spaceship: {}").format(sighting["ship"]))
        if sighting.get("bounty", 0) > 0:
            neat_bounty = EDFineOrBounty(sighting["bounty"]).pretty_print()
            # Translators: this is for the recently sighted outlaw feature; it shows their bounty if any
            readable.append(_(u"Wanted for {} credits").format(neat_bounty))
        return readable

    def __are_sightings_stale(self):
        if self.sightings.last_updated is None:
            return True
        now = datetime.datetime.now()
        epoch_now = time.mktime(now.timetuple())
        epoch_updated = time.mktime(self.sightings.last_updated.timetuple())
        return (epoch_now - epoch_updated) > self.reports_check_interval

    def __update_opponents_if_stale(self):
        updated = False
        if self.__are_sightings_stale():
            now = datetime.datetime.now()
            missing_seconds = self.timespan
            if self.sightings.last_updated:
                missing_seconds = int(
                    min(self.timespan,
                        (now - self.sightings.last_updated).total_seconds()))
            sightings = None
            if self.kind is EDROpponents.OUTLAWS:
                sightings = self.server.recent_outlaws(missing_seconds)
            elif self.kind is EDROpponents.ENEMIES:
                sightings = self.server.recent_enemies(missing_seconds,
                                                       self.powerplay)
            self.sightings.last_updated = now
            if sightings:
                updated = True
                #TODO can we just append the whole thing?
                sightings = sorted(sightings,
                                   key=lambda t: t["timestamp"],
                                   reverse=False)
                for sighting in sightings:
                    previous = self.sightings.get(sighting["cmdr"].lower())
                    if not previous or (previous and previous["timestamp"] <
                                        sighting["timestamp"]):
                        self.sightings.set(sighting["cmdr"].lower(), sighting)
                    self.recents.appendleft(sighting)
        return updated

    def _realtime_callback(self, kind, events):
        if events not in ["cancel", "auth_revoked"]:
            for report in events.values():
                previous = self.sightings.get(report["cmdr"].lower())
                if not previous or (previous and previous["timestamp"] <
                                    report["timestamp"]):
                    self.sightings.set(report["cmdr"].lower(), report)
                self.recents.appendleft(report)
            self.sightings.last_updated = datetime.datetime.now()
        self.realtime_callback(kind, events)

    def _node(self):
        if self.kind is self.OUTLAWS:
            return "/v1/outlaws/.json"
        elif self.kind is self.ENEMIES and self.powerplay:
            return "/v1/powerplay/{}/enemies/.json".format(
                self.server.nodify(self.powerplay))
        else:
            return None

    def _invalid_state(self):
        return self.kind is EDROpponents.ENEMIES and not self.powerplay
Exemple #13
0
 def __init__(self, help_file=None):
     if help_file:
         self.content = json.loads(
             open(utils2to3.abspathmaker(__file__, help_file)).read())
     else:
         self.content = HelpContent.DEFAULT_CONTENT
Exemple #14
0
 def soft(self):
     self.snd_warn = utils2to3.abspathmaker(__file__, 'sounds',
                                            'snd_warn_soft.wav')
     self.snd_notify = utils2to3.abspathmaker(__file__, 'sounds',
                                              'snd_notify_soft.wav')
Exemple #15
0
 def __init__(self, config_file='config/config.ini'):
     self.config = cp.ConfigParser()
     self.config.read(utils2to3.abspathmaker(__file__, config_file))
Exemple #16
0
class EDRFactions(object):
    EDR_FACTIONS_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                'edr_factions.v1.p')

    def __init__(self):
        edr_config = EDRConfig()

        try:
            with open(self.EDR_FACTIONS_CACHE, 'rb') as handle:
                self.factions_cache = pickle.load(handle)
        except:
            self.factions_cache = LRUCache(edr_config.lru_max_size(),
                                           edr_config.factions_max_age())

    def persist(self):
        with open(self.EDR_FACTIONS_CACHE, 'wb') as handle:
            pickle.dump(self.factions_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

    def process(self, factions, star_system):
        factions_in_system = {}
        for faction in factions:
            factions_in_system[faction["Name"].lower()] = EDRFaction(faction)
        self.factions_cache.set(star_system.lower(), factions_in_system)

    def get(self, name, star_system):
        factions_in_system = self.get_all(star_system)
        if factions_in_system:
            return factions_in_system.get(name.lower(), None)
        return None

    def get_all(self, star_system):
        return self.factions_cache.get(star_system.lower())

    def assess(self, star_system, security, population):
        factions_in_system = self.get_all(star_system)
        assessments = {}
        for faction in factions_in_system:
            assessments[faction] = factions_in_system[faction].assess(
                security, population)
        return assessments

    def summarize_yields(self, star_system, security, population, inventory):
        assessment = self.assess(star_system, security, population)
        if not assessment:
            return None

        yields = {}
        for faction_name in assessment:
            if not assessment[faction_name]:
                continue
            faction = self.get(faction_name, star_system)
            faction_chance = faction.influence
            state_chance = 1.0 / len(
                faction.active_states) if faction.active_states else 0.0
            chance = faction_chance * state_chance
            outcomes = assessment[faction_name].outcomes
            for material in outcomes:
                if yields.get(material, None) is None:
                    yields[material] = 0
                yields[material] += chance
        return [
            u"{:.0f}%: {}".format(chance * 100.0,
                                  inventory.oneliner(material.title()))
            for (material, chance
                 ) in sorted(yields.items(), key=lambda x: x[1], reverse=True)
        ]
Exemple #17
0
#!/usr/bin/env python
# coding=utf-8
from __future__ import absolute_import

import gettext
import os
import sys

import utils2to3

CONTEXT_SEPARATOR = u"|"
L10N_DIR = utils2to3.abspathmaker(__file__, 'l10n')
language = None
translate = gettext.translation('edr',
                                L10N_DIR,
                                fallback=True,
                                codeset="utf-8")


def set_language(lang):
    global language, translate
    language = lang
    if language:
        translate = gettext.translation('edr',
                                        L10N_DIR,
                                        fallback=True,
                                        languages=[language],
                                        codeset="utf-8")
    else:
        translate = gettext.translation('edr',
                                        L10N_DIR,
Exemple #18
0
class EDRCmdrs(object):
    #TODO these should be player and/or squadron specific
    EDR_CMDRS_CACHE = utils2to3.abspathmaker(__file__, 'cache', 'cmdrs.v7.p')
    EDR_INARA_CACHE = utils2to3.abspathmaker(__file__, 'cache', 'inara.v7.p')
    EDR_SQDRDEX_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                               'sqdrdex.v2.p')

    def __init__(self, edrserver):
        self.server = edrserver
        self._player = EDPlayerOne()
        self.heartbeat_timestamp = None

        edr_config = EDRConfig()
        self._edr_heartbeat = edr_config.edr_heartbeat()

        try:
            with open(self.EDR_CMDRS_CACHE, 'rb') as handle:
                self.cmdrs_cache = pickle.load(handle)
        except:
            self.cmdrs_cache = LRUCache(edr_config.lru_max_size(),
                                        edr_config.cmdrs_max_age())

        try:
            with open(self.EDR_INARA_CACHE, 'rb') as handle:
                self.inara_cache = pickle.load(handle)
        except:
            self.inara_cache = LRUCache(edr_config.lru_max_size(),
                                        edr_config.inara_max_age())

        try:
            with open(self.EDR_SQDRDEX_CACHE, 'rb') as handle:
                self.sqdrdex_cache = pickle.load(handle)
        except:
            self.sqdrdex_cache = LRUCache(edr_config.lru_max_size(),
                                          edr_config.sqdrdex_max_age())

    @property
    def player(self):
        return self._player

    def player_name(self):
        return self._player.name

    def set_player_name(self, new_player_name):
        if (new_player_name != self._player.name):
            self._player.force_new_name(new_player_name)
            self.__update_squadron_info(force_update=True)

    def player_pledged_to(self, power, time_pledged=0):
        edr_config = EDRConfig()
        delta = time_pledged - self._player.time_pledged if self._player.time_pledged else time_pledged
        if power == self._player.power and delta <= edr_config.noteworthy_pledge_threshold(
        ):
            EDRLOG.log(
                u"Skipping pledged_to (not noteworthy): current vs. proposed {} vs. {}; {} vs {}"
                .format(self._player.power, power, self._player.time_pledged,
                        time_pledged), "DEBUG")
            return False
        self._player.pledged_to(power, time_pledged)
        since = self._player.pledged_since()
        return self.server.pledged_to(power, since)

    def __squadron_id(self):
        self.__update_squadron_info()
        info = self._player.squadron_info()
        return info["squadronId"] if info else None

    def __update_squadron_info(self, force_update=False):
        if self.server.is_anonymous():
            return
        mark_twain_flag = int(
            (EDTime.js_epoch_now() - self.heartbeat_timestamp) /
            1000) >= self._edr_heartbeat if self.heartbeat_timestamp else True
        if force_update or mark_twain_flag:
            info = self.server.heartbeat()
            if info:
                self.heartbeat_timestamp = info[
                    "heartbeat"] if "heartbeat" in info else EDTime.js_epoch_now(
                    )
                self._player.squadron_member(
                    info) if "squadronId" in info else self._player.lone_wolf(
                    )
            else:
                self.heartbeat_timestamp = EDTime.js_epoch_now()
                self._player.lone_wolf()

    def persist(self):
        with open(self.EDR_CMDRS_CACHE, 'wb') as handle:
            pickle.dump(self.cmdrs_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_INARA_CACHE, 'wb') as handle:
            pickle.dump(self.inara_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_SQDRDEX_CACHE, 'wb') as handle:
            pickle.dump(self.sqdrdex_cache,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

    def evict(self, cmdr):
        try:
            del self.cmdrs_cache[cmdr.lower()]
        except KeyError:
            pass

        try:
            del self.inara_cache[cmdr.lower()]
        except KeyError:
            pass

        try:
            sqdr_id = self.__squadron_id()
            if sqdr_id:
                sq_cmdr_key = u"{}:{}".format(sqdr_id, cmdr.lower())
                del self.sqdrdex_cache[sq_cmdr_key]
        except KeyError:
            pass

    def __edr_cmdr(self, cmdr_name, autocreate):
        profile = self.cmdrs_cache.get(cmdr_name.lower())
        cached = self.cmdrs_cache.has_key(cmdr_name.lower())
        if cached or profile:
            EDRLOG.log(
                u"Cmdr {cmdr} is in the EDR cache with id={cid}".format(
                    cmdr=cmdr_name, cid=profile.cid if profile else 'N/A'),
                "DEBUG")
            return profile

        profile = self.server.cmdr(cmdr_name, autocreate)

        if not profile:
            self.cmdrs_cache.set(cmdr_name.lower(), None)
            EDRLOG.log(
                u"No match on EDR. Temporary entry to be nice on EDR's server.",
                "DEBUG")
            return None
        dex_profile = self.server.cmdrdex(profile.cid)
        if dex_profile:
            EDRLOG.log(
                u"EDR CmdrDex entry found for {cmdr}: {id}".format(
                    cmdr=cmdr_name, id=profile.cid), "DEBUG")
            profile.dex(dex_profile)
        self.cmdrs_cache.set(cmdr_name.lower(), profile)
        EDRLOG.log(
            u"Cached EDR profile {cmdr}: {id}".format(cmdr=cmdr_name,
                                                      id=profile.cid), "DEBUG")
        return profile

    def __edr_sqdrdex(self, cmdr_name, autocreate):
        sqdr_id = self.__squadron_id()
        if not sqdr_id:
            return None
        key = u"{}:{}".format(sqdr_id, cmdr_name.lower())
        profile = self.sqdrdex_cache.get(key)
        if profile:
            EDRLOG.log(
                u"Cmdr {cmdr} is in the EDR IFF cache for squadron {sqid} with key {key}"
                .format(cmdr=cmdr_name, sqid=sqdr_id, key=key), "DEBUG")
            return profile

        profile = self.__edr_cmdr(cmdr_name, autocreate)
        if not profile:
            return None

        sqdrdex_dict = self.server.sqdrdex(sqdr_id, profile.cid)
        if sqdrdex_dict:
            EDRLOG.log(
                u"EDR SqdrDex {sqid} entry found for {cmdr}@{cid}".format(
                    sqid=sqdr_id, cmdr=cmdr_name, cid=profile.cid), "DEBUG")
            profile.sqdrdex(sqdrdex_dict)
        self.sqdrdex_cache.set(u"{}:{}".format(sqdr_id, cmdr_name.lower()),
                               profile)
        EDRLOG.log(
            u"Cached EDR SqdrDex {sqid} entry for {cmdr}@{cid}".format(
                sqid=sqdr_id, cmdr=cmdr_name, cid=profile.cid), "DEBUG")
        return profile.sqdrdex_profile

    def __inara_cmdr(self, cmdr_name, check_inara_server):
        inara_profile = None
        stale = self.inara_cache.is_stale(cmdr_name.lower())
        cached = self.inara_cache.has_key(cmdr_name.lower())
        if cached and not stale:
            inara_profile = self.inara_cache.get(cmdr_name.lower())
            EDRLOG.log(
                u"Cmdr {} is in the Inara cache (name={})".format(
                    cmdr_name, inara_profile.name if inara_profile else 'N/A'),
                "DEBUG")
        elif check_inara_server:
            EDRLOG.log(
                u"Stale {} or not cached {} in Inara cache. Inara API call for {}."
                .format(stale, cached, cmdr_name), "INFO")
            inara_profile = self.server.inara_cmdr(cmdr_name)

            if inara_profile and inara_profile.name.lower() == cmdr_name.lower(
            ):
                self.inara_cache.set(cmdr_name.lower(), inara_profile)
                EDRLOG.log(
                    u"Cached Inara profile {}: {},{},{},{}".format(
                        cmdr_name, inara_profile.name, inara_profile.squadron,
                        inara_profile.role, inara_profile.powerplay), "DEBUG")
            elif self.inara_cache.has_key(cmdr_name.lower()):
                inara_profile = self.inara_cache.peek(cmdr_name.lower())
                self.inara_cache.refresh(cmdr_name.lower())
                EDRLOG.log(u"Refresh and re-use stale match in Inara cache.",
                           "INFO")
            else:
                inara_profile = None
                self.inara_cache.set(cmdr_name.lower(), None)
                EDRLOG.log(
                    u"No match on Inara. Temporary entry to be nice on Inara's server.",
                    "INFO")
        return inara_profile

    def cmdr(self, cmdr_name, autocreate=True, check_inara_server=False):
        profile = self.__edr_cmdr(cmdr_name, autocreate)
        inara_profile = self.__inara_cmdr(cmdr_name, check_inara_server)

        if profile is None:
            if inara_profile is None:
                EDRLOG.log(
                    u"Failed to retrieve/create cmdr {}".format(cmdr_name),
                    "ERROR")
                return None
            else:
                return inara_profile

        if inara_profile:
            EDRLOG.log(
                u"Combining info from EDR and Inara for cmdr {}".format(
                    cmdr_name), "INFO")
            profile.complement(inara_profile)

        squadron_profile = self.__edr_sqdrdex(cmdr_name, autocreate)
        if squadron_profile:
            EDRLOG.log(
                u"Combining info from Squadron for cmdr {}".format(cmdr_name),
                "INFO")
            profile.sqdrdex(squadron_profile.sqdrdex_dict())

        return profile

    def is_friend(self, cmdr_name):
        profile = self.__edr_cmdr(cmdr_name, False)
        if profile is None:
            return False
        return profile.is_friend()

    def is_ally(self, cmdr_name):
        sqdr_id = self.__squadron_id()
        if not sqdr_id:
            return False

        profile = self.__edr_sqdrdex(cmdr_name, False)
        if profile:
            return profile.is_ally()
        return False

    def tag_cmdr(self, cmdr_name, tag):
        if tag in ["enemy", "ally"]:
            return self.__squadron_tag_cmdr(cmdr_name, tag)
        else:
            return self.__tag_cmdr(cmdr_name, tag)

    def contracts(self):
        return self.server.contracts()

    def contract_for(self, cmdr_name):
        if not cmdr_name:
            return False

        profile = self.cmdr(cmdr_name)
        if not profile:
            return False

        return self.server.contract_for(profile.cid)

    def place_contract(self, cmdr_name, reward):
        if not cmdr_name:
            return False

        if reward <= 0:
            return self.remove_contract(cmdr_name)

        profile = self.cmdr(cmdr_name)
        if not profile:
            return False

        return self.server.place_contract(profile.cid, {
            "cname": cmdr_name.lower(),
            "reward": reward
        })

    def remove_contract(self, cmdr_name):
        if not cmdr_name:
            return False

        profile = self.cmdr(cmdr_name)
        if not profile:
            return False

        return self.server.remove_contract(profile.cid)

    def __tag_cmdr(self, cmdr_name, tag):
        EDRLOG.log(u"Tagging {} with {}".format(cmdr_name, tag), "DEBUG")
        profile = self.__edr_cmdr(cmdr_name, False)
        if profile is None:
            EDRLOG.log(u"Couldn't find a profile for {}.".format(cmdr_name),
                       "DEBUG")
            return False

        tagged = profile.tag(tag)
        if not tagged:
            EDRLOG.log(
                u"Couldn't tag {} with {} (e.g. already tagged)".format(
                    cmdr_name, tag), "DEBUG")
            self.evict(cmdr_name)
            return False

        dex_dict = profile.dex_dict()
        EDRLOG.log(u"New dex state: {}".format(dex_dict), "DEBUG")
        success = self.server.update_cmdrdex(profile.cid, dex_dict)
        self.evict(cmdr_name)
        return success

    def __squadron_tag_cmdr(self, cmdr_name, tag):
        sqdr_id = self.__squadron_id()
        if not sqdr_id:
            EDRLOG.log(u"Can't tag: not a member of a squadron", "DEBUG")
            return False

        EDRLOG.log(u"Tagging {} with {} for squadron".format(cmdr_name, tag),
                   "DEBUG")
        profile = self.__edr_sqdrdex(cmdr_name, False)
        if profile is None:
            EDRLOG.log(
                u"Couldn't find a squadron profile for {}.".format(cmdr_name),
                "DEBUG")
            return False

        tagged = profile.tag(tag)
        if not tagged:
            EDRLOG.log(
                u"Couldn't tag {} with {} (e.g. already tagged)".format(
                    cmdr_name, tag), "DEBUG")
            self.evict(cmdr_name)
            return False

        sqdrdex_dict = profile.sqdrdex_dict()
        EDRLOG.log(u"New dex state: {}".format(sqdrdex_dict), "DEBUG")
        augmented_sqdrdex_dict = sqdrdex_dict
        augmented_sqdrdex_dict["level"] = self._player.squadron_info(
        )["squadronLevel"]
        augmented_sqdrdex_dict["by"] = self._player.name
        success = self.server.update_sqdrdex(sqdr_id, profile.cid,
                                             augmented_sqdrdex_dict)
        self.evict(cmdr_name)
        return success

    def memo_cmdr(self, cmdr_name, memo):
        if memo is None:
            return self.clear_memo_cmdr(cmdr_name)
        EDRLOG.log(u"Writing a note about {}: {}".format(memo, cmdr_name),
                   "DEBUG")
        profile = self.__edr_cmdr(cmdr_name, False)
        if profile is None:
            EDRLOG.log(u"Couldn't find a profile for {}.".format(cmdr_name),
                       "DEBUG")
            return False

        noted = profile.memo(memo)
        if not noted:
            EDRLOG.log(u"Couldn't write a note about {}".format(cmdr_name),
                       "DEBUG")
            self.evict(cmdr_name)
            return False

        dex_dict = profile.dex_dict()
        success = self.server.update_cmdrdex(profile.cid, dex_dict)
        self.evict(cmdr_name)
        return success

    def clear_memo_cmdr(self, cmdr_name):
        EDRLOG.log(u"Removing a note from {}".format(cmdr_name), "DEBUG")
        profile = self.__edr_cmdr(cmdr_name, False)
        if profile is None:
            EDRLOG.log(u"Couldn't find a profile for {}.".format(cmdr_name),
                       "DEBUG")
            return False

        noted = profile.remove_memo()
        if not noted:
            EDRLOG.log(u"Couldn't remove a note from {}".format(cmdr_name),
                       "DEBUG")
            self.evict(cmdr_name)
            return False

        dex_dict = profile.dex_dict()
        success = self.server.update_cmdrdex(profile.cid, dex_dict)
        self.evict(cmdr_name)
        return success

    def untag_cmdr(self, cmdr_name, tag):
        if tag in ["enemy", "ally"]:
            return self.__squadron_untag_cmdr(cmdr_name, tag)
        else:
            return self.__untag_cmdr(cmdr_name, tag)

    def __untag_cmdr(self, cmdr_name, tag):
        EDRLOG.log(u"Removing {} tag from {}".format(tag, cmdr_name), "DEBUG")
        profile = self.__edr_cmdr(cmdr_name, False)
        if profile is None:
            EDRLOG.log(u"Couldn't find a profile for {}.".format(cmdr_name),
                       "DEBUG")
            return False

        untagged = profile.untag(tag)
        if not untagged:
            EDRLOG.log(
                u"Couldn't untag {} (e.g. tag not present)".format(cmdr_name),
                "DEBUG")
            self.evict(cmdr_name)
            return False

        dex_dict = profile.dex_dict()
        EDRLOG.log(u"New dex state: {}".format(dex_dict), "DEBUG")
        success = self.server.update_cmdrdex(profile.cid, dex_dict)
        self.evict(cmdr_name)
        return success

    def __squadron_untag_cmdr(self, cmdr_name, tag):
        sqdr_id = self.__squadron_id()
        if not sqdr_id:
            EDRLOG.log(u"Can't untag: not a member of a squadron", "DEBUG")
            return False

        EDRLOG.log(u"Removing {} tag from {}".format(tag, cmdr_name), "DEBUG")
        profile = self.__edr_cmdr(cmdr_name, False)
        if profile is None:
            EDRLOG.log(u"Couldn't find a profile for {}.".format(cmdr_name),
                       "DEBUG")
            return False

        untagged = profile.untag(tag)
        if not untagged:
            EDRLOG.log(
                u"Couldn't untag {} (e.g. tag not present)".format(cmdr_name),
                "DEBUG")
            self.evict(cmdr_name)
            return False

        sqdrdex_dict = profile.sqdrdex_dict()
        EDRLOG.log(u"New dex state: {}".format(sqdrdex_dict), "DEBUG")
        augmented_sqdrdex_dict = sqdrdex_dict
        augmented_sqdrdex_dict["level"] = self._player.squadron_info(
        )["squadronLevel"]
        augmented_sqdrdex_dict["by"] = self._player.name
        success = self.server.update_sqdrdex(sqdr_id, profile.cid,
                                             augmented_sqdrdex_dict)
        self.evict(cmdr_name)
        return success
Exemple #19
0
 def __init__(self, config_file='config/user_config.ini'):
     self.config = cp.ConfigParser()
     try:
         self.config.read(utils2to3.abspathmaker(__file__, config_file))
     except:
         self.config = None
Exemple #20
0
class EDRMiningStats(object):
    MINERALS_LUT = json.loads(open(utils2to3.abspathmaker(__file__, 'data', 'mining.json')).read())

    def __init__(self):
        self.lmh = {"-": 0, "L": 0, "M": 0, "H": 0}
        self.prospected_nb = 0
        self.max_efficiency = 150
        now = EDTime.py_epoch_now()
        self.start = now
        self.current = now
        self.last = {"timestamp": now, "raw": None, "materials": None, "minerals_stats": []}
        self.depleted = False
        self.of_interest = { "names": set(), "types": set()}
        self.stats = {}
        self.refined_nb = 0
        self.prospected_raw_history = deque(maxlen=8) # 8 max prospector drones
        self.efficiency = deque(maxlen=20)
        self.mineral_types_lut = {}
        for mineral in self.MINERALS_LUT:
            name = self.MINERALS_LUT[mineral]["name"]
            internal_name = self.MINERALS_LUT[mineral]["type"]
            self.mineral_types_lut[internal_name] = name
            symbol = self.MINERALS_LUT[mineral]["symbol"]
            self.of_interest["names"].add(name)
            self.of_interest["types"].add(internal_name)
            self.stats[mineral] = EDRMineralStats(name, internal_name, symbol)

    def reset(self):
        self.lmh = {"-": 0, "L": 0, "M": 0, "H": 0}
        self.prospected_nb = 0
        self.max_efficiency = 150
        now = EDTime.py_epoch_now()
        self.start = now
        self.current = now
        self.refined_nb = 0
        self.last = {"timestamp": now, "raw": None, "materials": None, "minerals_stats": []}
        self.depleted = False
        self.of_interest = { "names": set(), "types": set()}
        self.stats = {}
        self.prospected_raw_history = deque(maxlen=8) # 8 max prospector drones
        self.mineral_types_lut = {}
        for mineral in self.MINERALS_LUT:
            name = self.MINERALS_LUT[mineral]["name"]
            internal_name = self.MINERALS_LUT[mineral]["type"]
            self.mineral_types_lut[internal_name] = name
            symbol = self.MINERALS_LUT[mineral]["symbol"]
            self.of_interest["names"].add(name)
            self.of_interest["types"].add(internal_name)
            self.stats[mineral] = EDRMineralStats(name, internal_name, symbol)

    def prospected(self, entry):
        if entry.get("event", None) != "ProspectedAsteroid":
            return False
        if entry.get("Remaining", 0) <= 0:
            self.depleted = True
            return False

        self.depleted = False
        if self.__probably_previously_prospected(entry):
            return False
        
        self.prospected_raw_history.append(entry)
        now = EDTime.py_epoch_now()
        self.current = now
        self.prospected_nb += 1
        self.__update_efficiency()
        
        lut_content = {
            "n/a": "-",
            "$AsteroidMaterialContent_Low;": "L",
            "$AsteroidMaterialContent_Medium;": "M",
            "$AsteroidMaterialContent_High;": "H"
        }

        key = lut_content.get(entry.get("Content", "-"), "-")
        self.lmh[key] += 1
        materials = entry.get("Materials", [])
        self.last = {
            "timestamp": now,
            "raw": key,
            "materials": len(materials),
            "minerals_stats": []
        }
        
        for material in materials:
            cname = material.get("Name", "").lower()
            if cname in self.of_interest["names"]:
                proportion = material.get("Proportion", 0.0)
                self.stats[cname].prospected(proportion)
                self.last["minerals_stats"].append(self.stats[cname])
                

    def __probably_previously_prospected(self, entry):
        b = entry.copy()
        b["timestamp"] = ""
        b["Remaining"] = ""
        matching_entry = None
        for previous in self.prospected_raw_history:
            a = previous.copy()
            a["timestamp"] = ""
            a["Remaining"] = ""
            if a == b and previous["Remaining"] >= entry["Remaining"]:
                matching_entry = previous
                break 
            
        if matching_entry:
            max_age = 60*5
            a_time = EDTime()
            a_time.from_journal_timestamp(matching_entry["timestamp"])
            b_time = EDTime()
            b_time.from_journal_timestamp(entry["timestamp"])
            return (b_time.as_py_epoch() - a_time.as_py_epoch()) <= max_age
        return False
    
    def refined(self, entry):
        if entry.get("event", None) != "MiningRefined":
            return
        now = EDTime.py_epoch_now()
        self.current = now
        if entry.get("Type", "").lower() not in self.of_interest["types"]:
            return

        self.refined_nb += 1
        self.__update_efficiency()
        cinternal_name = entry.get("Type", "").lower()
        if cinternal_name in self.of_interest["types"]:
            cname = self.minerals_types_lut[cinternal_name]
            self.stats[cname].refined()

    def last_max():
        if not self.last["minerals_stats"]:
            return self.max
        return self.last["mineral_stats"][0].max

    def item_per_hour(self):
        now = EDTime.py_epoch_now()
        self.current = now
        elapsed_time = self.current - self.start
        if elapsed_time:
            return self.refined_nb / (elapsed_time / 3600.0)
        return 0
    
    def last_yield_average(self):
        if self.prospected_nb <= 0:
            return 0.0

        if not self.last["minerals_stats"]:
            return (self.sum / self.prospected_nb)*100.0
        return self.last["minerals_stats"][0].yield_average(self.prospected_nb)

    def __update_efficiency(self):
        now = EDTime.py_epoch_now()
        efficiency = self.mineral_per_hour()
        self.efficiency.append((now, efficiency))
        self.max_efficiency = max(self.max_efficiency, efficiency)


    def __repr__(self):
        return str(self.__dict__)
Exemple #21
0
class EDRAutoUpdater(object):
    REPO = "lekeno/edr"
    UPDATES = utils2to3.abspathmaker(__file__, 'updates')
    LATEST = utils2to3.abspathmaker(__file__, 'updates', 'latest.zip')
    BACKUP = utils2to3.abspathmaker(__file__, 'backup')
    EDR_PATH = os.path.abspath(os.path.dirname(__file__))

    def __init__(self):
        self.updates = EDRAutoUpdater.UPDATES
        self.output = EDRAutoUpdater.LATEST
        

    def download_latest(self):
        if not os.path.exists(self.updates):
            try:
                os.makedirs(self.updates)
            except OSError as e:
                if e.errno != errno.EEXIST:
                    return False

        download_url = self.__latest_release_url()
        if not download_url:
            return False
        response = requests.get(download_url, stream=True)
        response.raise_for_status()

        if response.status_code != requests.codes.ok:
            return False

        with open(self.output, 'wb') as handle:
            for block in response.iter_content(32768):
                handle.write(block)
        return True

    def clean_old_backups(self):
        files = os.listdir(EDRAutoUpdater.BACKUP)
        files = [os.path.join(EDRAutoUpdater.BACKUP, f) for f in files]
        files.sort(key=lambda x: os.path.getctime(x))
        nbfiles = len(files)
        max_backups = 5
        for i in range(0, nbfiles - max_backups):
            f = files[i]
            EDRLOG.log(u"Removing backup {}".format(f), "INFO")
            os.unlink(f)

    def make_backup(self):
        if not os.path.exists(EDRAutoUpdater.BACKUP):
            try:
                os.makedirs(EDRAutoUpdater.BACKUP)
            except OSError as e:
                if e.errno != errno.EEXIST:
                    return False
        name = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + '.zip'
        backup_file = os.path.join(EDRAutoUpdater.BACKUP, name)
        zipf = zipfile.ZipFile(backup_file, 'w', zipfile.ZIP_DEFLATED)
        self.__zipdir(EDRAutoUpdater.EDR_PATH, zipf)
        zipf.close()

    def __zipdir(self, path, ziph):
        for root, dirs, files in os.walk(path):
            dirs[:] = [d for d in dirs if (("updates" not in d) and ("backup" not in d))]
            for file in files:
                if file.endswith(".pyc") or file.endswith(".pyo"):
                    continue
                fp = os.path.join(root, file)
                ziph.write(fp, os.path.relpath(fp, EDRAutoUpdater.EDR_PATH))

    def extract_latest(self):
        with zipfile.ZipFile(self.output, "r") as latest:
            latest.extractall(EDRAutoUpdater.EDR_PATH)

    def __latest_release_url(self):
        latest_release_api = "https://api.github.com/repos/{}/releases/latest".format(self.REPO)
        response = requests.get(latest_release_api)
        if response.status_code != requests.codes.ok:
            EDRLOG.log(u"Couldn't check the latest release on github: {}".format(response.status_code), "WARNING")
            return None
        json_resp = json.loads(response.content)
        assets = json_resp.get("assets", None)
        if not assets:
            return None
        return assets[0].get("browser_download_url", None)
Exemple #22
0
import json
import re
import os
import edrlog
import edmodule
from edri18n import _
import utils2to3

EDRLOG = edrlog.EDRLog()
POWER_DATA = json.loads(
    open(utils2to3.abspathmaker(__file__, 'data',
                                'modules_power_data.json')).read())


class EDRXzibit(object):
    def __init__(self, vehicle):
        EDRLOG.log(u"Xzibit is checking your ship", "DEBUG")
        self.power_capacity = vehicle.power_capacity
        EDRLOG.log(u" Power cap: {}".format(self.power_capacity), "DEBUG")
        self.per_prio = {
            "1": {
                "modules": []
            },
            "2": {
                "modules": []
            },
            "3": {
                "modules": []
            },
            "4": {
                "modules": []
Exemple #23
0
class EDVehicleFactory(object):
    __vehicle_classes = {
        "sidewinder": EDSidewinder,
        "eagle": EDEagle,
        "hauler": EDHauler,
        "adder": EDAdder,
        "viper": EDViperMkIII,
        "cobramkiii": EDCobraMkIII,
        "type6": EDT6Transporter,
        "dolphin": EDDolphin,
        "type7": EDT7Transporter,
        "asp": EDAspExplorer,
        "vulture": EDVulture,
        "empire_trader": EDImperialClipper,
        "federation_dropship": EDFederalDropship,
        "orca": EDOrca,
        "type9": EDT9Heavy,
        "type9_military": EDT10Defender,
        "python": EDPython,
        "belugaliner": EDBelugaLiner,
        "ferdelance": EDFerDeLance,
        "anaconda": EDAnaconda,
        "federation_corvette": EDFederalCorvette,
        "cutter": EDImperialCutter,
        "diamondback": EDDiamondbackScout,
        "empire_courier": EDImperialCourier,
        "diamondbackxl": EDDiamondbackExplorer,
        "empire_eagle": EDImperialEagle,
        "federation_dropship_mkii": EDFederalAssaultShip,
        "federation_gunship": EDFederalGunship,
        "viper_mkiv": EDViperMkIV,
        "cobramkiv": EDCobraMkIV,
        "independant_trader": EDKeelback,
        "asp_scout": EDAspScout,
        "typex": EDAllianceChieftain,
        "typex_2": EDAllianceCrusader,
        "typex_3": EDAllianceChallenger,
        "krait_mkii": EDKraitMkII,
        "krait_light": EDKraitPhantom,
        "mamba": EDMamba,
        "empire_fighter": EDImperialFighter,
        "federation_fighter": EDF63Condor,
        "independent_fighter": EDTaipanFighter,
        "gdn_hybrid_fighter_v1": EDTrident,
        "gdn_hybrid_fighter_v2": EDJavelin,
        "gdn_hybrid_fighter_v3": EDLance,
        "testbuggy": EDSRV,
        "unknown": EDUnknownVehicle,
        "unknown (crew)": EDCrewUnknownVehicle,
        "unknown (captain)": EDCaptainUnknownVehicle
    }

    CANONICAL_SHIP_NAMES = json.loads(
        open(utils2to3.abspathmaker(__file__, 'data',
                                    'shipnames.json')).read())
    CANONICAL_MODULE_NAMES = json.loads(
        open(utils2to3.abspathmaker(__file__, 'data', 'modulenames.json'),
             encoding="utf-8",
             errors='ignore').read())

    @staticmethod
    def canonicalize(name):
        if name is None:
            return u"Unknown"  # Note: this shouldn't be translated

        if name in EDVehicleFactory.CANONICAL_SHIP_NAMES.values():
            return name  # Already canonical

        if name.lower() in EDVehicleFactory.CANONICAL_SHIP_NAMES:
            return EDVehicleFactory.CANONICAL_SHIP_NAMES[name.lower()]

        return name.lower()

    @staticmethod
    def normalize_module_name(name):
        normalized = name.lower()

        # suffix _name or _name; is not used in loadout or afmurepair events
        if normalized.endswith(u"_name"):
            useless_suffix_length = len(u"_name")
            normalized = normalized[:-useless_suffix_length]
        elif normalized.endswith(u"_name;"):
            useless_suffix_length = len(u"_name;")
            normalized = normalized[:-useless_suffix_length]

        if normalized.startswith(u"$"):
            normalized = normalized[1:]

        # just get rid of prefixes because sometimes int_ becomes ext_ depending on the event
        if normalized.startswith((u"int_", u"ext_", u"hpt_")):
            normalized = normalized[4:]
        return normalized

    @staticmethod
    def readable_module_names(name):
        if name is None:
            return u"Unknown"  # Note: this shouldn't be translated

        if name in EDVehicleFactory.CANONICAL_MODULE_NAMES.values():
            return name  # Already canonical

        normalized = EDVehicleFactory.normalize_module_name(name)
        if normalized in EDVehicleFactory.CANONICAL_MODULE_NAMES:
            return (
                EDVehicleFactory.CANONICAL_MODULE_NAMES[normalized]["name"],
                EDVehicleFactory.CANONICAL_MODULE_NAMES[normalized]
                ["shortname"])

        match = re.search(
            '([a-zA-Z_]*)_size([0-9])_class([0-9])_?([a-zA-Z_]*)?', normalized)
        if match:
            class_letter = chr(70 - int(match.group(3)))
            synthetic_name = ""
            if match.group(4):
                synthetic_name = u"{} {} ({}{})".format(
                    match.group(1), match.group(4), match.group(2),
                    class_letter)
            else:
                synthetic_name = u"{} ({}{})".format(match.group(1),
                                                     match.group(2),
                                                     class_letter)
            return (synthetic_name, synthetic_name)
        return (normalized.lower(), normalized.lower())

    @staticmethod
    def module_tags(name):
        if name is None:
            return {}

        normalized = EDVehicleFactory.normalize_module_name(name)
        if normalized not in EDVehicleFactory.CANONICAL_MODULE_NAMES:
            return {}
        return EDVehicleFactory.CANONICAL_MODULE_NAMES[normalized].get(
            "tags", {})

    @staticmethod
    def from_edmc_state(state):
        name = state.get('ShipType', None)

        if name is None:
            name = 'unknown'

        vehicle_class = EDVehicleFactory.__vehicle_classes.get(
            name.lower(), None)
        if vehicle_class is None:
            raise NotImplementedError(
                "The requested vehicle has not been implemented")

        vehicle = vehicle_class()
        vehicle.id = state.get('ShipID', None)
        vehicle.identity = state.get('ShipIdent', None)
        vehicle.name = state.get('ShipName', None)
        vehicle.hull_value = state.get('HullValue', None)
        vehicle.rebuy = state.get('Rebuy', None)

        modules = state.get('Modules', None)
        if modules:
            for module in modules:
                health = modules[module][
                    'Health'] * 100.0 if 'Health' in modules[module] else None
                vehicle.subsystem_health(modules[module].get('Item', None),
                                         health)
        return vehicle

    @staticmethod
    def from_internal_name(internal_name):
        return EDVehicleFactory.__vehicle_classes.get(internal_name.lower(),
                                                      EDUnknownVehicle)()

    @staticmethod
    def from_load_game_event(event):
        vehicle = EDVehicleFactory.from_internal_name(
            event.get("Ship", 'unknown'))
        vehicle.id = event.get('ShipID', None)
        vehicle.identity = event.get('ShipIdent', None)
        vehicle.name = event.get('ShipName', None)
        vehicle.fuel_capacity = event.get('FuelCapacity', None)
        vehicle.fuel_level = event.get('FuelLevel', None)
        return vehicle

    @staticmethod
    def from_loadout_event(event):
        vehicle = EDVehicleFactory.from_internal_name(
            event.get("Ship", 'unknown'))
        vehicle.id = event.get('ShipID', None)
        vehicle.identity = event.get('ShipIdent', None)
        vehicle.name = event.get('ShipName', None)
        vehicle.hull_health = event.get(
            'HullHealth', None) * 100.0  # normalized to 0.0 ... 1.0
        vehicle.fuel_capacity = event.get('FuelCapacity',
                                          None)  #missing from loadout event...
        vehicle.fuel_level = event.get('FuelLevel',
                                       None)  #missing from loadout event...
        if not 'Modules' in event:
            return vehicle

        modules = event['Modules']
        for module in modules:
            health = modules[module]['Health'] * 100.0 if 'Health' in modules[
                module] else None
            vehicle.subsystem_health(modules[module].get('Item', None), health)
        return vehicle

    @staticmethod
    def from_stored_ship(ship_info):
        vehicle = EDVehicleFactory.from_internal_name(
            ship_info.get("ShipType", 'unknown'))
        vehicle.id = ship_info.get('ShipID', None)
        vehicle.name = ship_info.get('Name', None)
        vehicle.value = ship_info.get('Value', None)
        vehicle.hot = ship_info.get('Hot', None)
        return vehicle

    @staticmethod
    def is_ship_launched_fighter(vehicle):
        return isinstance(vehicle, EDShipLaunchedFighter)

    @staticmethod
    def is_surface_vehicle(vehicle):
        return isinstance(vehicle, EDSurfaceVehicle)

    @staticmethod
    def unknown_vehicle():
        return EDUnknownVehicle()

    @staticmethod
    def default_srv():
        return EDSRV()

    @staticmethod
    def unknown_slf():
        return EDShipLaunchedFighter()
Exemple #24
0
class EDPlayerOne(EDPlayer):
    EDR_FLEET_CARRIER_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                     'fleet_carrier.v1.p')

    def __init__(self, name=None):
        super(EDPlayerOne, self).__init__(name)
        self.powerplay = None
        self.game_mode = None
        self.private_group = None
        self.previous_mode = None
        self.previous_wing = set()
        self.from_genesis = False
        self.wing = EDWing()
        self.friends = set()
        self.crew = None
        self._target = None
        self.instance = EDInstance()
        self.planetary_destination = None
        self.recon_box = EDReconBox()
        self.inventory = EDRInventory()
        self.fleet = edrfleet.EDRFleet()
        try:
            with open(self.EDR_FLEET_CARRIER_CACHE, 'rb') as handle:
                self.fleet_carrier = pickle.load(handle)
        except:
            self.fleet_carrier = edrfleetcarrier.EDRFleetCarrier()
        self.mining_stats = edrminingstats.EDRMiningStats()

    def __repr__(self):
        return str(self.__dict__)

    def persist(self):
        self.inventory.persist()
        with open(self.EDR_FLEET_CARRIER_CACHE, 'wb') as handle:
            pickle.dump(self.fleet_carrier,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

    @property
    def target(self):
        return self._target

    @target.setter
    def target(self, new_target):
        if self._target:
            self._target.targeted = False
            self._target._touch()
        self._target = new_target
        if new_target:
            new_target.targeted = True
            new_target._touch()
        self._touch()

    def lowish_fuel(self):
        if self.mothership.fuel_level is None or self.mothership.fuel_capacity is None:
            return True  # Better safe than sorry
        return (self.mothership.fuel_level /
                self.mothership.fuel_capacity) <= 0.3

    def heavily_damaged(self):
        if self.mothership.hull_health is None:
            return True  # Better safe than sorry
        return self.mothership.hull_health <= 50

    def json(self, fuel_info=False, with_target=False):
        result = {
            u"cmdr": self.name,
            u"timestamp": self.timestamp * 1000,
            u"wanted": self.wanted,
            u"bounty": self.bounty,
            u"starSystem": self.star_system,
            u"place": self.place,
            u"wingof": len(self.wing.wingmates),
            u"wing": self.wing.noteworthy_changes_json(self.instance),
            u"byPledge":
            self.powerplay.canonicalize() if self.powerplay else u'',
            u"ship": self.piloted_vehicle.json(fuel_info=fuel_info),
            u"mode": self.game_mode,
            u"group": self.private_group
        }
        if with_target:
            result[u"target"] = self.target.json() if self.target else {}

        result[u"crew"] = []
        if self.crew:
            result[u"crew"] = [{
                u"cmdr": crew_member
            } for crew_member in self.crew.all_members()]

        return result

    def force_new_name(self, new_name):
        self._name = new_name

    def in_solo_or_private(self):
        return self.game_mode in [u"Solo", u"Group"]

    def in_solo(self):
        return self.game_mode == u"Solo"

    def in_open(self):
        return self.game_mode == u"Open"

    def inception(self, genesis=False):
        if genesis:
            self.from_genesis = True
        self.previous_mode = None
        self.previous_wing = set()
        self.wing = EDWing()
        self.crew = None
        self.destroyed = False
        self.target = None
        self.wanted = False
        self.mothership = EDVehicleFactory.unknown_vehicle()
        self.piloted_vehicle = self.mothership
        self.srv = None
        self.slf = None
        self.location = EDLocation()
        self._bounty = None
        self.instance.reset()
        self.to_normal_space()
        self._touch()
        self.reset_mining_stats()

    def killed(self):
        super(EDPlayerOne, self).killed()
        self.previous_mode = self.game_mode
        self.previous_private_group = self.private_group
        self.previous_wing = self.wing.wingmates.copy()
        self.game_mode = None
        self.private_group = None
        self.wing = EDWing()
        self.crew = None
        self.target = None
        self.instance.reset()
        self.recon_box.reset()
        self._touch()

    def resurrect(self, rebought=True):
        self.game_mode = self.previous_mode
        self.private_group = self.previous_private_group
        self.wing = EDWing(self.previous_wing)
        self.previous_mode = None
        self.previous_wing = set()
        self.destroyed = False
        self.target = None
        self.to_normal_space()
        self.instance.reset()
        self._touch()
        if rebought:
            self.mothership.reset()
            if self.slf:
                self.slf.reset()
            if self.srv:
                self.srv.reset()
        else:
            self.mothership = EDVehicleFactory.unknown_vehicle()
            self.piloted_vehicle = self.mothership
            self.slf = None
            self.srv = None

    def is_crew_member(self):
        if not self.crew:
            return False
        return self.crew.captain != self.name

    def in_a_crew(self):
        return self.crew is not None

    def leave_wing(self):
        self.wing.leave()
        self._touch()

    def join_wing(self, others):
        self.wing.join(others)
        self.crew = None
        self._touch()

    def add_to_wing(self, other):
        self.wing.add(other)
        self._touch()

    def in_a_wing(self):
        return self.wing.formed()

    def leave_crew(self):
        self._touch()
        if not self.crew:
            return
        self.crew = None
        self.instance.reset()

    def disband_crew(self):
        self._touch()
        if not self.crew:
            return
        for member in self.crew.members:
            self.instance.player_out(member)
        self.crew.disband()

    def join_crew(self, captain):
        self.wing = EDWing()
        self.instance.reset()
        self.crew = EDRCrew(captain)
        self.crew.add(self.name)
        self.instanced(captain)
        self.mothership = EDVehicleFactory.unknown_vehicle()
        self.piloted_vehicle = self.mothership
        self.slf = None
        self.srv = None
        self._touch()

    def add_to_crew(self, member):
        self._touch()
        if not self.crew:
            self.crew = EDRCrew(self.name)
            self.wing = EDWing()
            self.instance.reset()
        self.instanced(member)
        return self.crew.add(member)

    def remove_from_crew(self, member):
        self._touch()
        if not self.crew:
            self.crew = EDRCrew(self.name)
            self.wing = EDWing()
            self.instance.reset()
        self.instance.player_out(member)
        return self.crew.remove(member)

    def crew_time_elapsed(self, member):
        if not self.crew:
            return 0
        return self.crew.duration(member)

    def is_captain(self, member=None):
        if not self.crew:
            return False
        if not member:
            member = self.name
        return self.crew.is_captain(member)

    def is_friend(self, cmdr_name):
        return cmdr_name in self.friends

    def is_wingmate(self, cmdr_name):
        return cmdr_name in self.wing.wingmates

    def is_crewmate(self, cmdr_name):
        if not self.crew:
            return False
        return cmdr_name in self.crew.all_members()

    def is_enemy_with(self, power):
        if self.is_independent() or not power:
            return False
        return self.powerplay.is_enemy(power)

    def to_normal_space(self):
        if self.in_normal_space():
            return
        super(EDPlayerOne, self).to_normal_space()
        self.instance.reset()

    def to_super_space(self):
        if self.in_supercruise():
            return
        super(EDPlayerOne, self).to_super_space()
        self.instance.reset()
        self.recon_box.reset()

    def to_hyper_space(self):
        if self.in_hyper_space():
            return
        super(EDPlayerOne, self).to_hyper_space()
        self.instance.reset()
        self.recon_box.reset()

    def wing_and_crew(self):
        wing_and_crew = self.wing.wingmates.copy()
        if self.crew:
            wing_and_crew.update(self.crew.all_members())
        return wing_and_crew

    def maybe_in_a_pvp_fight(self):
        if not self.in_a_fight():
            return False

        if self.instance.is_empty():
            # Can't PvP if there is no one.
            return False

        if not self.instance.anyone_beside(self.wing_and_crew()):
            return False

        return True

    def leave_vehicle(self):
        self.mothership = EDVehicleFactory.unknown_vehicle()
        self.piloted_vehicle = self.mothership
        self.slf = None
        self.srv = None
        self.instance.reset()
        self.recon_box.reset()
        self._touch()

    def targeting(self, cmdr):
        self.instance.player_in(cmdr)
        self.target = cmdr
        self._touch()

    def destroy(self, cmdr):
        self._touch()
        cmdr.killed()
        self.instance.player_out(cmdr.name)
        if self.target and self.target.name == cmdr.name:
            self.target = None

    def interdiction(self, interdicted, success):
        self._touch()
        self.to_normal_space()
        if success:
            interdicted.location = self.location
            self.instance.player_in(interdicted)
        else:
            self.recon_box.reset()

    def interdicted(self, interdictor, success):
        self._touch()
        if success:
            self.to_normal_space()
            interdictor.location = self.location
            self.instance.player_in(interdictor)
        else:
            self.instance.player_out(interdictor.cmdr_name)
            self.recon_box.reset()

    def is_instanced_with(self, cmdr_name):
        return self.instance.player(cmdr_name) != None

    def instanced(self, cmdr_name, ship_internal_name=None, piloted=True):
        self._touch()
        cmdr = self.instance.player(cmdr_name)
        if not cmdr:
            cmdr = EDPlayer(cmdr_name)
        cmdr.location = self.location
        if ship_internal_name:
            vehicle = EDVehicleFactory.from_internal_name(ship_internal_name)
            cmdr.update_vehicle_if_obsolete(vehicle, piloted)
        self.instance.player_in(cmdr)
        return cmdr

    def deinstanced(self, cmdr_name):
        self._touch()
        self.instance.player_out(cmdr_name)

    def attacked(self, target):
        self._touch()
        if target == u"Mothership":
            self.mothership.attacked()
        elif target == u"Fighter":
            if self.slf:
                self.slf.attacked()
            else:
                EDRLOG.log(u"SLF attacked but player had none", u"WARNING")
        elif target == u"You":
            self.piloted_vehicle.attacked()

    def update_fleet(self, stored_ships_entry):
        self.fleet.update(stored_ships_entry)

    def prospected(self, entry):
        self.mining_stats.prospected(entry)

    def refined(self, entry):
        self.mining_stats.refined(entry)

    def reset_mining_stats(self):
        self.mining_stats.reset()
Exemple #25
0
class EDRInventory(object):
    EDR_INVENTORY_ENCODED_CACHE = utils2to3.abspathmaker(
        __file__, 'cache', 'encoded_mats.v1.p')
    EDR_INVENTORY_RAW_CACHE = utils2to3.abspathmaker(__file__, 'cache',
                                                     'raw_mats.v1.p')
    EDR_INVENTORY_MANUFACTURED_CACHE = utils2to3.abspathmaker(
        __file__, 'cache', 'manufactured_mats.v1.p')

    MATERIALS_LUT = {
        "zinc": {
            "localized": _(u"Zinc"),
            "raw": "Zinc",
            "category": "raw",
            "grade": 2
        },
        "mercury": {
            "localized": _(u"Mercury"),
            "raw": "Mercury",
            "category": "raw",
            "grade": 3
        },
        "polonium": {
            "localized": _(u"Polonium"),
            "raw": "Polonium",
            "category": "raw",
            "grade": 4
        },
        "tellurium": {
            "localized": _(u"Tellurium"),
            "raw": "Tellurium",
            "category": "raw",
            "grade": 4
        },
        "yttrium": {
            "localized": _(u"Yttrium"),
            "raw": "Yttrium",
            "category": "raw",
            "grade": 4
        },
        "antimony": {
            "localized": _(u"Antimony"),
            "raw": "Antimony",
            "category": "raw",
            "grade": 4
        },
        "selenium": {
            "localized": _(u"Selenium"),
            "raw": "Selenium",
            "category": "raw",
            "grade": 4
        },
        "ruthenium": {
            "localized": _(u"Ruthenium"),
            "raw": "Ruthenium",
            "category": "raw",
            "grade": 4
        },
        "zirconium": {
            "localized": _(u"Zirconium"),
            "raw": "Zirconium",
            "category": "raw",
            "grade": 2
        },
        "vanadium": {
            "localized": _(u"Vanadium"),
            "raw": "Vanadium",
            "category": "raw",
            "grade": 2
        },
        "manganese": {
            "localized": _(u"Manganese"),
            "raw": "Manganese",
            "category": "raw",
            "grade": 2
        },
        "chromium": {
            "localized": _(u"Chromium"),
            "raw": "Chromium",
            "category": "raw",
            "grade": 2
        },
        "molybdenum": {
            "localized": _(u"Molybdenum"),
            "raw": "Molybdenum",
            "category": "raw",
            "grade": 3
        },
        "technetium": {
            "localized": _(u"Technetium"),
            "raw": "Technetium",
            "category": "raw",
            "grade": 4
        },
        "tin": {
            "localized": _(u"Tin"),
            "raw": "Tin",
            "category": "raw",
            "grade": 3
        },
        "arsenic": {
            "localized": _(u"Arsenic"),
            "raw": "Arsenic",
            "category": "raw",
            "grade": 2
        },
        "cadmium": {
            "localized": _(u"Cadmium"),
            "raw": "Cadmium",
            "category": "raw",
            "grade": 3
        },
        "iron": {
            "localized": _(u"Iron"),
            "raw": "Iron",
            "category": "raw",
            "grade": 1
        },
        "niobium": {
            "localized": _(u"Niobium"),
            "raw": "Niobium",
            "category": "raw",
            "grade": 3
        },
        "phosphorus": {
            "localized": _(u"Phosphorus"),
            "raw": "Phosphorus",
            "category": "raw",
            "grade": 1
        },
        "germanium": {
            "localized": _(u"Germanium"),
            "raw": "Germanium",
            "category": "raw",
            "grade": 2
        },
        "tungsten": {
            "localized": _(u"Tungsten"),
            "raw": "Tungsten",
            "category": "raw",
            "grade": 3
        },
        "sulphur": {
            "localized": _(u"Sulphur"),
            "raw": "Sulphur",
            "category": "raw",
            "grade": 1
        },
        "carbon": {
            "localized": _(u"Carbon"),
            "raw": "Carbon",
            "category": "raw",
            "grade": 1
        },
        "nickel": {
            "localized": _(u"Nickel"),
            "raw": "Nickel",
            "category": "raw",
            "grade": 1
        },
        "rhenium": {
            "localized": _(u"Rhenium"),
            "raw": "Rhenium",
            "category": "raw",
            "grade": 1
        },
        "boron": {
            "localized": _(u"Boron"),
            "raw": "Boron",
            "category": "raw",
            "grade": 3
        },
        "lead": {
            "localized": _(u"Lead"),
            "raw": "Lead",
            "category": "raw",
            "grade": 1
        },
        "focuscrystals": {
            "localized": _(u"Focus Crystals"),
            "raw": "Focus Crystals",
            "category": "manufactured",
            "grade": 3
        },
        "compoundshielding": {
            "localized": _(u"Compound Shielding"),
            "raw": "Compound Shielding",
            "category": "manufactured",
            "grade": 4
        },
        "galvanisingalloys": {
            "localized": _(u"Galvanising Alloys"),
            "raw": "Galvanising Alloys",
            "category": "manufactured",
            "grade": 2
        },
        "heatvanes": {
            "localized": _(u"Heat Vanes"),
            "raw": "Heat Vanes",
            "category": "manufactured",
            "grade": 4
        },
        "configurablecomponents": {
            "localized": _(u"Configurable Components"),
            "raw": "Configurable Components",
            "category": "manufactured",
            "grade": 4
        },
        "biotechconductors": {
            "localized": _(u"Biotech Conductors"),
            "raw": "Biotech Conductors",
            "category": "manufactured",
            "grade": 5
        },
        "chemicalmanipulators": {
            "localized": _(u"Chemical Manipulators"),
            "raw": "Chemical Manipulators",
            "category": "manufactured",
            "grade": 4
        },
        "mechanicalcomponents": {
            "localized": _(u"Mechanical Components"),
            "raw": "Mechanical Components",
            "category": "manufactured",
            "grade": 3
        },
        "fedproprietarycomposites": {
            "localized": _(u"Proprietary Composites"),
            "raw": "Proprietary Composites",
            "category": "manufactured",
            "grade": 4
        },
        "highdensitycomposites": {
            "localized": _(u"High Density Composites"),
            "raw": "High Density Composites",
            "category": "manufactured",
            "grade": 3
        },
        "protoradiolicalloys": {
            "localized": _(u"Proto Radiolic Alloys"),
            "raw": "Proto Radiolic Alloys",
            "category": "manufactured",
            "grade": 5
        },
        "chemicaldistillery": {
            "localized": _(u"Chemical Distillery"),
            "raw": "Chemical Distillery",
            "category": "manufactured",
            "grade": 3
        },
        "chemicalprocessors": {
            "localized": _(u"Chemical Processors"),
            "raw": "Chemical Processors",
            "category": "manufactured",
            "grade": 2
        },
        "imperialshielding": {
            "localized": _(u"Imperial Shielding"),
            "raw": "Imperial Shielding",
            "category": "manufactured",
            "grade": 5
        },
        "gridresistors": {
            "localized": _(u"Grid Resistors"),
            "raw": "Grid Resistors",
            "category": "manufactured",
            "grade": 1
        },
        "heatconductionwiring": {
            "localized": _(u"Heat Conduction Wiring"),
            "raw": "Heat Conduction Wiring",
            "category": "manufactured",
            "grade": 1
        },
        "militarygradealloys": {
            "localized": _(u"Military Grade Alloys"),
            "raw": "Military Grade Alloys",
            "category": "manufactured",
            "grade": 5
        },
        "hybridcapacitors": {
            "localized": _(u"Hybrid Capacitors"),
            "raw": "Hybrid Capacitors",
            "category": "manufactured",
            "grade": 2
        },
        "heatexchangers": {
            "localized": _(u"Heat Exchangers"),
            "raw": "Heat Exchangers",
            "category": "manufactured",
            "grade": 3
        },
        "conductivepolymers": {
            "localized": _(u"Conductive Polymers"),
            "raw": "Conductive Polymers",
            "category": "manufactured",
            "grade": 4
        },
        "shieldingsensors": {
            "localized": _(u"Shielding Sensors"),
            "raw": "Shielding Sensors",
            "category": "manufactured",
            "grade": 3
        },
        "heatdispersionplate": {
            "localized": _(u"Heat Dispersion Plate"),
            "raw": "Heat Dispersion Plate",
            "category": "manufactured",
            "grade": 2
        },
        "electrochemicalarrays": {
            "localized": _(u"Electrochemical Arrays"),
            "raw": "Electrochemical Arrays",
            "category": "manufactured",
            "grade": 1
        },
        "conductiveceramics": {
            "localized": _(u"Conductive Ceramics"),
            "raw": "Conductive Ceramics",
            "category": "manufactured",
            "grade": 3
        },
        "conductivecomponents": {
            "localized": _(u"Conductive Components"),
            "raw": "Conductive Components",
            "category": "manufactured",
            "grade": 2
        },
        "militarysupercapacitors": {
            "localized": _(u"Military Supercapacitors"),
            "raw": "Military Supercapacitors",
            "category": "manufactured",
            "grade": 5
        },
        "mechanicalequipment": {
            "localized": _(u"Mechanical Equipment"),
            "raw": "Mechanical Equipment",
            "category": "manufactured",
            "grade": 2
        },
        "phasealloys": {
            "localized": _(u"Phase Alloys"),
            "raw": "Phase Alloys",
            "category": "manufactured",
            "grade": 3
        },
        "pharmaceuticalisolators": {
            "localized": _(u"Pharmaceutical Isolators"),
            "raw": "Pharmaceutical Isolators",
            "category": "manufactured",
            "grade": 5
        },
        "fedcorecomposites": {
            "localized": _(u"Core Dynamics Composites"),
            "raw": "Core Dynamics Composites",
            "category": "manufactured",
            "grade": 5
        },
        "basicconductors": {
            "localized": _(u"Basic Conductors"),
            "raw": "Basic Conductors",
            "category": "manufactured",
            "grade": 1
        },
        "mechanicalscrap": {
            "localized": _(u"Mechanical Scrap"),
            "raw": "Mechanical Scrap",
            "category": "manufactured",
            "grade": 1
        },
        "salvagedalloys": {
            "localized": _(u"Salvaged Alloys"),
            "raw": "Salvaged Alloys",
            "category": "manufactured",
            "grade": 1
        },
        "protolightalloys": {
            "localized": _(u"Proto Light Alloys"),
            "raw": "Proto Light Alloys",
            "category": "manufactured",
            "grade": 4
        },
        "refinedfocuscrystals": {
            "localized": _(u"Refined Focus Crystals"),
            "raw": "Refined Focus Crystals",
            "category": "manufactured",
            "grade": 4
        },
        "shieldemitters": {
            "localized": _(u"Shield Emitters"),
            "raw": "Shield Emitters",
            "category": "manufactured",
            "grade": 1
        },
        "precipitatedalloys": {
            "localized": _(u"Precipitated Alloys"),
            "raw": "Precipitated Alloys",
            "category": "manufactured",
            "grade": 3
        },
        "wornshieldemitters": {
            "localized": _(u"Worn Shield Emitters"),
            "raw": "Worn Shield Emitters",
            "category": "manufactured",
            "grade": 1
        },
        "exquisitefocuscrystals": {
            "localized": _(u"Exquisite Focus Crystals"),
            "raw": "Exquisite Focus Crystals",
            "category": "manufactured",
            "grade": 5
        },
        "polymercapacitors": {
            "localized": _(u"Polymer Capacitors"),
            "raw": "Polymer Capacitors",
            "category": "manufactured",
            "grade": 4
        },
        "thermicalloys": {
            "localized": _(u"Thermic Alloys"),
            "raw": "Thermic Alloys",
            "category": "manufactured",
            "grade": 4
        },
        "improvisedcomponents": {
            "localized": _(u"Improvised Components"),
            "raw": "Improvised Components",
            "category": "manufactured",
            "grade": 5
        },
        "crystalshards": {
            "localized": _(u"Crystal Shards"),
            "raw": "Crystal Shards",
            "category": "manufactured",
            "grade": 1
        },
        "heatresistantceramics": {
            "localized": _(u"Heat Resistant Ceramics"),
            "raw": "Heat Resistant Ceramics",
            "category": "manufactured",
            "grade": 2
        },
        "temperedalloys": {
            "localized": _(u"Tempered Alloys"),
            "raw": "Tempered Alloys",
            "category": "manufactured",
            "grade": 1
        },
        "uncutfocuscrystals": {
            "localized": _(u"Flawed Focus Crystals"),
            "raw": "Flawed Focus Crystals",
            "category": "manufactured",
            "grade": 2
        },
        "filamentcomposites": {
            "localized": _(u"Filament Composites"),
            "raw": "Filament Composites",
            "category": "manufactured",
            "grade": 2
        },
        "compactcomposites": {
            "localized": _(u"Compact Composites"),
            "raw": "Compact Composites",
            "category": "manufactured",
            "grade": 1
        },
        "chemicalstorageunits": {
            "localized": _(u"Chemical Storage Units"),
            "raw": "Chemical Storage Units",
            "category": "manufactured",
            "grade": 1
        },
        "protoheatradiators": {
            "localized": _(u"Proto Heat Radiators"),
            "raw": "Proto Heat Radiators",
            "category": "manufactured",
            "grade": 5
        },
        "guardian_powerconduit": {
            "localized": _(u"Guardian Power Conduit"),
            "raw": "Guardian Power Conduit",
            "category": "manufactured",
            "grade": 2
        },
        "guardian_powercell": {
            "localized": _(u"Guardian Power Cell"),
            "raw": "Guardian Power Cell",
            "category": "manufactured",
            "grade": 1
        },
        "guardian_techcomponent": {
            "localized": _(u"Guardian Technology Component"),
            "raw": "Guardian Technology Component",
            "category": "manufactured",
            "grade": 3
        },
        "guardian_sentinel_wreckagecomponents": {
            "localized": _(u"Guardian Wreckage Components"),
            "raw": "Guardian Wreckage Components",
            "category": "manufactured",
            "grade": 1
        },
        "guardian_sentinel_weaponparts": {
            "localized": _(u"Guardian Sentinel Weapon Parts"),
            "raw": "Guardian Sentinel Weapon Parts",
            "category": "manufactured",
            "grade": 3
        },
        "classifiedscandata": {
            "localized": _(u"Classified Scan Fragment"),
            "raw": "Classified Scan Fragment",
            "category": "encoded",
            "grade": 5
        },
        "securityfirmware": {
            "localized": _(u"Security Firmware Patch"),
            "raw": "Security Firmware Patch",
            "category": "encoded",
            "grade": 4
        },
        "dataminedwake": {
            "localized": _(u"Datamined Wake Exceptions"),
            "raw": "Datamined Wake Exceptions",
            "category": "encoded",
            "grade": 5
        },
        "compactemissionsdata": {
            "localized": _(u"Abnormal Compact Emissions Data"),
            "raw": "Abnormal Compact Emissions Data",
            "category": "encoded",
            "grade": 5
        },
        "shieldpatternanalysis": {
            "localized": _(u"Aberrant Shield Pattern Analysis"),
            "raw": "Aberrant Shield Pattern Analysis",
            "category": "encoded",
            "grade": 4
        },
        "adaptiveencryptors": {
            "localized": _(u"Adaptive Encryptors Capture"),
            "raw": "Adaptive Encryptors Capture",
            "category": "encoded",
            "grade": 5
        },
        "emissiondata": {
            "localized": _(u"Unexpected Emission Data"),
            "raw": "Unexpected Emission Data",
            "category": "encoded",
            "grade": 3
        },
        "industrialfirmware": {
            "localized": _(u"Cracked Industrial Firmware"),
            "raw": "Cracked Industrial Firmware",
            "category": "encoded",
            "grade": 3
        },
        "scandatabanks": {
            "localized": _(u"Classified Scan Databanks"),
            "raw": "Classified Scan Databanks",
            "category": "encoded",
            "grade": 3
        },
        "legacyfirmware": {
            "localized": _(u"Specialised Legacy Firmware"),
            "raw": "Specialised Legacy Firmware",
            "category": "encoded",
            "grade": 1
        },
        "embeddedfirmware": {
            "localized": _(u"Modified Embedded Firmware"),
            "raw": "Modified Embedded Firmware",
            "category": "encoded",
            "grade": 5
        },
        "shieldcyclerecordings": {
            "localized": _(u"Distorted Shield Cycle Recordings"),
            "raw": "Distorted Shield Cycle Recordings",
            "category": "encoded",
            "grade": 1
        },
        "decodedemissiondata": {
            "localized": _(u"Decoded Emission Data"),
            "raw": "Decoded Emission Data",
            "category": "encoded",
            "grade": 4
        },
        "bulkscandata": {
            "localized": _(u"Anomalous Bulk Scan Data"),
            "raw": "Anomalous Bulk Scan Data",
            "category": "encoded",
            "grade": 1
        },
        "scanarchives": {
            "localized": _(u"Unidentified Scan Archives"),
            "raw": "Unidentified Scan Archives",
            "category": "encoded",
            "grade": 2
        },
        "shieldsoakanalysis": {
            "localized": _(u"Inconsistent Shield Soak Analysis"),
            "raw": "Inconsistent Shield Soak Analysis",
            "category": "encoded",
            "grade": 2
        },
        "encodedscandata": {
            "localized": _(u"Divergent Scan Data"),
            "raw": "Divergent Scan Data",
            "category": "encoded",
            "grade": 4
        },
        "shielddensityreports": {
            "localized": _(u"Untypical Shield Scans"),
            "raw": "Untypical Shield Scans",
            "category": "encoded",
            "grade": 3
        },
        "shieldfrequencydata": {
            "localized": _(u"Peculiar Shield Frequency Data"),
            "raw": "Peculiar Shield Frequency Data",
            "category": "encoded",
            "grade": 5
        },
        "encryptioncodes": {
            "localized": _(u"Tagged Encryption Codes"),
            "raw": "Tagged Encryption Codes",
            "category": "encoded",
            "grade": 2
        },
        "consumerfirmware": {
            "localized": _(u"Modified Consumer Firmware"),
            "raw": "Modified Consumer Firmware",
            "category": "encoded",
            "grade": 2
        },
        "archivedemissiondata": {
            "localized": _(u"Irregular Emission Data"),
            "raw": "Irregular Emission Data",
            "category": "encoded",
            "grade": 2
        },
        "symmetrickeys": {
            "localized": _(u"Open Symmetric Keys"),
            "raw": "Open Symmetric Keys",
            "category": "encoded",
            "grade": 3
        },
        "encryptedfiles": {
            "localized": _(u"Unusual Encrypted Files"),
            "raw": "Unusual Encrypted Files",
            "category": "encoded",
            "grade": 1
        },
        "scrambledemissiondata": {
            "localized": _(u"Exceptional Scrambled Emission Data"),
            "raw": "Exceptional Scrambled Emission Data",
            "category": "encoded",
            "grade": 1
        },
        "fsdtelemetry": {
            "localized": _(u"Anomalous FSD Telemetry"),
            "raw": "Anomalous FSD Telemetry",
            "category": "encoded",
            "grade": 2
        },
        "hyperspacetrajectories": {
            "localized": _(u"Eccentric Hyperspace Trajectories"),
            "raw": "Eccentric Hyperspace Trajectories",
            "category": "encoded",
            "grade": 4
        },
        "disruptedwakeechoes": {
            "localized": _(u"Atypical Disrupted Wake Echoes"),
            "raw": "Atypical Disrupted Wake Echoes",
            "category": "encoded",
            "grade": 1
        },
        "wakesolutions": {
            "localized": _(u"Strange Wake Solutions"),
            "raw": "Strange Wake Solutions",
            "category": "encoded",
            "grade": 3
        },
        "encryptionarchives": {
            "localized": _(u"Atypical Encryption Archives"),
            "raw": "Atypical Encryption Archives",
            "category": "encoded",
            "grade": 4
        },
        "ancientbiologicaldata": {
            "localized": _(u"Pattern Alpha Obelisk Data"),
            "raw": "Pattern Alpha Obelisk Data",
            "category": "encoded",
            "grade": 3
        },
        "ancienthistoricaldata": {
            "localized": _(u"Pattern Gamma Obelisk Data"),
            "raw": "Pattern Gamma Obelisk Data",
            "category": "encoded",
            "grade": 4
        },
        "guardian_moduleblueprint": {
            "localized": _(u"Guardian Module Blueprint Fragment"),
            "raw": "Guardian Module Blueprint Fragment",
            "category": "encoded",
            "grade": 5
        },
        "ancientculturaldata": {
            "localized": _(u"Pattern Beta Obelisk Data"),
            "raw": "Pattern Beta Obelisk Data",
            "category": "encoded",
            "grade": 2
        },
        "ancientlanguagedata": {
            "localized": _(u"Pattern Delta Obelisk Data"),
            "raw": "Pattern Delta Obelisk Data",
            "category": "encoded",
            "grade": 4
        },
        "guardian_vesselblueprint": {
            "localized": _(u"Guardian Starship Blueprint Fragment"),
            "raw": "Guardian Starship Blueprint Fragment",
            "category": "encoded",
            "grade": 5
        },
        "guardian_weaponblueprint": {
            "localized": _(u"Guardian Weapon Blueprint Fragment"),
            "raw": "Guardian Weapon Blueprint Fragment",
            "category": "encoded",
            "grade": 5
        },
        "ancienttechnologicaldata": {
            "localized": _(u"Pattern Epsilon Obelisk Data"),
            "raw": "Pattern Epsilon Obelisk Data",
            "category": "encoded",
            "grade": 5
        },
        "tg_shipsystemsdata": {
            "localized": _(u"Ship Systems Data"),
            "raw": "Ship Systems Data",
            "category": "encoded",
            "grade": 3
        },
        "tg_shipflightdata": {
            "localized": _(u"Ship Flight Data"),
            "raw": "Ship Flight Data",
            "category": "encoded",
            "grade": 3
        },
        "unknownshipsignature": {
            "localized": _(u"Thargoid Ship Signature"),
            "raw": "Thargoid Ship Signature",
            "category": "encoded",
            "grade": 3
        },
        "tg_structuraldata": {
            "localized": _(u"Thargoid Structural Data"),
            "raw": "Thargoid Structural Data",
            "category": "encoded",
            "grade": 2
        },
        "unknownwakedata": {
            "localized": _(u"Thargoid Wake Data"),
            "raw": "Thargoid Wake Data",
            "category": "encoded",
            "grade": 4
        },
        "tg_biomechanicalconduits": {
            "localized": _(u"Bio-Mechanical Conduits"),
            "raw": "Bio-Mechanical Conduits",
            "category": "manufactured",
            "grade": 3
        },
        "tg_propulsionelement": {
            "localized": _(u"Propulsion Elements"),
            "raw": "Propulsion Elements",
            "category": "manufactured",
            "grade": 3
        },
        "unknowncarapace": {
            "localized": _(u"Thargoid Carapace"),
            "raw": "Thargoid Carapace",
            "category": "manufactured",
            "grade": 2
        },
        "unknownenergycell": {
            "localized": _(u"Thargoid Energy Cell"),
            "raw": "Thargoid Energy Cell",
            "category": "manufactured",
            "grade": 3
        },
        "unknownorganiccircuitry": {
            "localized": _(u"Thargoid Organic Circuitry"),
            "raw": "Thargoid Organic Circuitry",
            "category": "manufactured",
            "grade": 5
        },
        "unknowntechnologycomponents": {
            "localized": _(u"Thargoid Technological Components"),
            "raw": "Thargoid Technological Components",
            "category": "manufactured",
            "grade": 4
        }
    }

    INTERNAL_NAMES_LUT = {
        u'classified scan databanks': 'scandatabanks',
        u'conductive components': 'conductivecomponents',
        u'abnormal compact emissions data': 'compactemissionsdata',
        u'germanium': 'germanium',
        u'atypical disrupted wake echoes': 'disruptedwakeechoes',
        u'crystal shards': 'crystalshards',
        u'selenium': 'selenium',
        u'technetium': 'technetium',
        u'galvanising alloys': 'galvanisingalloys',
        u'improvised components': 'improvisedcomponents',
        u'cracked industrial firmware': 'industrialfirmware',
        u'guardian technology component': 'guardian_techcomponent',
        u'heat resistant ceramics': 'heatresistantceramics',
        u'unexpected emission data': 'emissiondata',
        u'tungsten': 'tungsten',
        u'exceptional scrambled emission data': 'scrambledemissiondata',
        u'thermic alloys': 'thermicalloys',
        u'molybdenum': 'molybdenum',
        u'atypical encryption archives': 'encryptionarchives',
        u'salvaged alloys': 'salvagedalloys',
        u'pharmaceutical isolators': 'pharmaceuticalisolators',
        u'divergent scan data': 'encodedscandata',
        u'anomalous fsd telemetry': 'fsdtelemetry',
        u'pattern delta obelisk data': 'ancientlanguagedata',
        u'worn shield emitters': 'wornshieldemitters',
        u'strange wake solutions': 'wakesolutions',
        u'tempered alloys': 'temperedalloys',
        u'zinc': 'zinc',
        u'mechanical equipment': 'mechanicalequipment',
        u'eccentric hyperspace trajectories': 'hyperspacetrajectories',
        u'grid resistors': 'gridresistors',
        u'unusual encrypted files': 'encryptedfiles',
        u'peculiar shield frequency data': 'shieldfrequencydata',
        u'specialised legacy firmware': 'legacyfirmware',
        u'flawed focus crystals': 'uncutfocuscrystals',
        u'pattern beta obelisk data': 'ancientculturaldata',
        u'antimony': 'antimony',
        u'untypical shield scans': 'shielddensityreports',
        u'focus crystals': 'focuscrystals',
        u'lead': 'lead',
        u'heat dispersion plate': 'heatdispersionplate',
        u'irregular emission data': 'archivedemissiondata',
        u'guardian module blueprint fragment': 'guardian_moduleblueprint',
        u'yttrium': 'yttrium',
        u'mechanical scrap': 'mechanicalscrap',
        u'biotech conductors': 'biotechconductors',
        u'military grade alloys': 'militarygradealloys',
        u'basic conductors': 'basicconductors',
        u'boron': 'boron',
        u'carbon': 'carbon',
        u'unidentified scan archives': 'scanarchives',
        u'imperial shielding': 'imperialshielding',
        u'chemical distillery': 'chemicaldistillery',
        u'guardian wreckage components':
        'guardian_sentinel_wreckagecomponents',
        u'proto radiolic alloys': 'protoradiolicalloys',
        u'proto heat radiators': 'protoheatradiators',
        u'cadmium': 'cadmium',
        u'filament composites': 'filamentcomposites',
        u'exquisite focus crystals': 'exquisitefocuscrystals',
        u'electrochemical arrays': 'electrochemicalarrays',
        u'mechanical components': 'mechanicalcomponents',
        u'pattern alpha obelisk data': 'ancientbiologicaldata',
        u'arsenic': 'arsenic',
        u'chromium': 'chromium',
        u'conductive ceramics': 'conductiveceramics',
        u'mercury': 'mercury',
        u'chemical processors': 'chemicalprocessors',
        u'pattern gamma obelisk data': 'ancienthistoricaldata',
        u'proprietary composites': 'fedproprietarycomposites',
        u'proto light alloys': 'protolightalloys',
        u'datamined wake exceptions': 'dataminedwake',
        u'adaptive encryptors capture': 'adaptiveencryptors',
        u'open symmetric keys': 'symmetrickeys',
        u'nickel': 'nickel',
        u'ruthenium': 'ruthenium',
        u'guardian sentinel weapon parts': 'guardian_sentinel_weaponparts',
        u'decoded emission data': 'decodedemissiondata',
        u'guardian power cell': 'guardian_powercell',
        u'chemical storage units': 'chemicalstorageunits',
        u'sulphur': 'sulphur',
        u'anomalous bulk scan data': 'bulkscandata',
        u'refined focus crystals': 'refinedfocuscrystals',
        u'zirconium': 'zirconium',
        u'heat vanes': 'heatvanes',
        u'niobium': 'niobium',
        u'iron': 'iron',
        u'conductive polymers': 'conductivepolymers',
        u'configurable components': 'configurablecomponents',
        u'rhenium': 'rhenium',
        u'security firmware patch': 'securityfirmware',
        u'aberrant shield pattern analysis': 'shieldpatternanalysis',
        u'modified consumer firmware': 'consumerfirmware',
        u'military supercapacitors': 'militarysupercapacitors',
        u'heat conduction wiring': 'heatconductionwiring',
        u'inconsistent shield soak analysis': 'shieldsoakanalysis',
        u'distorted shield cycle recordings': 'shieldcyclerecordings',
        u'shield emitters': 'shieldemitters',
        u'tin': 'tin',
        u'chemical manipulators': 'chemicalmanipulators',
        u'hybrid capacitors': 'hybridcapacitors',
        u'tagged encryption codes': 'encryptioncodes',
        u'classified scan fragment': 'classifiedscandata',
        u'polymer capacitors': 'polymercapacitors',
        u'precipitated alloys': 'precipitatedalloys',
        u'heat exchangers': 'heatexchangers',
        u'polonium': 'polonium',
        u'core dynamics composites': 'fedcorecomposites',
        u'high density composites': 'highdensitycomposites',
        u'modified embedded firmware': 'embeddedfirmware',
        u'phosphorus': 'phosphorus',
        u'guardian power conduit': 'guardian_powerconduit',
        u'vanadium': 'vanadium',
        u'shielding sensors': 'shieldingsensors',
        u'compound shielding': 'compoundshielding',
        u'manganese': 'manganese',
        u'compact composites': 'compactcomposites',
        u'tellurium': 'tellurium',
        u'phase alloys': 'phasealloys',
        u'thargoid organic circuitry': u'unknownorganiccircuitry',
        u'thargoid energy cell': u'unknownenergycell',
        u'thargoid structural data': u'tg_structuraldata',
        u'thargoid ship signature': u'unknownshipsignature',
        u'thargoid carapace': u'unknowncarapace',
        u'propulsion elements': u'tg_propulsionelement',
        u'guardian weapon blueprint fragment': u'guardian_weaponblueprint',
        u'guardian starship blueprint fragment': u'guardian_vesselblueprint',
        u'pattern epsilon obelisk data': u'ancienttechnologicaldata',
        u'bio-mechanical conduits': u'tg_biomechanicalconduits',
        u'ship flight data': u'tg_shipflightdata',
        u'thargoid wake data': u'unknownwakedata',
        u'thargoid technological components': u'unknowntechnologycomponents',
        u'ship systems data': u'tg_shipsystemsdata'
    }

    def __init__(self):
        self.initialized = False
        self.inconsistencies = False
        try:
            with open(self.EDR_INVENTORY_ENCODED_CACHE, 'rb') as handle:
                self.encoded = pickle.load(handle)
        except:
            self.encoded = {}

        try:
            with open(self.EDR_INVENTORY_RAW_CACHE, 'rb') as handle:
                self.raw = pickle.load(handle)
        except:
            self.raw = {}

        try:
            with open(self.EDR_INVENTORY_MANUFACTURED_CACHE, 'rb') as handle:
                self.manufactured = pickle.load(handle)
        except:
            self.manufactured = {}
        self.__check()

    def initialize(self, materials):
        for thing in materials.get("Encoded", []):
            cname = self.__c_name(thing["Name"])
            self.encoded[cname] = thing["Count"]

        for thing in materials.get("Raw", []):
            cname = self.__c_name(thing["Name"])
            self.raw[cname] = thing["Count"]

        for thing in materials.get("Manufactured", []):
            cname = self.__c_name(thing["Name"])
            self.manufactured[cname] = thing["Count"]
        self.initialized = True
        self.inconsistencies = False

    def initialize_with_edmc(self, state):
        for thing in state.get("Encoded", {}):
            cname = self.__c_name(thing)
            self.encoded[cname] = state["Encoded"][thing]

        for thing in state.get("Raw", {}):
            cname = self.__c_name(thing)
            self.raw[cname] = state["Raw"][thing]

        for thing in state.get("Manufactured", {}):
            cname = self.__c_name(thing)
            self.manufactured[cname] = state["Manufactured"][thing]
        self.initialized = True
        self.inconsistencies = False

    def stale_or_incorrect(self):
        return not self.initialized or self.inconsistencies

    def persist(self):
        with open(self.EDR_INVENTORY_ENCODED_CACHE, 'wb') as handle:
            pickle.dump(self.encoded, handle, protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_INVENTORY_MANUFACTURED_CACHE, 'wb') as handle:
            pickle.dump(self.manufactured,
                        handle,
                        protocol=pickle.HIGHEST_PROTOCOL)

        with open(self.EDR_INVENTORY_RAW_CACHE, 'wb') as handle:
            pickle.dump(self.raw, handle, protocol=pickle.HIGHEST_PROTOCOL)

    def collected(self, info):
        self.add(info["Category"], info["Name"], info["Count"])

    def discarded(self, info):
        self.substract(info["Category"], info["Name"], info["Count"])

    def count(self, name):
        cname = self.__c_name(name)
        category = self.category(cname)
        if category == "encoded":
            return self.encoded.get(cname, 0)
        elif category == "raw":
            return self.raw.get(cname, 0)
        elif category == "manufactured":
            return self.manufactured.get(cname, 0)
        return 0

    def oneliner(self, name):
        cname = self.__c_name(name)
        category = self.category(cname)
        entry = self.MATERIALS_LUT.get(cname, None)
        if not category or not entry:
            return name
        count = self.count(cname)
        grades = [u"?", u"Ⅰ", u"Ⅱ", u"Ⅲ", u"Ⅳ", u"Ⅴ"]
        slots = [u"?", u"300", u"250", u"200", u"150", u"100"]
        return u"{} (Grade {}; {}/{})".format(_(entry["raw"]),
                                              grades[entry["grade"]], count,
                                              slots[entry["grade"]])

    def __check(self):
        self.inconsistencies = False
        for collection in [self.encoded, self.raw, self.manufactured]:
            for thing in collection:
                self.__check_item(thing)
                if self.inconsistencies:
                    return False
        return True

    def __check_item(self, name):
        cname = self.__c_name(name)
        entry = self.MATERIALS_LUT.get(cname, None)
        if not entry:
            return False
        count = self.count(cname)
        if count < 0:
            self.inconsistencies = True
            return False
        max_for_slot = self.slots(name)
        if count > max_for_slot:
            self.inconsistencies = True
            return False
        return True

    def donated_engineer(self, info):
        if info["Type"] != "Material":
            return
        category = self.category(info["Name"])
        if category:
            self.substract(category, info["Name"], info["Quantity"])

    def donated_science(self, info):
        self.substract(info["Category"], info["Name"], info["Count"])

    def consumed(self, ingredients):
        for ingredient in ingredients:
            category = ingredient.get("Category",
                                      self.category(ingredient["Name"]))
            if category:
                self.substract(category, ingredient["Name"],
                               ingredient["Count"])

    def traded(self, info):
        paid = info["Paid"]
        self.substract(paid["Category"], paid["Material"], paid["Quantity"])
        received = info["Received"]
        self.add(received["Category"], received["Material"],
                 received["Quantity"])

    def rewarded(self, info):
        # TODO Does Search And Rescue give material rewards??
        if "MaterialsReward" not in info:
            return
        for reward in info["MaterialsReward"]:
            self.add(reward["Category"], reward["Name"], reward["Count"])

    def add(self, category, name, count):
        ccategory = self.__c_cat(category)
        cname = self.__c_name(name)
        if ccategory == "encoded":
            self.encoded[cname] = min(
                self.encoded.get(cname, 0) + count, self.slots(name))
        elif ccategory == "raw":
            self.raw[cname] = min(
                self.raw.get(cname, 0) + count, self.slots(name))
        elif ccategory == "manufactured":
            self.manufactured[cname] = min(
                self.manufactured.get(cname, 0) + count, self.slots(name))

    def slots(self, name):
        cname = self.__c_name(name)
        entry = self.MATERIALS_LUT.get(cname, None)
        if not entry:
            return 100
        slots = [100, 300, 250, 200, 150, 100]
        return slots[entry["grade"]]

    def substract(self, category, name, count):
        ccategory = self.__c_cat(category)
        cname = self.__c_name(name)
        if ccategory == "encoded":
            self.encoded[cname] = max(self.encoded.get(cname, 0) - count, 0)
        elif ccategory == "raw":
            self.raw[cname] = max(self.raw.get(cname, 0) - count, 0)
        elif ccategory == "manufactured":
            self.manufactured[cname] = max(
                self.manufactured.get(cname, 0) - count, 0)

    def category(self, name):
        cname = self.__c_name(name)
        entry = self.MATERIALS_LUT.get(cname, None)
        return entry["category"] if entry else None

    def __c_cat(self, category):
        ccat = category.lower()
        if ccat.endswith(u";"):
            ccat = ccat[:-1]
        if ccat.startswith(u"$MICRORESOURCE_CATEGORY_"):
            useless_prefix_length = len(u"$MICRORESOURCE_CATEGORY_")
            ccat = ccat[useless_prefix_length:]
        return ccat

    def __c_name(self, name):
        cname = name.lower()
        if cname in self.MATERIALS_LUT:
            return cname
        return self.INTERNAL_NAMES_LUT.get(cname, cname)