From 3c4174d23fe45fe02f276012f7f68286b821c7a2 Mon Sep 17 00:00:00 2001 From: A_D Date: Sun, 1 Aug 2021 08:57:28 +0200 Subject: [PATCH 01/27] Added monitor state TypedDict definitions These don't actually change types or make anything more safe. What they WILL do is make mypy and co complain loudly if we make mistakes. --- monitor.py | 43 +++++++++-------- monitor_state_dict.py | 110 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 monitor_state_dict.py diff --git a/monitor.py b/monitor.py index f1390b142..ad50b5a46 100644 --- a/monitor.py +++ b/monitor.py @@ -1,6 +1,7 @@ """Monitor for new Journal files and contents of latest.""" import json +from monitor_state_dict import MonitorStateDict import pathlib import queue import re @@ -115,13 +116,13 @@ def __init__(self) -> None: def __init_state(self) -> None: # Cmdr state shared with EDSM and plugins # If you change anything here update PLUGINS.md documentation! - self.state: Dict = { + self.state: MonitorStateDict = { 'GameLanguage': None, # From `Fileheader 'GameVersion': None, # From `Fileheader 'GameBuild': None, # From `Fileheader 'Captain': None, # On a crew 'Cargo': defaultdict(int), - 'Credits': None, + 'Credits': -1, 'FID': None, # Frontier Cmdr ID 'Horizons': None, # Does this user have Horizons? 'Odyssey': False, # Have we detected we're running under Odyssey? @@ -541,23 +542,24 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.systemaddress = None self.started = timegm(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) # Don't set Ship, ShipID etc since this will reflect Fighter or SRV if starting in those - self.state.update({ - 'Captain': None, - 'Credits': entry['Credits'], - 'FID': entry.get('FID'), # From 3.3 - 'Horizons': entry['Horizons'], # From 3.0 - 'Odyssey': entry.get('Odyssey', False), # From 4.0 Odyssey - 'Loan': entry['Loan'], - 'Engineers': {}, - 'Rank': {}, - 'Reputation': {}, - 'Statistics': {}, - 'Role': None, - 'Taxi': None, - 'Dropship': None, - 'Body': None, - 'BodyType': None, - }) + + # Cant use update() without the entire thing, do stuff manually here + self.state['Captain'] = None + self.state['Credits'] = entry['Credits'] + self.state['FID'] = entry.get('FID') # From 3.3 + self.state['Horizons'] = entry['Horizons'] # From 3.0 + self.state['Odyssey'] = entry.get('Odyssey', False) # From 4.0 Odyssey + self.state['Loan'] = entry['Loan'] + self.state['Engineers'] = {} + self.state['Rank'] = {} + self.state['Reputation'] = {} + self.state['Statistics'] = {} + self.state['Role'] = None + self.state['Taxi'] = None + self.state['Dropship'] = None + self.state['Body'] = None + self.state['BodyType'] = None + if entry.get('Ship') is not None and self._RE_SHIP_ONFOOT.search(entry['Ship']): self.state['OnFoot'] = True @@ -631,6 +633,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.state['Modules'][module['Slot']] = module elif event_type == 'modulebuy': + self.state['Modules'][entry['Slot']] = { 'Slot': entry['Slot'], 'Item': self.canonicalise(entry['BuyItem']), @@ -847,7 +850,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # From 3.3 full Cargo event (after the first one) is written to a separate file if 'Inventory' not in entry: with open(join(self.currentdir, 'Cargo.json'), 'rb') as h: # type: ignore - entry = json.load(h, object_pairs_hook=OrderedDict) # Preserve property order because why not? + entry = json.load(h) self.state['CargoJSON'] = entry clean = self.coalesce_cargo(entry['Inventory']) diff --git a/monitor_state_dict.py b/monitor_state_dict.py new file mode 100644 index 000000000..e48205642 --- /dev/null +++ b/monitor_state_dict.py @@ -0,0 +1,110 @@ +""" +Contains the definitions for monitor.state. + +This is essentially a stopgap while OOP state is worked on. +""" +from __future__ import annotations + +from typing import MutableMapping, TYPE_CHECKING, Any, DefaultDict, Dict, List, Literal, Optional, Set, Tuple, TypedDict + + +class MonitorStateDict(TypedDict): + """Top level state dictionary for monitor.py.""" + + # Game related + GameLanguage: Optional[str] # From `Fileheader` + GameVersion: Optional[str] # From `Fileheader` + GameBuild: Optional[str] # From `Fileheader` + Horizons: Optional[bool] # Does the player have Horizons? + Odyssey: bool # Have we detected Odyssey? + + # Multi-crew + + Captain: Optional[str] # If on a crew, the captian's name + Role: Optional[Literal['Idle', 'FireCon', 'FighterCon']] # Role in crew + + # Cmdr state + FID: Optional[str] # Frontier CMDR ID + Friends: Set[str] # Online Friends + Credits: int + Loan: Optional[int] + Engineers: Dict[Any, Any] # TODO + Rank: Dict[Any, Any] # TODO + Reputation: Dict[Any, Any] # TODO + Statistics: Dict[Any, Any] # This is very freeform. + + # Engineering Materials + Raw: DefaultDict[str, int] + Encoded: DefaultDict[str, int] + Manufactured: DefaultDict[str, int] + + # Ship + ShipID: Optional[str] + ShipIdent: Optional[str] + ShipName: Optional[str] + ShipType: Optional[str] + + HullValue: Optional[int] + ModulesValue: Optional[int] + Rebuy: Optional[int] + Modules: Optional[Dict[Any, Any]] # TODO + + # Cargo (yes technically its on the cmdr not the ship but this makes more sense.) + CargoJSON: Optional[MutableMapping[str, Any]] # Raw data from the last cargo.json read + Cargo: DefaultDict[str, int] + + # Navigation + Route: Optional[NavRoute] # Last route plotted + Body: Optional[str] + BodyType: Optional[str] + Taxi: Optional[bool] + Dropship: Optional[bool] + + # Odyssey + OnFoot: bool + Component: DefaultDict[str, int] + Item: DefaultDict[str, int] + Consumable: DefaultDict[str, int] + Data: DefaultDict[str, int] + BackPack: OdysseyBackpack + BackpackJSON: Optional[MutableMapping[str, Any]] # Direct from Game + ShipLockerJSON: Optional[MutableMapping[str, Any]] # Direct from Game + + SuitCurrent: Optional[int] # TODO: int? + Suits: Dict[Any, Any] # TODO: With additional class + SuitLoadoutCurrent: Optional[int] # TODO: int? + SuitLoadouts: Optional[Dict] # TODO: class? + + +class OdysseyBackpack(TypedDict): + """Odyssey Backpack contents (used when on-foot).""" + + Component: DefaultDict[str, int] + Item: DefaultDict[str, int] + Consumable: DefaultDict[str, int] + Data: DefaultDict[str, int] + + +class NavRoute(TypedDict): + """Description of navroute.json at time of writing.""" + + timestamp: str + route: List[NavRouteEntry] + + +class NavRouteEntry(TypedDict): + """Single NavRoute entry.""" + + StarSystem: str + SystemAddress: int + StarPos: Tuple[float, float, float] + StarClass: str + + +if TYPE_CHECKING: + test: MonitorStateDict = {} # type: ignore + + test['GameLanguage'] + test['Role'] = 'FighterCon' + + ... From 481170fef5f95698e99cce1c816feb321b71bc85 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 3 Aug 2021 13:58:27 +0200 Subject: [PATCH 02/27] Updated definitions based on feedback --- monitor_state_dict.py | 68 +++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/monitor_state_dict.py b/monitor_state_dict.py index e48205642..f9b7fbb80 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -5,17 +5,19 @@ """ from __future__ import annotations -from typing import MutableMapping, TYPE_CHECKING, Any, DefaultDict, Dict, List, Literal, Optional, Set, Tuple, TypedDict +from typing import ( + TYPE_CHECKING, Any, DefaultDict, Dict, List, Literal, MutableMapping, Optional, Set, Tuple, TypedDict +) class MonitorStateDict(TypedDict): """Top level state dictionary for monitor.py.""" # Game related - GameLanguage: Optional[str] # From `Fileheader` - GameVersion: Optional[str] # From `Fileheader` - GameBuild: Optional[str] # From `Fileheader` - Horizons: Optional[bool] # Does the player have Horizons? + GameLanguage: str # From `Fileheader` + GameVersion: str # From `Fileheader` + GameBuild: str # From `Fileheader` + Horizons: bool # Does the player have Horizons? Odyssey: bool # Have we detected Odyssey? # Multi-crew @@ -24,10 +26,10 @@ class MonitorStateDict(TypedDict): Role: Optional[Literal['Idle', 'FireCon', 'FighterCon']] # Role in crew # Cmdr state - FID: Optional[str] # Frontier CMDR ID + FID: str # Frontier CMDR ID Friends: Set[str] # Online Friends Credits: int - Loan: Optional[int] + Loan: int Engineers: Dict[Any, Any] # TODO Rank: Dict[Any, Any] # TODO Reputation: Dict[Any, Any] # TODO @@ -40,25 +42,26 @@ class MonitorStateDict(TypedDict): # Ship ShipID: Optional[str] - ShipIdent: Optional[str] + ShipIdent: str ShipName: Optional[str] ShipType: Optional[str] - HullValue: Optional[int] - ModulesValue: Optional[int] - Rebuy: Optional[int] - Modules: Optional[Dict[Any, Any]] # TODO + HullValue: int + ModulesValue: int + Rebuy: int + Modules: Dict[Any, Any] # TODO + ModuleInfo: MutableMapping[Any, Any] # From the game, freeform # Cargo (yes technically its on the cmdr not the ship but this makes more sense.) - CargoJSON: Optional[MutableMapping[str, Any]] # Raw data from the last cargo.json read + CargoJSON: MutableMapping[str, Any] # Raw data from the last cargo.json read Cargo: DefaultDict[str, int] # Navigation - Route: Optional[NavRoute] # Last route plotted - Body: Optional[str] - BodyType: Optional[str] - Taxi: Optional[bool] - Dropship: Optional[bool] + NavRoute: NavRouteDict # Last route plotted + Body: str + BodyType: str + Taxi: bool + Dropship: bool # Odyssey OnFoot: bool @@ -67,13 +70,13 @@ class MonitorStateDict(TypedDict): Consumable: DefaultDict[str, int] Data: DefaultDict[str, int] BackPack: OdysseyBackpack - BackpackJSON: Optional[MutableMapping[str, Any]] # Direct from Game - ShipLockerJSON: Optional[MutableMapping[str, Any]] # Direct from Game + BackpackJSON: MutableMapping[str, Any] # Direct from Game + ShipLockerJSON: MutableMapping[str, Any] # Direct from Game SuitCurrent: Optional[int] # TODO: int? Suits: Dict[Any, Any] # TODO: With additional class - SuitLoadoutCurrent: Optional[int] # TODO: int? - SuitLoadouts: Optional[Dict] # TODO: class? + SuitLoadoutCurrent: Optional[SuitLoadoutDict] # TODO: int? + SuitLoadouts: Dict[int, SuitLoadoutDict] # TODO: class? class OdysseyBackpack(TypedDict): @@ -85,20 +88,27 @@ class OdysseyBackpack(TypedDict): Data: DefaultDict[str, int] -class NavRoute(TypedDict): +class NavRouteDict(TypedDict): """Description of navroute.json at time of writing.""" - timestamp: str - route: List[NavRouteEntry] + timestamp: str + route: List[NavRouteEntry] class NavRouteEntry(TypedDict): """Single NavRoute entry.""" - StarSystem: str - SystemAddress: int - StarPos: Tuple[float, float, float] - StarClass: str + StarSystem: str + SystemAddress: int + StarPos: Tuple[float, float, float] + StarClass: str + + +class SuitLoadoutDict(TypedDict): + loadoutSlotId: int + suit: Any + name: str + slots: Dict[Any, Any] if TYPE_CHECKING: From 65464f04b4cfaa68efa83519d75d1bd342153cc1 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 3 Aug 2021 13:58:53 +0200 Subject: [PATCH 03/27] resolved type errors --- monitor.py | 152 +++++++++++++++++++++++++++++------------------------ 1 file changed, 82 insertions(+), 70 deletions(-) diff --git a/monitor.py b/monitor.py index ad50b5a46..57447dbe9 100644 --- a/monitor.py +++ b/monitor.py @@ -1,7 +1,7 @@ """Monitor for new Journal files and contents of latest.""" import json -from monitor_state_dict import MonitorStateDict +from monitor_state_dict import MonitorStateDict, NavRouteDict, SuitLoadoutDict import pathlib import queue import re @@ -117,16 +117,16 @@ def __init_state(self) -> None: # Cmdr state shared with EDSM and plugins # If you change anything here update PLUGINS.md documentation! self.state: MonitorStateDict = { - 'GameLanguage': None, # From `Fileheader - 'GameVersion': None, # From `Fileheader - 'GameBuild': None, # From `Fileheader + 'GameLanguage': '', # From `Fileheader + 'GameVersion': '', # From `Fileheader + 'GameBuild': '', # From `Fileheader 'Captain': None, # On a crew 'Cargo': defaultdict(int), 'Credits': -1, - 'FID': None, # Frontier Cmdr ID - 'Horizons': None, # Does this user have Horizons? + 'FID': '', # Frontier Cmdr ID + 'Horizons': False, # Does this user have Horizons? 'Odyssey': False, # Have we detected we're running under Odyssey? - 'Loan': None, + 'Loan': 0, 'Raw': defaultdict(int), 'Manufactured': defaultdict(int), 'Encoded': defaultdict(int), @@ -137,15 +137,15 @@ def __init_state(self) -> None: 'Role': None, # Crew role - None, Idle, FireCon, FighterCon 'Friends': set(), # Online friends 'ShipID': None, - 'ShipIdent': None, + 'ShipIdent': '', 'ShipName': None, 'ShipType': None, - 'HullValue': None, - 'ModulesValue': None, - 'Rebuy': None, - 'Modules': None, - 'CargoJSON': None, # The raw data from the last time cargo.json was read - 'Route': None, # Last plotted route from Route.json file + 'HullValue': 0, + 'ModulesValue': 0, + 'Rebuy': 0, + 'Modules': {}, + 'CargoJSON': {}, # The raw data from the last time cargo.json was read + 'NavRoute': NavRouteDict(timestamp='', route=[]), # Last plotted route from Route.json file 'OnFoot': False, # Whether we think you're on-foot 'Component': defaultdict(int), # Odyssey Components in Ship Locker 'Item': defaultdict(int), # Odyssey Items in Ship Locker @@ -157,16 +157,17 @@ def __init_state(self) -> None: 'Item': defaultdict(int), # BackPack Items 'Data': defaultdict(int), # Backpack Data }, - 'BackpackJSON': None, # Raw JSON from `Backpack.json` file, if available - 'ShipLockerJSON': None, # Raw JSON from the `ShipLocker.json` file, if available + 'BackpackJSON': {}, # Raw JSON from `Backpack.json` file, if available + 'ShipLockerJSON': {}, # Raw JSON from the `ShipLocker.json` file, if available 'SuitCurrent': None, 'Suits': {}, 'SuitLoadoutCurrent': None, 'SuitLoadouts': {}, - 'Taxi': None, # True whenever we are _in_ a taxi. ie, this is reset on Disembark etc. - 'Dropship': None, # Best effort as to whether or not the above taxi is a dropship. - 'Body': None, - 'BodyType': None, + 'Taxi': False, # True whenever we are _in_ a taxi. ie, this is reset on Disembark etc. + 'Dropship': False, # Best effort as to whether or not the above taxi is a dropship. + 'Body': '', + 'BodyType': '', + 'ModuleInfo': {}, } def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001 @@ -266,8 +267,8 @@ def stop(self) -> None: self.systemaddress = None self.is_beta = False self.state['OnFoot'] = False - self.state['Body'] = None - self.state['BodyType'] = None + self.state['Body'] = '' + self.state['BodyType'] = '' if self.observed: logger.debug('self.observed: Calling unschedule_all()') @@ -546,7 +547,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # Cant use update() without the entire thing, do stuff manually here self.state['Captain'] = None self.state['Credits'] = entry['Credits'] - self.state['FID'] = entry.get('FID') # From 3.3 + self.state['FID'] = entry.get('FID', '') # From 3.3 self.state['Horizons'] = entry['Horizons'] # From 3.0 self.state['Odyssey'] = entry.get('Odyssey', False) # From 4.0 Odyssey self.state['Loan'] = entry['Loan'] @@ -555,10 +556,10 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.state['Reputation'] = {} self.state['Statistics'] = {} self.state['Role'] = None - self.state['Taxi'] = None - self.state['Dropship'] = None - self.state['Body'] = None - self.state['BodyType'] = None + self.state['Taxi'] = False + self.state['Dropship'] = False + self.state['Body'] = '' + self.state['BodyType'] = '' if entry.get('Ship') is not None and self._RE_SHIP_ONFOOT.search(entry['Ship']): self.state['OnFoot'] = True @@ -580,25 +581,25 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C elif event_type == 'shipyardbuy': self.state['ShipID'] = None - self.state['ShipIdent'] = None + self.state['ShipIdent'] = '' self.state['ShipName'] = None self.state['ShipType'] = self.canonicalise(entry['ShipType']) - self.state['HullValue'] = None - self.state['ModulesValue'] = None - self.state['Rebuy'] = None - self.state['Modules'] = None + self.state['HullValue'] = 0 + self.state['ModulesValue'] = 0 + self.state['Rebuy'] = 0 + self.state['Modules'] = {} self.state['Credits'] -= entry.get('ShipPrice', 0) elif event_type == 'shipyardswap': self.state['ShipID'] = entry['ShipID'] - self.state['ShipIdent'] = None + self.state['ShipIdent'] = '' self.state['ShipName'] = None self.state['ShipType'] = self.canonicalise(entry['ShipType']) - self.state['HullValue'] = None - self.state['ModulesValue'] = None - self.state['Rebuy'] = None - self.state['Modules'] = None + self.state['HullValue'] = 0 + self.state['ModulesValue'] = 0 + self.state['Rebuy'] = 0 + self.state['Modules'] = {} elif ( event_type == 'loadout' and @@ -616,9 +617,9 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.state['ShipName'] = entry['ShipName'] self.state['ShipType'] = self.canonicalise(entry['Ship']) - self.state['HullValue'] = entry.get('HullValue') # not present on exiting Outfitting - self.state['ModulesValue'] = entry.get('ModulesValue') # not present on exiting Outfitting - self.state['Rebuy'] = entry.get('Rebuy') + self.state['HullValue'] = entry.get('HullValue', 0) # not present on exiting Outfitting + self.state['ModulesValue'] = entry.get('ModulesValue', 0) # not present on exiting Outfitting + self.state['Rebuy'] = entry.get('Rebuy', 0) # Remove spurious differences between initial Loadout event and subsequent self.state['Modules'] = {} for module in entry['Modules']: @@ -759,16 +760,16 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # • OnFoot: bool if event_type in ('location', 'carrierjump'): self.planet = entry.get('Body') if entry.get('BodyType') == 'Planet' else None - self.state['Body'] = entry.get('Body') - self.state['BodyType'] = entry.get('BodyType') + self.state['Body'] = entry.get('Body', '') + self.state['BodyType'] = entry.get('BodyType', '') # if event_type == 'location': # logger.trace('"Location" event') elif event_type == 'fsdjump': self.planet = None - self.state['Body'] = None - self.state['BodyType'] = None + self.state['Body'] = '' + self.state['BodyType'] = '' if 'StarPos' in entry: self.coordinates = tuple(entry['StarPos']) # type: ignore @@ -790,9 +791,9 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.stationtype = entry.get('StationType') # May be None self.stationservices = entry.get('StationServices') # None in Odyssey for on-foot 'Location' - self.state['Taxi'] = entry.get('Taxi', None) + self.state['Taxi'] = entry.get('Taxi', False) if not self.state['Taxi']: - self.state['Dropship'] = None + self.state['Dropship'] = False elif event_type == 'approachbody': self.planet = entry['Body'] @@ -801,8 +802,8 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C elif event_type in ('leavebody', 'supercruiseentry'): self.planet = None - self.state['Body'] = None - self.state['BodyType'] = None + self.state['Body'] = '' + self.state['BodyType'] = '' elif event_type in ('rank', 'promotion'): payload = dict(entry) @@ -823,7 +824,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C payload.pop('event') payload.pop('timestamp') # NB: We need the original casing for these keys - self.state[entry['event']] = payload + self.state[entry['event']] = payload # type: ignore # Non-literal, but the options are ensured above elif event_type == 'engineerprogress': # Sanity check - at least once the 'Engineer' (name) was missing from this in early @@ -1005,6 +1006,9 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C for c in entry[changes]: category = self.category(c['Type']) name = self.canonicalise(c['Name']) + if TYPE_CHECKING: + # Cheaty "its fine I promise" for TypedDict + category = cast(Literal['Component', 'Data', 'Consumable', 'Item'], category) if changes == 'Removed': self.state['BackPack'][category][name] -= c['Count'] @@ -1016,9 +1020,9 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # As of Odyssey Alpha Phase 1 Hotfix 2 keeping track of BackPack # materials is impossible when used/picked up anyway. for c in self.state['BackPack']: - for m in self.state['BackPack'][c]: - if self.state['BackPack'][c][m] < 0: - self.state['BackPack'][c][m] = 0 + for m in self.state['BackPack'][c]: # type: ignore # c and m are dynamic but "safe" + if self.state['BackPack'][c][m] < 0: # type: ignore # c and m are dynamic but "safe" + self.state['BackPack'][c][m] = 0 # type: ignore # c and m are dynamic but "safe" elif event_type == 'buymicroresources': # From 4.0.0.400 we get an empty (see file) `ShipLocker` event, @@ -1092,7 +1096,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C if self.state['SuitLoadouts']: loadout_id = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) try: - self.state['SuitLoadouts'].pop(f'{loadout_id}') + self.state['SuitLoadouts'].pop(loadout_id) except KeyError: # This should no longer happen, as we're now handling CreateSuitLoadout properly @@ -1293,24 +1297,25 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # Added in ED 3.7 - multi-hop route details in NavRoute.json with open(join(self.currentdir, 'NavRoute.json'), 'rb') as rf: # type: ignore try: - entry = json.load(rf) + nv_entry: NavRouteDict = json.load(rf) except json.JSONDecodeError: logger.exception('Failed decoding NavRoute.json', exc_info=True) else: - self.state['NavRoute'] = entry + self.state['NavRoute'] = nv_entry + entry = cast(dict, nv_entry) elif event_type == 'moduleinfo': with open(join(self.currentdir, 'ModulesInfo.json'), 'rb') as mf: # type: ignore try: - entry = json.load(mf) + m_entry = json.load(mf) except json.JSONDecodeError: logger.exception('Failed decoding ModulesInfo.json', exc_info=True) else: - self.state['ModuleInfo'] = entry + self.state['ModuleInfo'] = m_entry elif event_type in ('collectcargo', 'marketbuy', 'buydrones', 'miningrefined'): commodity = self.canonicalise(entry['Type']) @@ -1345,6 +1350,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C elif event_type == 'materials': for category in ('Raw', 'Manufactured', 'Encoded'): + category = cast(Literal['Raw', 'Manufactured', 'Encoded'], category) self.state[category] = defaultdict(int) self.state[category].update({ self.canonicalise(x['Name']): x['Count'] for x in entry.get(category, []) @@ -1352,17 +1358,18 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C elif event_type == 'materialcollected': material = self.canonicalise(entry['Name']) - self.state[entry['Category']][material] += entry['Count'] + self.state[entry['Category']][material] += entry['Count'] # type: ignore elif event_type in ('materialdiscarded', 'scientificresearch'): material = self.canonicalise(entry['Name']) - state_category = self.state[entry['Category']] + state_category = self.state[entry['Category']] # type: ignore state_category[material] -= entry['Count'] if state_category[material] <= 0: state_category.pop(material) elif event_type == 'synthesis': for category in ('Raw', 'Manufactured', 'Encoded'): + category = cast(Literal['Raw', 'Manufactured', 'Encoded'], category) for x in entry['Materials']: material = self.canonicalise(x['Name']) if material in self.state[category]: @@ -1372,7 +1379,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C elif event_type == 'materialtrade': category = self.category(entry['Paid']['Category']) - state_category = self.state[category] + state_category = self.state[category] # type: ignore paid = entry['Paid'] received = entry['Received'] @@ -1388,6 +1395,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C ): for category in ('Raw', 'Manufactured', 'Encoded'): + category = cast(Literal['Raw', 'Manufactured', 'Encoded'], category) for x in entry.get('Ingredients', []): material = self.canonicalise(x['Name']) if material in self.state[category]: @@ -1426,7 +1434,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C if 'Category' in reward: # Category not present in E:D 3.0 category = self.category(reward['Category']) material = self.canonicalise(reward['Name']) - self.state[category][material] += reward.get('Count', 1) + self.state[category][material] += reward.get('Count', 1) # type: ignore elif event_type == 'engineercontribution': commodity = self.canonicalise(entry.get('Commodity')) @@ -1438,6 +1446,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C material = self.canonicalise(entry.get('Material')) if material: for category in ('Raw', 'Manufactured', 'Encoded'): + category = cast(Literal['Raw', 'Manufactured', 'Encoded'], category) if material in self.state[category]: self.state[category][material] -= entry['Quantity'] if self.state[category][material] <= 0: @@ -1446,6 +1455,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C elif event_type == 'technologybroker': for thing in entry.get('Ingredients', []): # 3.01 for category in ('Cargo', 'Raw', 'Manufactured', 'Encoded'): + category = cast(Literal['Raw', 'Manufactured', 'Encoded'], category) item = self.canonicalise(thing['Name']) if item in self.state[category]: self.state[category][item] -= thing['Count'] @@ -1461,6 +1471,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C for thing in entry.get('Materials', []): # 3.02 material = self.canonicalise(thing['Name']) category = thing['Category'] + category = cast(Literal['Raw', 'Manufactured', 'Encoded'], category) self.state[category][material] -= thing['Count'] if self.state[category][material] <= 0: self.state[category].pop(material) @@ -1478,8 +1489,8 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.systemaddress = None self.state['OnFoot'] = False - self.state['Body'] = None - self.state['BodyType'] = None + self.state['Body'] = '' + self.state['BodyType'] = '' elif event_type == 'changecrewrole': self.state['Role'] = entry['Role'] @@ -1496,8 +1507,8 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.coordinates = None self.systemaddress = None - self.state['Body'] = None - self.state['BodyType'] = None + self.state['Body'] = '' + self.state['BodyType'] = '' # TODO: on_foot: Will we get an event after this to know ? elif event_type == 'friends': @@ -1693,19 +1704,20 @@ def suitloadout_store_from_event(self, entry) -> Tuple[int, int]: suitloadout_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) # Make the new loadout, in the CAPI format - new_loadout = { + new_loadout: SuitLoadoutDict = { 'loadoutSlotId': suitloadout_slotid, 'suit': suit, 'name': entry['LoadoutName'], 'slots': self.suit_loadout_slots_array_to_dict(entry['Modules']), } + # Assign this loadout into our state - self.state['SuitLoadouts'][f"{suitloadout_slotid}"] = new_loadout + self.state['SuitLoadouts'][suitloadout_slotid] = new_loadout # Now add in the extra fields for new_suit to be a 'full' Suit structure suit['id'] = suit.get('id') # Not available in 4.0.0.100 journal event # Ensure the suit is in self.state['Suits'] - self.state['Suits'][f"{suitid}"] = suit + self.state['Suits'][suitid] = suit return suitid, suitloadout_slotid @@ -1724,9 +1736,9 @@ def suit_and_loadout_setcurrent(self, suitid: int, suitloadout_slotid: int) -> b str_suitloadoutid = f"{suitloadout_slotid}" if (self.state['Suits'].get(str_suitid, False) - and self.state['SuitLoadouts'].get(str_suitloadoutid, False)): + and self.state['SuitLoadouts'].get(str_suitloadoutid, False)): # type: ignore # pyright issues... self.state['SuitCurrent'] = self.state['Suits'][str_suitid] - self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][str_suitloadoutid] + self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][suitloadout_slotid] return True logger.error(f"Tried to set a suit and suitloadout where we didn't know about both: {suitid=}, " From 649d08623c9b0e0bcd729ac37b9d06db4a9a4786 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 3 Aug 2021 14:09:53 +0200 Subject: [PATCH 04/27] resolved additional suit related errors --- monitor.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/monitor.py b/monitor.py index 57447dbe9..e4be7bd45 100644 --- a/monitor.py +++ b/monitor.py @@ -1682,7 +1682,7 @@ def suitloadout_store_from_event(self, entry) -> Tuple[int, int]: # Check if this looks like a suit we already have stored, so as # to avoid 'bad' Journal localised names. - suit = self.state['Suits'].get(f"{suitid}", None) + suit = self.state['Suits'].get(suitid, None) if suit is None: # Initial suit containing just the data that is then embedded in # the loadout @@ -1732,17 +1732,14 @@ def suit_and_loadout_setcurrent(self, suitid: int, suitloadout_slotid: int) -> b :param suitloadout_slotid: Numeric ID of the slot for the suit loadout. :return: True if we could do this, False if not. """ - str_suitid = f"{suitid}" - str_suitloadoutid = f"{suitloadout_slotid}" - - if (self.state['Suits'].get(str_suitid, False) - and self.state['SuitLoadouts'].get(str_suitloadoutid, False)): # type: ignore # pyright issues... - self.state['SuitCurrent'] = self.state['Suits'][str_suitid] + if suitid in self.state['Suits'] and suitloadout_slotid in self.state['SuitLoadouts']: + self.state['SuitCurrent'] = self.state['Suits'][suitid] self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][suitloadout_slotid] return True logger.error(f"Tried to set a suit and suitloadout where we didn't know about both: {suitid=}, " - f"{str_suitloadoutid=}") + f"{suitloadout_slotid=}") + return False # TODO: *This* will need refactoring and a proper validation infrastructure From a540e2be7051327ab0f111e39bef5c21c192fbfb Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 3 Aug 2021 14:13:29 +0200 Subject: [PATCH 05/27] Set SuitCurrent to an empty dict by default --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index e4be7bd45..55bd152bd 100644 --- a/monitor.py +++ b/monitor.py @@ -159,7 +159,7 @@ def __init_state(self) -> None: }, 'BackpackJSON': {}, # Raw JSON from `Backpack.json` file, if available 'ShipLockerJSON': {}, # Raw JSON from the `ShipLocker.json` file, if available - 'SuitCurrent': None, + 'SuitCurrent': {}, 'Suits': {}, 'SuitLoadoutCurrent': None, 'SuitLoadouts': {}, From 68354d93363ecb67c2c41dd2d216fa40c7b4ad6a Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 3 Aug 2021 14:13:43 +0200 Subject: [PATCH 06/27] removed comments --- monitor_state_dict.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monitor_state_dict.py b/monitor_state_dict.py index f9b7fbb80..838ae2339 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -73,9 +73,9 @@ class MonitorStateDict(TypedDict): BackpackJSON: MutableMapping[str, Any] # Direct from Game ShipLockerJSON: MutableMapping[str, Any] # Direct from Game - SuitCurrent: Optional[int] # TODO: int? - Suits: Dict[Any, Any] # TODO: With additional class - SuitLoadoutCurrent: Optional[SuitLoadoutDict] # TODO: int? + SuitCurrent: Dict[str, Any] + Suits: Dict[int, Any] # TODO: With additional class + SuitLoadoutCurrent: Optional[SuitLoadoutDict] SuitLoadouts: Dict[int, SuitLoadoutDict] # TODO: class? From a974b59bc937eeb943ba0845fb024a3ff375039d Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 4 Aug 2021 16:50:51 +0200 Subject: [PATCH 07/27] re-added import missed in rebase --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 55bd152bd..7baf7c996 100644 --- a/monitor.py +++ b/monitor.py @@ -12,7 +12,7 @@ from os.path import basename, expanduser, isdir, join from sys import platform from time import gmtime, localtime, sleep, strftime, strptime, time -from typing import TYPE_CHECKING, Any, BinaryIO, Dict, List, MutableMapping, Optional +from typing import TYPE_CHECKING, Any, BinaryIO, List, MutableMapping, Optional, cast from typing import OrderedDict as OrderedDictT from typing import Tuple From b4a42c0f7f08cd6f3beeb8ac9f1d6fff16fde99c Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 4 Aug 2021 17:39:06 +0200 Subject: [PATCH 08/27] Added rank/reputation/engineers --- monitor_state_dict.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monitor_state_dict.py b/monitor_state_dict.py index 838ae2339..fa0cf9243 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -30,9 +30,12 @@ class MonitorStateDict(TypedDict): Friends: Set[str] # Online Friends Credits: int Loan: int - Engineers: Dict[Any, Any] # TODO - Rank: Dict[Any, Any] # TODO - Reputation: Dict[Any, Any] # TODO + + # (A_D) One day I will change this to be a NamedTuple. But for now it will suffice to state that: + # (Rank, RankProgress) | Literal['Known', 'Invited' (or any other of the possible states that ISNT a rank number)] + Engineers: Dict[str, Union[str, Tuple[int, int]]] + Rank: Dict[str, Tuple[int, int]] # (RankMajor, RankProgress) + Reputation: Dict[str, float] # Superpower -> level Statistics: Dict[Any, Any] # This is very freeform. # Engineering Materials From ad8b1f7519b4836e7d3b2ed28cffbdf37cab0b62 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 4 Aug 2021 17:41:10 +0200 Subject: [PATCH 09/27] fixed spell checker complaints --- monitor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monitor.py b/monitor.py index 7baf7c996..54ced6a2b 100644 --- a/monitor.py +++ b/monitor.py @@ -16,6 +16,9 @@ from typing import OrderedDict as OrderedDictT from typing import Tuple +# spell-checker: words loadoutid slotid fdev fid relog onfoot fsdjump cheaty suitid fauto sauto intimidator navroute +# spell-checker: words quitacrew joinacrew sellshiponrebuy npccrewpaidwage + if TYPE_CHECKING: import tkinter @@ -682,7 +685,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # This event is logged when a player (on foot) gets into a ship or SRV # Parameters: # • SRV: true if getting into SRV, false if getting into a ship - # • Taxi: true when boarding a taxi transposrt ship + # • Taxi: true when boarding a taxi transport ship # • Multicrew: true when boarding another player’s vessel # • ID: player’s ship ID (if players own vessel) # • StarSystem @@ -710,7 +713,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # # Parameters: # • SRV: true if getting out of SRV, false if getting out of a ship - # • Taxi: true when getting out of a taxi transposrt ship + # • Taxi: true when getting out of a taxi transport ship # • Multicrew: true when getting out of another player’s vessel # • ID: player’s ship ID (if players own vessel) # • StarSystem From 7a6943d8029ad4a0b82413ef72720d8a7522a372 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 4 Aug 2021 17:41:40 +0200 Subject: [PATCH 10/27] added modules --- monitor.py | 22 ++++++++++------- monitor_state_dict.py | 56 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/monitor.py b/monitor.py index 54ced6a2b..20e3a08e0 100644 --- a/monitor.py +++ b/monitor.py @@ -1,7 +1,6 @@ """Monitor for new Journal files and contents of latest.""" import json -from monitor_state_dict import MonitorStateDict, NavRouteDict, SuitLoadoutDict import pathlib import queue import re @@ -12,9 +11,11 @@ from os.path import basename, expanduser, isdir, join from sys import platform from time import gmtime, localtime, sleep, strftime, strptime, time -from typing import TYPE_CHECKING, Any, BinaryIO, List, MutableMapping, Optional, cast +from typing import TYPE_CHECKING, Any, BinaryIO, Dict, List, Literal, MutableMapping, Optional from typing import OrderedDict as OrderedDictT -from typing import Tuple +from typing import Tuple, Union, cast + +from monitor_state_dict import ModuleDict, ModuleEngineering, MonitorStateDict, NavRouteDict, SuitLoadoutDict # spell-checker: words loadoutid slotid fdev fid relog onfoot fsdjump cheaty suitid fauto sauto intimidator navroute # spell-checker: words quitacrew joinacrew sellshiponrebuy npccrewpaidwage @@ -637,8 +638,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.state['Modules'][module['Slot']] = module elif event_type == 'modulebuy': - - self.state['Modules'][entry['Slot']] = { + new_module: ModuleDict = { 'Slot': entry['Slot'], 'Item': self.canonicalise(entry['BuyItem']), 'On': True, @@ -646,6 +646,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C 'Health': 1.0, 'Value': entry['BuyPrice'], } + self.state['Modules'][entry['Slot']] = new_module self.state['Credits'] -= entry.get('BuyPrice', 0) @@ -836,10 +837,11 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C if self.event_valid_engineerprogress(entry): engineers = self.state['Engineers'] if 'Engineers' in entry: # Startup summary - self.state['Engineers'] = { + to_set: Dict[str, Union[str, Tuple[int, int]]] = { e['Engineer']: ((e['Rank'], e.get('RankProgress', 0)) if 'Rank' in e else e['Progress']) for e in entry['Engineers'] } + self.state['Engineers'] = to_set else: # Promotion engineer = entry['Engineer'] @@ -1408,7 +1410,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C module = self.state['Modules'][entry['Slot']] assert(module['Item'] == self.canonicalise(entry['Module'])) - module['Engineering'] = { + to_set_me: ModuleEngineering = { 'Engineer': entry['Engineer'], 'EngineerID': entry['EngineerID'], 'BlueprintName': entry['BlueprintName'], @@ -1418,6 +1420,8 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C 'Modifiers': entry['Modifiers'], } + module['Engineering'] = to_set_me + if 'ExperimentalEffect' in entry: module['Engineering']['ExperimentalEffect'] = entry['ExperimentalEffect'] module['Engineering']['ExperimentalEffect_Localised'] = entry['ExperimentalEffect_Localised'] @@ -1580,8 +1584,8 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.state['Credits'] -= entry.get('Price', 0) elif event_type == 'carrierbanktransfer': - if (newbal := entry.get('PlayerBalance')): - self.state['Credits'] = newbal + if (new_bal := entry.get('PlayerBalance')): + self.state['Credits'] = new_bal elif event_type == 'carrierdecommission': # v30 doc says nothing about citing the refund amount diff --git a/monitor_state_dict.py b/monitor_state_dict.py index fa0cf9243..d634bf4d8 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -6,7 +6,7 @@ from __future__ import annotations from typing import ( - TYPE_CHECKING, Any, DefaultDict, Dict, List, Literal, MutableMapping, Optional, Set, Tuple, TypedDict + TYPE_CHECKING, Any, DefaultDict, Dict, List, Literal, MutableMapping, Optional, Set, Tuple, TypedDict, Union ) @@ -52,7 +52,7 @@ class MonitorStateDict(TypedDict): HullValue: int ModulesValue: int Rebuy: int - Modules: Dict[Any, Any] # TODO + Modules: Dict[str, ModuleDict] ModuleInfo: MutableMapping[Any, Any] # From the game, freeform # Cargo (yes technically its on the cmdr not the ship but this makes more sense.) @@ -108,16 +108,58 @@ class NavRouteEntry(TypedDict): class SuitLoadoutDict(TypedDict): + """Single suit loadout.""" loadoutSlotId: int suit: Any name: str slots: Dict[Any, Any] -if TYPE_CHECKING: - test: MonitorStateDict = {} # type: ignore +class _ModuleEngineeringModifiers(TypedDict): + Label: str + Value: float + OriginalValue: float + LessIsGood: int - test['GameLanguage'] - test['Role'] = 'FighterCon' - ... +class _ModuleExperimentalEffects(TypedDict, total=False): + """Experimental effects an engineered module *MAY* have.""" + + ExperimentalEffect: str + ExperimentalEffect_Localised: str + + +class ModuleEngineering(_ModuleExperimentalEffects): + """Engineering modifiers for a module.""" + + Engineer: str + EngineerID: int + BlueprintName: str + BlueprintID: int + Level: int + Quality: int + Modifiers: List[_ModuleEngineeringModifiers] + + +class _ModulesOptionals(TypedDict, total=False): + """Optional fields a module may have.""" + + On: bool + Priority: int + Health: float + Value: int + Engineering: ModuleEngineering + + +class _ModulesWeaponsOptionals(TypedDict, total=False): + """Optional fields modules *may* have if they are weapons""" + + AmmoInClip: int + AmmoInHopper: int + + +class ModuleDict(_ModulesOptionals, _ModulesWeaponsOptionals): + """Dictionary containing module information""" + + Item: str + Slot: str From 01cc8a2c664f425b1de054ab066d503df2afe172 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 4 Aug 2021 20:56:47 +0200 Subject: [PATCH 11/27] removed Optional from ShipID, ShipName, and ShipType --- monitor.py | 16 ++++++++-------- monitor_state_dict.py | 16 +++++++--------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/monitor.py b/monitor.py index 20e3a08e0..7c05fbdd2 100644 --- a/monitor.py +++ b/monitor.py @@ -140,10 +140,10 @@ def __init_state(self) -> None: 'Statistics': {}, 'Role': None, # Crew role - None, Idle, FireCon, FighterCon 'Friends': set(), # Online friends - 'ShipID': None, + 'ShipID': -1, 'ShipIdent': '', - 'ShipName': None, - 'ShipType': None, + 'ShipName': '', + 'ShipType': '', 'HullValue': 0, 'ModulesValue': 0, 'Rebuy': 0, @@ -171,7 +171,7 @@ def __init_state(self) -> None: 'Dropship': False, # Best effort as to whether or not the above taxi is a dropship. 'Body': '', 'BodyType': '', - 'ModuleInfo': {}, + 'ModuleInfo': {}, } def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001 @@ -580,13 +580,13 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C if 'UserShipId' in entry: # Only present when changing the ship's ident self.state['ShipIdent'] = entry['UserShipId'] - self.state['ShipName'] = entry.get('UserShipName') + self.state['ShipName'] = entry.get('UserShipName', '') self.state['ShipType'] = self.canonicalise(entry['Ship']) elif event_type == 'shipyardbuy': - self.state['ShipID'] = None + self.state['ShipID'] = -1 self.state['ShipIdent'] = '' - self.state['ShipName'] = None + self.state['ShipName'] = '' self.state['ShipType'] = self.canonicalise(entry['ShipType']) self.state['HullValue'] = 0 self.state['ModulesValue'] = 0 @@ -598,7 +598,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C elif event_type == 'shipyardswap': self.state['ShipID'] = entry['ShipID'] self.state['ShipIdent'] = '' - self.state['ShipName'] = None + self.state['ShipName'] = '' self.state['ShipType'] = self.canonicalise(entry['ShipType']) self.state['HullValue'] = 0 self.state['ModulesValue'] = 0 diff --git a/monitor_state_dict.py b/monitor_state_dict.py index d634bf4d8..056502301 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -5,9 +5,7 @@ """ from __future__ import annotations -from typing import ( - TYPE_CHECKING, Any, DefaultDict, Dict, List, Literal, MutableMapping, Optional, Set, Tuple, TypedDict, Union -) +from typing import Any, DefaultDict, Dict, List, Literal, MutableMapping, Optional, Set, Tuple, TypedDict, Union class MonitorStateDict(TypedDict): @@ -44,10 +42,10 @@ class MonitorStateDict(TypedDict): Manufactured: DefaultDict[str, int] # Ship - ShipID: Optional[str] + ShipID: int ShipIdent: str - ShipName: Optional[str] - ShipType: Optional[str] + ShipName: str + ShipType: str HullValue: int ModulesValue: int @@ -79,7 +77,7 @@ class MonitorStateDict(TypedDict): SuitCurrent: Dict[str, Any] Suits: Dict[int, Any] # TODO: With additional class SuitLoadoutCurrent: Optional[SuitLoadoutDict] - SuitLoadouts: Dict[int, SuitLoadoutDict] # TODO: class? + SuitLoadouts: Dict[int, SuitLoadoutDict] class OdysseyBackpack(TypedDict): @@ -152,14 +150,14 @@ class _ModulesOptionals(TypedDict, total=False): class _ModulesWeaponsOptionals(TypedDict, total=False): - """Optional fields modules *may* have if they are weapons""" + """Optional fields modules *may* have if they are weapons.""" AmmoInClip: int AmmoInHopper: int class ModuleDict(_ModulesOptionals, _ModulesWeaponsOptionals): - """Dictionary containing module information""" + """Dictionary containing module information.""" Item: str Slot: str From 34325a6a854f6c44f2de15f9c8c3fb6ecc30f6ae Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 4 Aug 2021 21:22:48 +0200 Subject: [PATCH 12/27] workaround for autopep8 issues --- monitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monitor.py b/monitor.py index 7c05fbdd2..7ab847936 100644 --- a/monitor.py +++ b/monitor.py @@ -126,7 +126,7 @@ def __init_state(self) -> None: 'GameBuild': '', # From `Fileheader 'Captain': None, # On a crew 'Cargo': defaultdict(int), - 'Credits': -1, + 'Credits': 0-1, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 'FID': '', # Frontier Cmdr ID 'Horizons': False, # Does this user have Horizons? 'Odyssey': False, # Have we detected we're running under Odyssey? @@ -140,7 +140,7 @@ def __init_state(self) -> None: 'Statistics': {}, 'Role': None, # Crew role - None, Idle, FireCon, FighterCon 'Friends': set(), # Online friends - 'ShipID': -1, + 'ShipID': 0-1, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 'ShipIdent': '', 'ShipName': '', 'ShipType': '', From a8b06c7357bcca8a53451dd0629cec7bbe089555 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 13:07:43 +0200 Subject: [PATCH 13/27] added suit and suit loadouts --- monitor.py | 28 +++++++++++------ monitor_state_dict.py | 73 +++++++++++++++++++++++++++++-------------- 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/monitor.py b/monitor.py index 7ab847936..255970bdc 100644 --- a/monitor.py +++ b/monitor.py @@ -15,7 +15,9 @@ from typing import OrderedDict as OrderedDictT from typing import Tuple, Union, cast -from monitor_state_dict import ModuleDict, ModuleEngineering, MonitorStateDict, NavRouteDict, SuitLoadoutDict +from monitor_state_dict import ( + ModuleDict, ModuleEngineering, MonitorStateDict, NavRouteDict, OdysseyWeapon, SuitDict, SuitLoadoutDict +) # spell-checker: words loadoutid slotid fdev fid relog onfoot fsdjump cheaty suitid fauto sauto intimidator navroute # spell-checker: words quitacrew joinacrew sellshiponrebuy npccrewpaidwage @@ -163,7 +165,7 @@ def __init_state(self) -> None: }, 'BackpackJSON': {}, # Raw JSON from `Backpack.json` file, if available 'ShipLockerJSON': {}, # Raw JSON from the `ShipLocker.json` file, if available - 'SuitCurrent': {}, + 'SuitCurrent': None, 'Suits': {}, 'SuitLoadoutCurrent': None, 'SuitLoadouts': {}, @@ -389,7 +391,8 @@ def worker(self) -> None: # noqa: C901, CCR001 # Watchdog thread -- there is a way to get this by using self.observer.emitters and checking for an attribute: # watch, but that may have unforseen differences in behaviour. - emitter = self.observed and self.observer._emitter_for_watch[self.observed] # Note: Uses undocumented attribute + # Note: Uses undocumented attribute + emitter = self.observed and self.observer._emitter_for_watch[self.observed] # type: ignore logger.debug('Entering loop...') while True: @@ -1131,7 +1134,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # { "timestamp":"2021-04-29T09:03:37Z", "event":"BuySuit", "Name":"UtilitySuit_Class1", # "Name_Localised":"Maverick Suit", "Price":150000, "SuitID":1698364934364699 } loc_name = entry.get('Name_Localised', entry['Name']) - self.state['Suits'][entry['SuitID']] = { + to_set_suit: SuitDict = { 'name': entry['Name'], 'locName': loc_name, 'edmcName': self.suit_sane_name(loc_name), @@ -1139,6 +1142,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C 'suitId': entry['SuitID'], 'mods': entry['SuitMods'], # Suits can (rarely) be bought with modules installed } + self.state['Suits'][entry['SuitID']] = to_set_suit # update credits if price := entry.get('Price') is None: @@ -1196,16 +1200,18 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C if self.state['SuitLoadouts']: loadout_id = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) try: - self.state['SuitLoadouts'][loadout_id]['slots'][entry['SlotName']] = { + w_to_set: OdysseyWeapon = { 'name': entry['ModuleName'], 'locName': entry.get('ModuleName_Localised', entry['ModuleName']), 'id': None, 'weaponrackId': entry['SuitModuleID'], 'locDescription': '', 'class': entry['Class'], - 'mods': entry['WeaponMods'] + 'mods': entry['WeaponMods'], } + self.state['SuitLoadouts'][loadout_id]['slots'][entry['SlotName']] = w_to_set + except KeyError: logger.error(f"LoadoutEquipModule: {entry}") @@ -1689,7 +1695,7 @@ def suitloadout_store_from_event(self, entry) -> Tuple[int, int]: # Check if this looks like a suit we already have stored, so as # to avoid 'bad' Journal localised names. - suit = self.state['Suits'].get(suitid, None) + suit: Optional[SuitDict] = self.state['Suits'].get(suitid, None) if suit is None: # Initial suit containing just the data that is then embedded in # the loadout @@ -1699,8 +1705,12 @@ def suitloadout_store_from_event(self, entry) -> Tuple[int, int]: suitname = entry.get('SuitName_Localised', entry['SuitName']) edmc_suitname = self.suit_sane_name(suitname) suit = { - 'edmcName': edmc_suitname, - 'locName': suitname, + 'edmcName': edmc_suitname, + 'locName': suitname, + 'suitId': 0-1, + 'id': None, + 'name': entry['SuitName'], + 'mods': entry['SuitMods'] } # Overwrite with latest data, just in case, as this can be from CAPI which may or may not have had diff --git a/monitor_state_dict.py b/monitor_state_dict.py index 056502301..bb3a22c73 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -5,36 +5,36 @@ """ from __future__ import annotations -from typing import Any, DefaultDict, Dict, List, Literal, MutableMapping, Optional, Set, Tuple, TypedDict, Union +from typing import Any, DefaultDict, Dict, List, MutableMapping, Optional, Set, Tuple, TypedDict, Union class MonitorStateDict(TypedDict): """Top level state dictionary for monitor.py.""" # Game related - GameLanguage: str # From `Fileheader` - GameVersion: str # From `Fileheader` - GameBuild: str # From `Fileheader` - Horizons: bool # Does the player have Horizons? - Odyssey: bool # Have we detected Odyssey? + GameLanguage: str # From `Fileheader` + GameVersion: str # From `Fileheader` + GameBuild: str # From `Fileheader` + Horizons: bool # Does the player have Horizons? + Odyssey: bool # Have we detected Odyssey? # Multi-crew - Captain: Optional[str] # If on a crew, the captian's name - Role: Optional[Literal['Idle', 'FireCon', 'FighterCon']] # Role in crew + Captain: Optional[str] # If on a crew, the captian's name + Role: Optional[str] # Role in crew # Cmdr state - FID: str # Frontier CMDR ID - Friends: Set[str] # Online Friends + FID: str # Frontier CMDR ID + Friends: Set[str] # Online Friends Credits: int Loan: int # (A_D) One day I will change this to be a NamedTuple. But for now it will suffice to state that: # (Rank, RankProgress) | Literal['Known', 'Invited' (or any other of the possible states that ISNT a rank number)] Engineers: Dict[str, Union[str, Tuple[int, int]]] - Rank: Dict[str, Tuple[int, int]] # (RankMajor, RankProgress) - Reputation: Dict[str, float] # Superpower -> level - Statistics: Dict[Any, Any] # This is very freeform. + Rank: Dict[str, Tuple[int, int]] # (RankMajor, RankProgress) + Reputation: Dict[str, float] # Superpower -> level + Statistics: Dict[Any, Any] # This is very freeform. # Engineering Materials Raw: DefaultDict[str, int] @@ -51,14 +51,14 @@ class MonitorStateDict(TypedDict): ModulesValue: int Rebuy: int Modules: Dict[str, ModuleDict] - ModuleInfo: MutableMapping[Any, Any] # From the game, freeform + ModuleInfo: MutableMapping[Any, Any] # From the game, freeform # Cargo (yes technically its on the cmdr not the ship but this makes more sense.) - CargoJSON: MutableMapping[str, Any] # Raw data from the last cargo.json read + CargoJSON: MutableMapping[str, Any] # Raw data from the last cargo.json read Cargo: DefaultDict[str, int] # Navigation - NavRoute: NavRouteDict # Last route plotted + NavRoute: NavRouteDict # Last route plotted Body: str BodyType: str Taxi: bool @@ -71,11 +71,11 @@ class MonitorStateDict(TypedDict): Consumable: DefaultDict[str, int] Data: DefaultDict[str, int] BackPack: OdysseyBackpack - BackpackJSON: MutableMapping[str, Any] # Direct from Game - ShipLockerJSON: MutableMapping[str, Any] # Direct from Game + BackpackJSON: MutableMapping[str, Any] # Direct from Game + ShipLockerJSON: MutableMapping[str, Any] # Direct from Game - SuitCurrent: Dict[str, Any] - Suits: Dict[int, Any] # TODO: With additional class + SuitCurrent: Optional[SuitDict] + Suits: Dict[int, SuitDict] SuitLoadoutCurrent: Optional[SuitLoadoutDict] SuitLoadouts: Dict[int, SuitLoadoutDict] @@ -107,10 +107,37 @@ class NavRouteEntry(TypedDict): class SuitLoadoutDict(TypedDict): """Single suit loadout.""" - loadoutSlotId: int - suit: Any + + loadoutSlotId: int # noqa: N815 + suit: SuitDict name: str - slots: Dict[Any, Any] + slots: Dict[str, OdysseyWeapon] + + +class SuitDict(TypedDict): + """Dict representing a single suit.""" + + name: str + locName: str # noqa: N815 + edmcName: str # noqa: N815 + id: Any # ??? some sort of ID, not listed as to where or what + suitId: int # noqa: N815 + mods: List[str] + + +_OdysseyWeaponClassField = TypedDict('_OdysseyWeaponClassField', {'class': int}) + + +class OdysseyWeapon(_OdysseyWeaponClassField): + """Suit Weapon for an odyssey suit loadout""" + + name: str + locName: str # noqa: N815 + id: Any + weaponrackId: int # noqa: N815 + locDescription: str # noqa: N815 + # class: int Oh this'll be fun. -- See the definition of the TypedDict this inherits from + mods: List[str] class _ModuleEngineeringModifiers(TypedDict): From d79ded63c72994f6d48c73e844871cc7dcf5db48 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 13:51:20 +0200 Subject: [PATCH 14/27] update state usage --- EDMarketConnector.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8296028ac..9823aff2e 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -660,13 +660,13 @@ def update_suit_text(self) -> None: self.suit['text'] = '' return - if (suit := monitor.state.get('SuitCurrent')) is None: + if (suit := monitor.state['SuitCurrent']) is None: self.suit['text'] = f'<{_("Unknown")}>' # LANG: Unknown suit return suitname = suit['edmcName'] - if (suitloadout := monitor.state.get('SuitLoadoutCurrent')) is None: + if (suitloadout := monitor.state['SuitLoadoutCurrent']) is None: self.suit['text'] = '' return @@ -739,7 +739,7 @@ def set_labels(self): """Set main window labels, e.g. after language change.""" self.cmdr_label['text'] = _('Cmdr') + ':' # LANG: Label for commander name in main window # LANG: 'Ship' or multi-crew role label in main window, as applicable - self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':' # Main window + self.ship_label['text'] = (_('Role') if monitor.state['Captain'] else _('Ship')) + ':' # Main window self.suit_label['text'] = _('Suit') + ':' # LANG: Label for 'Suit' line in main UI self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI @@ -991,7 +991,7 @@ def getandsend(self, event=None, retrying: bool = False): # noqa: C901, CCR001 if monitor.state['Modules']: self.ship.configure(state=True) - if monitor.state.get('SuitCurrent') is not None: + if monitor.state['SuitCurrent'] is not None: if (loadout := data.get('loadout')) is not None: if (suit := loadout.get('suit')) is not None: if (suitname := suit.get('edmcName')) is not None: From c4db544495b1429400b29fcaf4e2c40f11659560 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 14:04:39 +0200 Subject: [PATCH 15/27] clear up assumptions and type monitor state usage --- companion.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/companion.py b/companion.py index 0efae6aa7..2cbeacfbe 100644 --- a/companion.py +++ b/companion.py @@ -11,6 +11,7 @@ import csv import hashlib import json +from monitor_state_dict import SuitDict, SuitLoadoutDict import numbers import os import random @@ -388,7 +389,7 @@ def authorize(self, payload: str) -> str: # noqa: CCR001 # All 'FID' seen in Journals so far have been 'F' # Frontier, Steam and Epic - if f'F{customer_id}' != monitor.state.get('FID'): + if f'F{customer_id}' != monitor.state['FID']: # LANG: Frontier auth customer_id doesn't match game session FID raise CredentialsError(_("Error: customer_id doesn't match!")) @@ -713,28 +714,37 @@ def suit_update(self, data: CAPIData) -> None: :param data: CAPI data to extra suit data from. """ + current_suit: Optional[SuitDict] if (current_suit := data.get('suit')) is None: # Probably no Odyssey on the account, so point attempting more. return monitor.state['SuitCurrent'] = current_suit # It's easier to always have this in the 'sparse array' dict form - suits = data.get('suits') + suits: Optional[Union[List[SuitDict], Dict[int, SuitDict]]] = data.get('suits') if isinstance(suits, list): monitor.state['Suits'] = dict(enumerate(suits)) - else: + elif isinstance(suits, dict): monitor.state['Suits'] = suits + else: + # If it was neither, it didn't exist, so ... dont muck with it, as something is either broken or + # we didn't see a bunch of info. + pass # We need to be setting our edmcName for all suits - loc_name = monitor.state['SuitCurrent'].get('locName', monitor.state['SuitCurrent']['name']) - monitor.state['SuitCurrent']['edmcName'] = monitor.suit_sane_name(loc_name) + if (current_suit := monitor.state['SuitCurrent']) is not None: + loc_name = current_suit['locName'] if current_suit['locName'] else current_suit['name'] + current_suit['edmcName'] = monitor.suit_sane_name(loc_name) + for s in monitor.state['Suits']: loc_name = monitor.state['Suits'][s].get('locName', monitor.state['Suits'][s]['name']) monitor.state['Suits'][s]['edmcName'] = monitor.suit_sane_name(loc_name) + suit_loadouts: Optional[Union[List[SuitLoadoutDict], Dict[int, SuitLoadoutDict]]] if (suit_loadouts := data.get('loadouts')) is None: logger.warning('CAPI data had "suit" but no (suit) "loadouts"') + return monitor.state['SuitLoadoutCurrent'] = data.get('loadout') # It's easier to always have this in the 'sparse array' dict form From 9070b676084ccd50f3b3e0096119fd100b7f9a51 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 14:17:56 +0200 Subject: [PATCH 16/27] Re-type plugins as needed for MonitorStateDict --- plugins/eddb.py | 3 ++- plugins/eddn.py | 3 ++- plugins/edsm.py | 3 ++- plugins/inara.py | 15 ++++++++------- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/plugins/eddb.py b/plugins/eddb.py index 80fd94315..1d4e1e9d4 100644 --- a/plugins/eddb.py +++ b/plugins/eddb.py @@ -33,6 +33,7 @@ import plug from companion import CAPIData from config import config +from monitor_state_dict import MonitorStateDict if TYPE_CHECKING: from tkinter import Tk @@ -93,7 +94,7 @@ def prefs_changed(cmdr, is_beta): pass -def journal_entry(cmdr, is_beta, system, station, entry, state): +def journal_entry(cmdr, is_beta, system, station, entry, state: MonitorStateDict): if (ks := killswitch.get_disabled('plugins.eddb.journal')).disabled: logger.warning(f'Journal processing for EDDB has been disabled: {ks.reason}') # LANG: Journal Processing disabled due to an active killswitch diff --git a/plugins/eddn.py b/plugins/eddn.py index 194a52deb..f279b3c84 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -24,6 +24,7 @@ from config import applongname, appversion_nobuild, config, debug_senders from EDMCLogging import get_main_logger from monitor import monitor +from monitor_state_dict import MonitorStateDict from myNotebook import Frame from prefs import prefsVersion from ttkHyperlinkLabel import HyperlinkLabel @@ -773,7 +774,7 @@ def journal_entry( # noqa: C901, CCR001 system: str, station: str, entry: MutableMapping[str, Any], - state: Mapping[str, Any] + state: MonitorStateDict ) -> Optional[str]: """ Process a new Journal entry. diff --git a/plugins/edsm.py b/plugins/edsm.py index 30340925a..3c15856ae 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -26,6 +26,7 @@ from config import applongname, appversion, config, debug_senders, trace_on from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT from EDMCLogging import get_main_logger +from monitor_state_dict import MonitorStateDict from ttkHyperlinkLabel import HyperlinkLabel if TYPE_CHECKING: @@ -393,7 +394,7 @@ def credentials(cmdr: str) -> Optional[Tuple[str, str]]: def journal_entry( # noqa: C901, CCR001 - cmdr: str, is_beta: bool, system: str, station: str, entry: MutableMapping[str, Any], state: Mapping[str, Any] + cmdr: str, is_beta: bool, system: str, station: str, entry: MutableMapping[str, Any], state: MonitorStateDict ) -> None: """Journal Entry hook.""" if (ks := killswitch.get_disabled('plugins.edsm.journal')).disabled: diff --git a/plugins/inara.py b/plugins/inara.py index ea46f9ce4..9bac0ec44 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1,6 +1,7 @@ """Inara Sync.""" import json +from monitor_state_dict import MonitorStateDict import threading import time import tkinter as tk @@ -327,7 +328,7 @@ def credentials(cmdr: Optional[str]) -> Optional[str]: def journal_entry( # noqa: C901, CCR001 - cmdr: str, is_beta: bool, system: str, station: str, entry: Dict[str, Any], state: Dict[str, Any] + cmdr: str, is_beta: bool, system: str, station: str, entry: Dict[str, Any], state: MonitorStateDict ) -> str: """ Journal entry hook. @@ -443,7 +444,7 @@ def journal_entry( # noqa: C901, CCR001 if state['Engineers']: # Not populated < 3.3 to_send_list: List[Mapping[str, Any]] = [] for k, v in state['Engineers'].items(): - e = {'engineerName': k} + e: Dict[str, Any] = {'engineerName': k} if isinstance(v, tuple): e['rankValue'] = v[0] @@ -726,9 +727,9 @@ def journal_entry( # noqa: C901, CCR001 new_add_event('setCommanderInventoryMaterials', entry['timestamp'], materials) this.materials = materials - except Exception as e: - logger.debug('Adding events', exc_info=e) - return str(e) + except Exception as ex: + logger.debug('Adding events', exc_info=ex) + return str(ex) # Send credits and stats to Inara on startup only - otherwise may be out of date if event_name == 'LoadGame': @@ -1113,7 +1114,7 @@ def journal_entry( # noqa: C901, CCR001 if not all(t in entry for t in ('Components', 'Consumables', 'Data', 'Items')): # So it's an empty event, core EDMC should have stuffed the data # into state['ShipLockerJSON']. - entry = state['ShipLockerJSON'] + entry = dict(state['ShipLockerJSON']) odyssey_plural_microresource_types = ('Items', 'Components', 'Data', 'Consumables') # we're getting new data here. so reset it on inara's side just to be sure that we set everything right @@ -1361,7 +1362,7 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001 this.lastcredits = int(data['commander']['credits']) -def make_loadout(state: Dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001 +def make_loadout(state: Mapping[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001 """ Construct an inara loadout from an event. From b0abf0fa623fb9e499bd2016a6fae84fdbe631b2 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 14:18:50 +0200 Subject: [PATCH 17/27] change default to 0, rearange comments credits defaulting to 0 is fine --- monitor.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/monitor.py b/monitor.py index 255970bdc..4d4f01a0b 100644 --- a/monitor.py +++ b/monitor.py @@ -123,15 +123,15 @@ def __init_state(self) -> None: # Cmdr state shared with EDSM and plugins # If you change anything here update PLUGINS.md documentation! self.state: MonitorStateDict = { - 'GameLanguage': '', # From `Fileheader - 'GameVersion': '', # From `Fileheader - 'GameBuild': '', # From `Fileheader - 'Captain': None, # On a crew + 'GameLanguage': '', # From `Fileheader + 'GameVersion': '', # From `Fileheader + 'GameBuild': '', # From `Fileheader + 'Captain': '', # On a crew 'Cargo': defaultdict(int), - 'Credits': 0-1, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 - 'FID': '', # Frontier Cmdr ID - 'Horizons': False, # Does this user have Horizons? - 'Odyssey': False, # Have we detected we're running under Odyssey? + 'Credits': 0, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 + 'FID': '', # Frontier Cmdr ID + 'Horizons': False, # Does this user have Horizons? + 'Odyssey': False, # Have we detected we're running under Odyssey? 'Loan': 0, 'Raw': defaultdict(int), 'Manufactured': defaultdict(int), @@ -140,9 +140,9 @@ def __init_state(self) -> None: 'Rank': {}, 'Reputation': {}, 'Statistics': {}, - 'Role': None, # Crew role - None, Idle, FireCon, FighterCon - 'Friends': set(), # Online friends - 'ShipID': 0-1, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 + 'Role': '', # Crew role - None, Idle, FireCon, FighterCon + 'Friends': set(), # Online friends + 'ShipID': 0-1, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 'ShipIdent': '', 'ShipName': '', 'ShipType': '', @@ -150,21 +150,21 @@ def __init_state(self) -> None: 'ModulesValue': 0, 'Rebuy': 0, 'Modules': {}, - 'CargoJSON': {}, # The raw data from the last time cargo.json was read + 'CargoJSON': {}, # The raw data from the last time cargo.json was read 'NavRoute': NavRouteDict(timestamp='', route=[]), # Last plotted route from Route.json file - 'OnFoot': False, # Whether we think you're on-foot - 'Component': defaultdict(int), # Odyssey Components in Ship Locker - 'Item': defaultdict(int), # Odyssey Items in Ship Locker - 'Consumable': defaultdict(int), # Odyssey Consumables in Ship Locker - 'Data': defaultdict(int), # Odyssey Data in Ship Locker - 'BackPack': { # Odyssey BackPack contents - 'Component': defaultdict(int), # BackPack Components - 'Consumable': defaultdict(int), # BackPack Consumables - 'Item': defaultdict(int), # BackPack Items - 'Data': defaultdict(int), # Backpack Data + 'OnFoot': False, # Whether we think you're on-foot + 'Component': defaultdict(int), # Odyssey Components in Ship Locker + 'Item': defaultdict(int), # Odyssey Items in Ship Locker + 'Consumable': defaultdict(int), # Odyssey Consumables in Ship Locker + 'Data': defaultdict(int), # Odyssey Data in Ship Locker + 'BackPack': { # Odyssey BackPack contents + 'Component': defaultdict(int), # BackPack Components + 'Consumable': defaultdict(int), # BackPack Consumables + 'Item': defaultdict(int), # BackPack Items + 'Data': defaultdict(int), # Backpack Data }, - 'BackpackJSON': {}, # Raw JSON from `Backpack.json` file, if available - 'ShipLockerJSON': {}, # Raw JSON from the `ShipLocker.json` file, if available + 'BackpackJSON': {}, # Raw JSON from `Backpack.json` file, if available + 'ShipLockerJSON': {}, # Raw JSON from the `ShipLocker.json` file, if available 'SuitCurrent': None, 'Suits': {}, 'SuitLoadoutCurrent': None, From a1dec247b4651113e75347a0b77886a9a018362a Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 14:19:09 +0200 Subject: [PATCH 18/27] removed Optional for multicrew items --- monitor_state_dict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monitor_state_dict.py b/monitor_state_dict.py index bb3a22c73..c5a819330 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -20,8 +20,8 @@ class MonitorStateDict(TypedDict): # Multi-crew - Captain: Optional[str] # If on a crew, the captian's name - Role: Optional[str] # Role in crew + Captain: str # If on a crew, the captian's name + Role: str # Role in crew # Cmdr state FID: str # Frontier CMDR ID From e43a38ea0f899fc4dffe4914947d12208c322660 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 15:49:12 +0200 Subject: [PATCH 19/27] Noted that 0-1 is a workaround for autopep8 --- monitor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monitor.py b/monitor.py index 4d4f01a0b..202daa326 100644 --- a/monitor.py +++ b/monitor.py @@ -128,7 +128,7 @@ def __init_state(self) -> None: 'GameBuild': '', # From `Fileheader 'Captain': '', # On a crew 'Cargo': defaultdict(int), - 'Credits': 0, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 + 'Credits': 0, 'FID': '', # Frontier Cmdr ID 'Horizons': False, # Does this user have Horizons? 'Odyssey': False, # Have we detected we're running under Odyssey? @@ -142,7 +142,7 @@ def __init_state(self) -> None: 'Statistics': {}, 'Role': '', # Crew role - None, Idle, FireCon, FighterCon 'Friends': set(), # Online friends - 'ShipID': 0-1, # HACK: https://github.com/PyCQA/pycodestyle/issues/1008 + 'ShipID': 0-1, # WORKAROUND: https://github.com/PyCQA/pycodestyle/issues/1008 'ShipIdent': '', 'ShipName': '', 'ShipType': '', @@ -1707,7 +1707,7 @@ def suitloadout_store_from_event(self, entry) -> Tuple[int, int]: suit = { 'edmcName': edmc_suitname, 'locName': suitname, - 'suitId': 0-1, + 'suitId': 0-1, # WORKAROUND: https://github.com/PyCQA/pycodestyle/issues/1008 'id': None, 'name': entry['SuitName'], 'mods': entry['SuitMods'] From 6ccf6ca8fe830e02d94d0fea506067c889191009 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 16:04:38 +0200 Subject: [PATCH 20/27] Moved some comments around --- monitor_state_dict.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monitor_state_dict.py b/monitor_state_dict.py index c5a819330..3d6d180a3 100644 --- a/monitor_state_dict.py +++ b/monitor_state_dict.py @@ -20,8 +20,8 @@ class MonitorStateDict(TypedDict): # Multi-crew - Captain: str # If on a crew, the captian's name - Role: str # Role in crew + Captain: str # If on a crew, the captian's name + Role: str # Role in crew # Cmdr state FID: str # Frontier CMDR ID @@ -129,7 +129,7 @@ class SuitDict(TypedDict): class OdysseyWeapon(_OdysseyWeaponClassField): - """Suit Weapon for an odyssey suit loadout""" + """Suit Weapon for an odyssey suit loadout.""" name: str locName: str # noqa: N815 @@ -141,6 +141,7 @@ class OdysseyWeapon(_OdysseyWeaponClassField): class _ModuleEngineeringModifiers(TypedDict): + """Engineering modifiers for (ship) modules.""" Label: str Value: float OriginalValue: float @@ -148,7 +149,7 @@ class _ModuleEngineeringModifiers(TypedDict): class _ModuleExperimentalEffects(TypedDict, total=False): - """Experimental effects an engineered module *MAY* have.""" + """Experimental effects an engineered (ship) module *MAY* have.""" ExperimentalEffect: str ExperimentalEffect_Localised: str @@ -167,7 +168,7 @@ class ModuleEngineering(_ModuleExperimentalEffects): class _ModulesOptionals(TypedDict, total=False): - """Optional fields a module may have.""" + """Optional fields a (ship) module may have.""" On: bool Priority: int @@ -177,7 +178,7 @@ class _ModulesOptionals(TypedDict, total=False): class _ModulesWeaponsOptionals(TypedDict, total=False): - """Optional fields modules *may* have if they are weapons.""" + """Optional fields (ship) modules *may* have if they are weapons.""" AmmoInClip: int AmmoInHopper: int From 66b39b54b73746c41e4b4fe35f2ffb7000b50da5 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 16:05:01 +0200 Subject: [PATCH 21/27] ensured that a missing locName doesnt explode --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index 2cbeacfbe..284243e3a 100644 --- a/companion.py +++ b/companion.py @@ -734,7 +734,7 @@ def suit_update(self, data: CAPIData) -> None: # We need to be setting our edmcName for all suits if (current_suit := monitor.state['SuitCurrent']) is not None: - loc_name = current_suit['locName'] if current_suit['locName'] else current_suit['name'] + loc_name = current_suit['locName'] if current_suit.get('locName') else current_suit['name'] current_suit['edmcName'] = monitor.suit_sane_name(loc_name) for s in monitor.state['Suits']: From c2367988b050b019ac1b4463ab7e96fce90ea38c Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 16:06:09 +0200 Subject: [PATCH 22/27] add extra entry for empty crew role --- EDMarketConnector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 9823aff2e..66bea7173 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1076,6 +1076,7 @@ def crewroletext(role: str) -> str: """ return { None: '', + '': '', 'Idle': '', 'FighterCon': _('Fighter'), # LANG: Multicrew role 'FireCon': _('Gunner'), # LANG: Multicrew role From dc7c745582380344c8920f9adb1146c970afe47f Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 16:08:00 +0200 Subject: [PATCH 23/27] note hacky use of undoccumented attr --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 202daa326..73c147970 100644 --- a/monitor.py +++ b/monitor.py @@ -391,7 +391,7 @@ def worker(self) -> None: # noqa: C901, CCR001 # Watchdog thread -- there is a way to get this by using self.observer.emitters and checking for an attribute: # watch, but that may have unforseen differences in behaviour. - # Note: Uses undocumented attribute + # HACK: Uses undocumented attribute emitter = self.observed and self.observer._emitter_for_watch[self.observed] # type: ignore logger.debug('Entering loop...') From 4724042785dd325dbb51db130642d964b13be597 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 16:08:59 +0200 Subject: [PATCH 24/27] missed two Captian and Role sets --- monitor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monitor.py b/monitor.py index 73c147970..072d0d655 100644 --- a/monitor.py +++ b/monitor.py @@ -552,7 +552,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # Don't set Ship, ShipID etc since this will reflect Fighter or SRV if starting in those # Cant use update() without the entire thing, do stuff manually here - self.state['Captain'] = None + self.state['Captain'] = '' self.state['Credits'] = entry['Credits'] self.state['FID'] = entry.get('FID', '') # From 3.3 self.state['Horizons'] = entry['Horizons'] # From 3.0 @@ -562,7 +562,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.state['Rank'] = {} self.state['Reputation'] = {} self.state['Statistics'] = {} - self.state['Role'] = None + self.state['Role'] = '' self.state['Taxi'] = False self.state['Dropship'] = False self.state['Body'] = '' @@ -1509,8 +1509,8 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C self.state['Role'] = entry['Role'] elif event_type == 'quitacrew': - self.state['Captain'] = None - self.state['Role'] = None + self.state['Captain'] = '' + self.state['Role'] = '' self.planet = None self.system = None self.station = None From 2e245f6ed71c5495e7ae66db4612c50097e7386d Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 6 Aug 2021 16:12:36 +0200 Subject: [PATCH 25/27] removed TYPE_CHECKING guard --- monitor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monitor.py b/monitor.py index 072d0d655..ea2657e8d 100644 --- a/monitor.py +++ b/monitor.py @@ -1014,9 +1014,8 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C for c in entry[changes]: category = self.category(c['Type']) name = self.canonicalise(c['Name']) - if TYPE_CHECKING: - # Cheaty "its fine I promise" for TypedDict - category = cast(Literal['Component', 'Data', 'Consumable', 'Item'], category) + # Cheaty "its fine I promise" for TypedDict + category = cast(Literal['Component', 'Data', 'Consumable', 'Item'], category) if changes == 'Removed': self.state['BackPack'][category][name] -= c['Count'] @@ -1407,6 +1406,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C for category in ('Raw', 'Manufactured', 'Encoded'): category = cast(Literal['Raw', 'Manufactured', 'Encoded'], category) + for x in entry.get('Ingredients', []): material = self.canonicalise(x['Name']) if material in self.state[category]: From 581a5d8df86dbaa6937d4e7a378c346ab938a249 Mon Sep 17 00:00:00 2001 From: A_D Date: Sat, 7 Aug 2021 14:23:29 +0200 Subject: [PATCH 26/27] resolve flake8 complaints --- plugins/eddb.py | 2 +- plugins/inara.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/eddb.py b/plugins/eddb.py index 1d4e1e9d4..c8792589c 100644 --- a/plugins/eddb.py +++ b/plugins/eddb.py @@ -94,7 +94,7 @@ def prefs_changed(cmdr, is_beta): pass -def journal_entry(cmdr, is_beta, system, station, entry, state: MonitorStateDict): +def journal_entry(cmdr, is_beta, system, station, entry, state: MonitorStateDict): # noqa: CCR001 D103 if (ks := killswitch.get_disabled('plugins.eddb.journal')).disabled: logger.warning(f'Journal processing for EDDB has been disabled: {ks.reason}') # LANG: Journal Processing disabled due to an active killswitch diff --git a/plugins/inara.py b/plugins/inara.py index 9bac0ec44..ead843151 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1,7 +1,6 @@ """Inara Sync.""" import json -from monitor_state_dict import MonitorStateDict import threading import time import tkinter as tk @@ -22,6 +21,7 @@ from companion import CAPIData from config import applongname, appversion, config, debug_senders from EDMCLogging import get_main_logger +from monitor_state_dict import MonitorStateDict from ttkHyperlinkLabel import HyperlinkLabel logger = get_main_logger() From 6a24473755562782d885d53383cfda8d7587260b Mon Sep 17 00:00:00 2001 From: A_D Date: Sat, 7 Aug 2021 14:25:15 +0200 Subject: [PATCH 27/27] fixed import order --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index 284243e3a..72afa4009 100644 --- a/companion.py +++ b/companion.py @@ -11,7 +11,6 @@ import csv import hashlib import json -from monitor_state_dict import SuitDict, SuitLoadoutDict import numbers import os import random @@ -29,6 +28,7 @@ from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor +from monitor_state_dict import SuitDict, SuitLoadoutDict from protocol import protocolhandler logger = get_main_logger()