diff --git a/monitor.py b/monitor.py index eff8799f..215b76bb 100644 --- a/monitor.py +++ b/monitor.py @@ -160,6 +160,10 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below '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, } def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001 @@ -261,6 +265,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.systemaddress = None self.is_beta = False self.state['OnFoot'] = False + self.state['Body'] = None + self.state['BodyType'] = None if self.observed: logger.debug('self.observed: Calling unschedule_all()') @@ -535,6 +541,10 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below 'Reputation': {}, 'Statistics': {}, 'Role': None, + 'Taxi': None, + 'Dropship': None, + 'Body': None, + 'BodyType': None, }) if entry.get('Ship') is not None and self._RE_SHIP_ONFOOT.search(entry['Ship']): self.state['OnFoot'] = True @@ -668,6 +678,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.station = entry.get('StationName', '') self.state['OnFoot'] = False + self.state['Taxi'] = entry['Taxi'] elif event_type == 'Disembark': # This event is logged when the player steps out of a ship or SRV @@ -694,10 +705,17 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.station = None self.state['OnFoot'] = True + if self.state['Taxi'] is not None and self.state['Taxi'] != entry.get('Taxi', False): + logger.warning('Disembarked from a taxi but we didn\'t know we were in a taxi?') + + self.state['Taxi'] = False + self.state['Dropship'] = False elif event_type == 'DropshipDeploy': # We're definitely on-foot now self.state['OnFoot'] = True + self.state['Taxi'] = False + self.state['Dropship'] = False elif event_type == 'Docked': self.station = entry.get('StationName') # May be None @@ -705,6 +723,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.stationtype = entry.get('StationType') # May be None self.stationservices = entry.get('StationServices') # None under E:D < 2.4 + # No need to set self.state['Taxi'] or Dropship here, if its those, the next event is a Disembark anyway + elif event_type in ('Location', 'FSDJump', 'CarrierJump'): # alpha4 - any changes ? # Location: @@ -715,12 +735,16 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # • 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') # if event_type == 'Location': # logger.trace('"Location" event') elif event_type == 'FSDJump': self.planet = None + self.state['Body'] = None + self.state['BodyType'] = None if 'StarPos' in entry: self.coordinates = tuple(entry['StarPos']) # type: ignore @@ -742,11 +766,19 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below 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) + if not self.state['Taxi']: + self.state['Dropship'] = None + elif event_type == 'ApproachBody': self.planet = entry['Body'] + self.state['Body'] = entry['Body'] + self.state['BodyType'] = 'Planet' # Best guess. Journal says always planet. elif event_type in ('LeaveBody', 'SupercruiseEntry'): self.planet = None + self.state['Body'] = None + self.state['BodyType'] = None elif event_type in ('Rank', 'Promotion'): payload = dict(entry) @@ -1284,6 +1316,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below elif event_type == 'BookDropship': self.state['Credits'] -= entry.get('Cost', 0) + self.state['Dropship'] = True # Technically we *might* now not be OnFoot. # The problem is that this event is recorded both for signing up for # an on-foot CZ, and when you use the Dropship to return after the @@ -1297,12 +1330,16 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below elif event_type == 'BookTaxi': self.state['Credits'] -= entry.get('Cost', 0) + # Dont set taxi state here, as we're not IN a taxi yet. Set it on Embark elif event_type == 'CancelDropship': self.state['Credits'] += entry.get('Refund', 0) + self.state['Dropship'] = False + self.state['Taxi'] = False elif event_type == 'CancelTaxi': self.state['Credits'] += entry.get('Refund', 0) + self.state['Taxi'] = False elif event_type == 'NavRoute': # Added in ED 3.7 - multi-hop route details in NavRoute.json @@ -1493,6 +1530,9 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.systemaddress = None self.state['OnFoot'] = False + self.state['Body'] = None + self.state['BodyType'] = None + elif event_type == 'ChangeCrewRole': self.state['Role'] = entry['Role'] @@ -1507,6 +1547,9 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.stationservices = None self.coordinates = None self.systemaddress = None + + self.state['Body'] = None + self.state['BodyType'] = None # TODO: on_foot: Will we get an event after this to know ? elif event_type == 'Friends': @@ -1588,6 +1631,11 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below elif event_type == 'Resurrect': self.state['Credits'] -= entry.get('Cost', 0) + # HACK (not game related / 2021-06-2): self.planet is moved into a more general self.state['Body']. + # This exists to help plugins doing what they SHOULDN'T BE cope. It will be removed at some point. + if self.state['Body'] is None or self.state['BodyType'] == 'Planet': + self.planet = self.state['Body'] + return entry except Exception as ex: diff --git a/plugins/inara.py b/plugins/inara.py index 5e08cf74..c6795bd0 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -565,15 +565,37 @@ def journal_entry( # noqa: C901, CCR001 this.suppress_docked = False else: + to_send = { + 'starsystemName': system, + 'stationName': station, + 'shipType': state['ShipType'], + 'shipGameID': state['ShipID'], + } + + if entry.get('Taxi'): + # we're in a taxi, dont store ShipType or shipGameID + del to_send['shipType'] + del to_send['shipGameID'] + + # We were in a taxi. What kind? + if state['Dropship'] is not None and state['Dropship']: + to_send['isTaxiDropship'] = True + + elif state['Taxi'] is not None and state['Taxi']: + to_send['isTaxiShuttle'] = True + + else: # we dont know one way or another. Given we were told it IS a taxi, assume its a shuttle. + to_send['isTaxiShuttle'] = True + + if 'MarketID' in entry: + to_send['marketID'] = entry['MarketID'] + + # TODO: we _can_ include a Body name here, but I'm not entirely sure how best to go about doing that + new_add_event( 'addCommanderTravelDock', entry['timestamp'], - { - 'starsystemName': system, - 'stationName': station, - 'shipType': state['ShipType'], - 'shipGameID': state['ShipID'], - } + to_send ) elif event_name == 'Undocked': @@ -597,15 +619,29 @@ def journal_entry( # noqa: C901, CCR001 elif event_name == 'FSDJump': this.undocked = False + to_send = { + 'starsystemName': entry['StarSystem'], + 'starsystemCoords': entry['StarPos'], + 'jumpDistance': entry['JumpDist'], + 'shipType': state['ShipType'], + 'shipGameID': state['ShipID'], + } + + if state['Taxi'] is not None and state['Taxi']: + del to_send['shipType'] + del to_send['shipGameID'] + + # taxi. What kind? + if state['Dropship'] is not None and state['Dropship']: + to_send['isTaxiDropship'] = True + + else: + to_send['isTaxiShuttle'] = True + new_add_event( 'addCommanderTravelFSDJump', entry['timestamp'], - { - 'starsystemName': entry['StarSystem'], - 'jumpDistance': entry['JumpDist'], - 'shipType': state['ShipType'], - 'shipGameID': state['ShipID'], - } + to_send ) if entry.get('Factions'): @@ -619,16 +655,21 @@ def journal_entry( # noqa: C901, CCR001 ) elif event_name == 'CarrierJump': + to_send = { + 'starsystemName': entry['StarSystem'], + 'stationName': entry['StationName'], + 'marketID': entry['MarketID'], + 'shipType': state['ShipType'], + 'shipGameID': state['ShipID'], + } + + if 'StarPos' in entry: + to_send['starsystemCoords'] = entry['StarPos'] + new_add_event( 'addCommanderTravelCarrierJump', entry['timestamp'], - { - 'starsystemName': entry['StarSystem'], - 'stationName': entry['StationName'], - 'marketID': entry['MarketID'], - 'shipType': state['ShipType'], - 'shipGameID': state['ShipID'], - } + to_send ) if entry.get('Factions'): @@ -975,6 +1016,167 @@ def journal_entry( # noqa: C901, CCR001 } ) + # New Odyssey features + elif event_name == 'DropshipDeploy': + new_add_event( + 'addCommanderTravelLand', + entry['timestamp'], + { + 'starsystemName': entry['StarSystem'], + 'starsystemBodyName': entry['Body'], + 'isTaxiDropship': True, + } + ) + + elif event_name == 'Touchdown': + # Touchdown has FAR more info available on Odyssey vs Horizons: + # Horizons: + # {"timestamp":"2021-05-31T09:10:54Z","event":"Touchdown", + # "PlayerControlled":true,"Latitude":46.691929,"Longitude":-92.679977} + # + # Odyssey: + # {"timestamp":"2021-05-31T08:48:08Z","event":"Touchdown","PlayerControlled":true,"Taxi":false, + # "Multicrew":false,"StarSystem":"Gateway","SystemAddress":2832631665362,"Body":"Saunder's Rock","BodyID":2, + # "OnStation":false,"OnPlanet":true,"Latitude":54.79665,"Longitude":-99.498253} + # + # So we're going to do a lot of checking here and bail out if we dont like the look of ANYTHING here + + to_send_data: Optional[Dict[str, Any]] = {} # This is a glorified sentinel until lower down. + # On Horizons, neither of these exist on TouchDown + star_system_name = entry.get('StarSystem', this.system) + body_name = entry.get('Body', state['Body'] if state['BodyType'] == 'Planet' else None) + + if star_system_name is None: + logger.warning('Refusing to update addCommanderTravelLand as we dont have a StarSystem!') + to_send_data = None + + if body_name is None: + logger.warning('Refusing to update addCommanderTravelLand as we dont have a Body!') + to_send_data = None + + if (op := entry.get('OnPlanet')) is not None and not op: + logger.warning('Refusing to update addCommanderTravelLand when OnPlanet is False!') + logger.warning(f'{entry=}') + to_send_data = None + + if not entry['PlayerControlled']: + logger.info("Not updating inara addCommanderTravelLand for autonomous recall landing") + to_send_data = None + + if to_send_data is not None: + # Above checks passed. Lets build and send this! + to_send_data['starsystemName'] = star_system_name # Required + to_send_data['starsystemBodyName'] = body_name # Required + + # Following are optional + + # lat/long is always there unless its an automated (recall) landing. Thus as we're sure its _not_ + # we can assume this exists. If it doesn't its a bug anyway. + to_send_data['starsystemBodyCoords'] = [entry['Latitude'], entry['Longitude']] + if state.get('ShipID') is not None: + to_send_data['shipGameID'] = state['ShipID'] + + if state.get('ShipType') is not None: + to_send_data['shipType'] = state['ShipType'] + + to_send_data['isTaxiShuttle'] = False + to_send_data['isTaxiDropShip'] = False + + new_add_event('addCommanderTravelLand', entry['timestamp'], to_send_data) + + elif event_name == 'ShipLockerMaterials': + 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 + reset_data = [{'itemType': t} for t in odyssey_plural_microresource_types] + set_data = [] + for typ in odyssey_plural_microresource_types: + set_data.extend([ + {'itemName': thing['Name'], 'itemCount': thing['Count'], 'itemType': typ} for thing in entry[typ] + ]) + + new_add_event('resetCommanderInventory', entry['timestamp'], reset_data) + new_add_event('setCommanderInventory', entry['timestamp'], set_data) + + elif event_name in ('CreateSuitLoadout', 'SuitLoadout'): + # CreateSuitLoadout and SuitLoadout are pretty much the same event: + # ╙─╴% cat Journal.* | jq 'select(.event == "SuitLoadout" or .event == "CreateSuitLoadout") | keys' -c \ + # | uniq + # + # ["LoadoutID","LoadoutName","Modules","SuitID","SuitMods","SuitName","SuitName_Localised","event", + # "timestamp"] + + to_send = { + 'loadoutGameID': entry['LoadoutID'], + 'loadoutName': entry['LoadoutName'], + 'suitGameID': entry['SuitID'], + 'suitType': entry['SuitName'], + 'suitMods': entry['SuitMods'], + 'suitLoadout': [ + { + 'slotName': x['SlotName'], + 'itemName': x['ModuleName'], + 'itemClass': x['Class'], + 'itemGameID': x['SuitModuleID'], + 'engineering': [{'blueprintName': mod} for mod in x['WeaponMods']], + } for x in entry['Modules'] + ], + } + + new_add_event('setCommanderSuitLoadout', entry['timestamp'], to_send) + + elif event_name == 'DeleteSuitLoadout': + new_add_event('delCommanderSuitLoadout', entry['timestamp'], {'loadoutGameID': entry['LoadoutID']}) + + elif event_name == 'RenameSuitLoadout': + to_send = { + 'loadoutGameID': entry['LoadoutID'], + 'loadoutName': entry['LoadoutName'], + # may as well... + 'suitType': entry['SuitName'], + 'suitGameID': entry['SuitID'] + } + new_add_event('updateCommanderSuitLoadout', entry['timestamp'], {}) + + elif event_name == 'LoadoutEquipModule': + to_send = { + 'loadoutGameID': entry['LoadoutID'], + 'loadoutName': entry['LoadoutName'], + 'suitType': entry['SuitName'], + 'suitGameID': entry['SuitID'], + 'suitLoadout': [ + { + 'slotName': entry['SlotName'], + 'itemName': entry['ModuleName'], + 'itemGameID': entry['SuitModuleID'], + 'itemClass': entry['Class'], + 'engineering': [{'blueprintName': mod} for mod in entry['WeaponMods']], + } + ], + } + + new_add_event('updateCommanderSuitLoadout', entry['timestamp'], to_send) + + elif event_name == "Location": + to_send = { + 'starsystemName': entry['StarSystem'], + 'starsystemCoords': entry['StarPos'], + } + + if entry['Docked']: + to_send['stationName'] = entry['StationName'] + to_send['marketID'] = entry['MarketID'] + + if entry['Docked'] and entry['BodyType'] == 'Planet': + # we're Docked, but we're not on a Station, thus we're docked at a planetary base of some kind + # and thus, we SHOULD include starsystemBodyName + to_send['starsystemBodyName'] = entry['Body'] + + if 'Longitude' in entry and 'Latitude' in entry: + # These were included thus we are landed + to_send['starsystemBodyCoords'] = [entry['Latitude'], entry['Longitude']] + + new_add_event('setCommanderTravelLocation', entry['timestamp'], to_send) + # Community Goals if event_name == 'CommunityGoal': # Remove any unsent @@ -1250,7 +1452,7 @@ def new_worker(): 'appVersion': str(appversion()), 'APIkey': creds.api_key, 'commanderName': creds.cmdr, - 'commanderFrontierID': creds.fid + 'commanderFrontierID': creds.fid, }, 'events': [ {'eventName': e.name, 'eventTimestamp': e.timestamp, 'eventData': e.data} for e in event_list @@ -1307,6 +1509,7 @@ def send_data(url: str, data: Mapping[str, Any]) -> bool: # noqa: CCR001 :param data: the data to POST :return: success state """ + r = this.session.post(url, data=json.dumps(data, separators=(',', ':')), timeout=_TIMEOUT) r.raise_for_status() reply = r.json()