mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-21 11:27:38 +03:00
Merge pull request #1004 from EDCD/enhancement/odyssey-suits
Add support for Odyssey Suit/Loadout data
This commit is contained in:
commit
27cd46e23d
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.gitversion
|
||||
.DS_Store
|
||||
build
|
||||
ChangeLog.html
|
||||
dist.*
|
||||
dump
|
||||
*.bak
|
||||
@ -21,4 +22,4 @@ venv
|
||||
*.code-workspace
|
||||
htmlcov/
|
||||
.ignored
|
||||
.coverage
|
||||
.coverage
|
||||
|
@ -12,10 +12,10 @@ import sys
|
||||
import webbrowser
|
||||
from builtins import object, str
|
||||
from os import chdir, environ
|
||||
from os.path import dirname, isdir, join
|
||||
from os.path import dirname, join
|
||||
from sys import platform
|
||||
from time import localtime, strftime, time
|
||||
from typing import TYPE_CHECKING, Any, Mapping, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
|
||||
# Have this as early as possible for people running EDMarketConnector.exe
|
||||
# from cmd.exe or a bat file or similar. Else they might not be in the correct
|
||||
@ -55,6 +55,7 @@ import killswitch
|
||||
from config import appversion, appversion_nobuild, config, copyright
|
||||
# isort: on
|
||||
|
||||
from companion import CAPIData
|
||||
from EDMCLogging import edmclogger, logger, logging
|
||||
from journal_lock import JournalLock, JournalLockResult
|
||||
|
||||
@ -316,6 +317,8 @@ class AppWindow(object):
|
||||
EVENT_BUTTON = 4
|
||||
EVENT_VIRTUAL = 35
|
||||
|
||||
PADX = 5
|
||||
|
||||
def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out
|
||||
|
||||
self.holdofftime = config.get_int('querytime', default=0) + companion.holdoff
|
||||
@ -350,37 +353,51 @@ class AppWindow(object):
|
||||
frame.columnconfigure(1, weight=1)
|
||||
|
||||
self.cmdr_label = tk.Label(frame)
|
||||
self.ship_label = tk.Label(frame)
|
||||
self.system_label = tk.Label(frame)
|
||||
self.station_label = tk.Label(frame)
|
||||
|
||||
self.cmdr_label.grid(row=1, column=0, sticky=tk.W)
|
||||
self.ship_label.grid(row=2, column=0, sticky=tk.W)
|
||||
self.system_label.grid(row=3, column=0, sticky=tk.W)
|
||||
self.station_label.grid(row=4, column=0, sticky=tk.W)
|
||||
|
||||
self.cmdr = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='cmdr')
|
||||
self.ship_label = tk.Label(frame)
|
||||
self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship')
|
||||
self.suit_label = tk.Label(frame)
|
||||
self.suit = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='suit')
|
||||
self.system_label = tk.Label(frame)
|
||||
self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.system_url, popup_copy=True, name='system')
|
||||
self.station_label = tk.Label(frame)
|
||||
self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station')
|
||||
# system and station text is set/updated by the 'provider' plugins
|
||||
# eddb, edsm and inara. Look for:
|
||||
#
|
||||
# parent.children['system'] / parent.children['station']
|
||||
self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.system_url, popup_copy=True, name='system')
|
||||
self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station')
|
||||
|
||||
self.cmdr.grid(row=1, column=1, sticky=tk.EW)
|
||||
self.ship.grid(row=2, column=1, sticky=tk.EW)
|
||||
self.system.grid(row=3, column=1, sticky=tk.EW)
|
||||
self.station.grid(row=4, column=1, sticky=tk.EW)
|
||||
ui_row = 1
|
||||
|
||||
self.cmdr_label.grid(row=ui_row, column=0, sticky=tk.W)
|
||||
self.cmdr.grid(row=ui_row, column=1, sticky=tk.EW)
|
||||
ui_row += 1
|
||||
|
||||
self.ship_label.grid(row=ui_row, column=0, sticky=tk.W)
|
||||
self.ship.grid(row=ui_row, column=1, sticky=tk.EW)
|
||||
ui_row += 1
|
||||
|
||||
self.suit_grid_row = ui_row
|
||||
self.suit_shown = False
|
||||
ui_row += 1
|
||||
|
||||
self.system_label.grid(row=ui_row, column=0, sticky=tk.W)
|
||||
self.system.grid(row=ui_row, column=1, sticky=tk.EW)
|
||||
ui_row += 1
|
||||
|
||||
self.station_label.grid(row=ui_row, column=0, sticky=tk.W)
|
||||
self.station.grid(row=ui_row, column=1, sticky=tk.EW)
|
||||
ui_row += 1
|
||||
|
||||
for plugin in plug.PLUGINS:
|
||||
appitem = plugin.get_app(frame)
|
||||
if appitem:
|
||||
tk.Frame(frame, highlightthickness=1).grid(columnspan=2, sticky=tk.EW) # separator
|
||||
if isinstance(appitem, tuple) and len(appitem) == 2:
|
||||
row = frame.grid_size()[1]
|
||||
appitem[0].grid(row=row, column=0, sticky=tk.W)
|
||||
appitem[1].grid(row=row, column=1, sticky=tk.EW)
|
||||
ui_row = frame.grid_size()[1]
|
||||
appitem[0].grid(row=ui_row, column=0, sticky=tk.W)
|
||||
appitem[1].grid(row=ui_row, column=1, sticky=tk.EW)
|
||||
|
||||
else:
|
||||
appitem.grid(columnspan=2, sticky=tk.EW)
|
||||
|
||||
@ -389,17 +406,17 @@ class AppWindow(object):
|
||||
self.theme_button = tk.Label(frame, width=32 if platform == 'darwin' else 28, state=tk.DISABLED)
|
||||
self.status = tk.Label(frame, name='status', anchor=tk.W)
|
||||
|
||||
row = frame.grid_size()[1]
|
||||
self.button.grid(row=row, columnspan=2, sticky=tk.NSEW)
|
||||
self.theme_button.grid(row=row, columnspan=2, sticky=tk.NSEW)
|
||||
ui_row = frame.grid_size()[1]
|
||||
self.button.grid(row=ui_row, columnspan=2, sticky=tk.NSEW)
|
||||
self.theme_button.grid(row=ui_row, columnspan=2, sticky=tk.NSEW)
|
||||
theme.register_alternate((self.button, self.theme_button, self.theme_button),
|
||||
{'row': row, 'columnspan': 2, 'sticky': tk.NSEW})
|
||||
{'row': ui_row, 'columnspan': 2, 'sticky': tk.NSEW})
|
||||
self.status.grid(columnspan=2, sticky=tk.EW)
|
||||
self.button.bind('<Button-1>', self.getandsend)
|
||||
theme.button_bind(self.theme_button, self.getandsend)
|
||||
|
||||
for child in frame.winfo_children():
|
||||
child.grid_configure(padx=5, pady=(platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0)
|
||||
child.grid_configure(padx=self.PADX, pady=(platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0)
|
||||
|
||||
# The type needs defining for adding the menu entry, but won't be
|
||||
# properly set until later
|
||||
@ -496,7 +513,7 @@ class AppWindow(object):
|
||||
theme_close.grid(row=0, column=4, padx=2)
|
||||
theme.button_bind(theme_close, self.onexit, image=self.theme_close)
|
||||
self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||
self.theme_file_menu.grid(row=1, column=0, padx=5, sticky=tk.W)
|
||||
self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
|
||||
theme.button_bind(self.theme_file_menu,
|
||||
lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
@ -513,7 +530,7 @@ class AppWindow(object):
|
||||
lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
+ e.widget.winfo_height()))
|
||||
tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=5, sticky=tk.EW)
|
||||
tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
|
||||
theme.register(self.theme_minimize) # images aren't automatically registered
|
||||
theme.register(self.theme_close)
|
||||
self.blank_menubar = tk.Frame(frame)
|
||||
@ -586,6 +603,52 @@ class AppWindow(object):
|
||||
config.delete('logdir', suppress=True)
|
||||
|
||||
self.postprefs(False) # Companion login happens in callback from monitor
|
||||
self.toggle_suit_row(visible=False)
|
||||
|
||||
def update_suit_text(self) -> None:
|
||||
"""Update the suit text for current type and loadout."""
|
||||
if (suit := monitor.state.get('SuitCurrent')) is None:
|
||||
self.suit['text'] = f'<{_("Unknown")}>'
|
||||
return
|
||||
|
||||
suitname = suit['locName']
|
||||
|
||||
if (suitloadout := monitor.state.get('SuitLoadoutCurrent')) is None:
|
||||
self.suit['text'] = ''
|
||||
return
|
||||
|
||||
loadout_name = suitloadout['name']
|
||||
self.suit['text'] = f'{suitname} ({loadout_name})'
|
||||
|
||||
def toggle_suit_row(self, visible: Optional[bool] = None) -> None:
|
||||
"""
|
||||
Toggle the visibility of the 'Suit' row.
|
||||
|
||||
:param visible: Force visibility to this.
|
||||
"""
|
||||
if visible is True:
|
||||
self.suit_shown = False
|
||||
|
||||
elif visible is False:
|
||||
self.suit_shown = True
|
||||
|
||||
if not self.suit_shown:
|
||||
if platform != 'win32':
|
||||
pady = 2
|
||||
|
||||
else:
|
||||
|
||||
pady = 0
|
||||
|
||||
self.suit_label.grid(row=self.suit_grid_row, column=0, sticky=tk.W, padx=self.PADX, pady=pady)
|
||||
self.suit.grid(row=self.suit_grid_row, column=1, sticky=tk.EW, padx=self.PADX, pady=pady)
|
||||
self.suit_shown = True
|
||||
|
||||
else:
|
||||
# Hide the Suit row
|
||||
self.suit_label.grid_forget()
|
||||
self.suit.grid_forget()
|
||||
self.suit_shown = False
|
||||
|
||||
def postprefs(self, dologin: bool = True):
|
||||
"""Perform necessary actions after the Preferences dialog is applied."""
|
||||
@ -615,6 +678,7 @@ class AppWindow(object):
|
||||
self.cmdr_label['text'] = _('Cmdr') + ':' # Main window
|
||||
# Multicrew role label in main window
|
||||
self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':' # Main window
|
||||
self.suit_label['text'] = _('Suit') + ':' # Main window
|
||||
self.system_label['text'] = _('System') + ':' # Main window
|
||||
self.station_label['text'] = _('Station') + ':' # Main window
|
||||
self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window
|
||||
@ -692,26 +756,7 @@ class AppWindow(object):
|
||||
|
||||
self.cooldown()
|
||||
|
||||
def dump_capi_data(self, data: Mapping[str, Any]):
|
||||
"""Dump CAPI data to file for examination."""
|
||||
if isdir('dump'):
|
||||
system = data['lastSystem']['name']
|
||||
|
||||
if data['commander'].get('docked'):
|
||||
station = f'.{data["lastStarport"]["name"]}'
|
||||
|
||||
else:
|
||||
station = ''
|
||||
|
||||
timestamp = strftime('%Y-%m-%dT%H.%M.%S', localtime())
|
||||
with open(f'dump/{system}{station}.{timestamp}.json', 'wb') as h:
|
||||
h.write(json.dumps(dict(data),
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
separators=(',', ': ')).encode('utf-8'))
|
||||
|
||||
def export_market_data(self, data: Mapping[str, Any]) -> bool: # noqa: CCR001
|
||||
def export_market_data(self, data: CAPIData) -> bool: # noqa: CCR001
|
||||
"""
|
||||
Export CAPI market data.
|
||||
|
||||
@ -839,7 +884,7 @@ class AppWindow(object):
|
||||
|
||||
else:
|
||||
if __debug__: # Recording
|
||||
self.dump_capi_data(data)
|
||||
companion.session.dump_capi_data(data)
|
||||
|
||||
if not monitor.state['ShipType']: # Started game in SRV or fighter
|
||||
self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name'])
|
||||
@ -853,6 +898,19 @@ class AppWindow(object):
|
||||
if monitor.state['Modules']:
|
||||
self.ship.configure(state=True)
|
||||
|
||||
if monitor.state.get('SuitCurrent') is not None:
|
||||
if (loadout := data.get('loadout')) is not None:
|
||||
if (suit := loadout.get('suit')) is not None:
|
||||
if (suitname := suit.get('locName')) is not None:
|
||||
# We've been paranoid about loadout->suit->suitname, now just assume loadouts is there
|
||||
loadout_name = data['loadouts'][f"{loadout['loadoutSlotId']}"]['name']
|
||||
self.suit['text'] = f'{suitname} ({loadout_name})'
|
||||
|
||||
self.toggle_suit_row(visible=True)
|
||||
|
||||
else:
|
||||
self.toggle_suit_row(visible=False)
|
||||
|
||||
if data['commander'].get('credits') is not None:
|
||||
monitor.state['Credits'] = data['commander']['credits']
|
||||
monitor.state['Loan'] = data['commander'].get('debt', 0)
|
||||
@ -974,6 +1032,8 @@ class AppWindow(object):
|
||||
self.ship_label['text'] = _('Ship') + ':' # Main window
|
||||
self.ship['text'] = ''
|
||||
|
||||
self.update_suit_text()
|
||||
|
||||
self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED) # Copy
|
||||
|
||||
if entry['event'] in (
|
||||
@ -1273,7 +1333,7 @@ class AppWindow(object):
|
||||
self.w.update_idletasks()
|
||||
|
||||
try:
|
||||
data = companion.session.station()
|
||||
data: CAPIData = companion.session.station()
|
||||
self.status['text'] = ''
|
||||
default_extension: str = ''
|
||||
|
||||
|
@ -532,6 +532,9 @@
|
||||
/* Label for 'UI Scaling' option [prefs.py] */
|
||||
"UI Scale Percentage" = "UI Scale Percentage";
|
||||
|
||||
/* General 'Unknown', e.g. suit loadout */
|
||||
"Unknown" = "Unknown";
|
||||
|
||||
/* Update button in main window. [EDMarketConnector.py] */
|
||||
"Update" = "Update";
|
||||
|
||||
|
17
PLUGINS.md
17
PLUGINS.md
@ -531,6 +531,15 @@ Content of `state` (updated to the current journal entry):
|
||||
| `NavRoute` | `dict` | Last plotted multi-hop route |
|
||||
| `ModuleInfo` | `dict` | Last loaded ModulesInfo.json data |
|
||||
| `OnFoot` | `bool` | Whether the Cmdr is on foot |
|
||||
| `Component` | `dict` | 'Component' MicroResources in Odyssey, `int` count each. |
|
||||
| `Item` | `dict` | 'Item' MicroResources in Odyssey, `int` count each. |
|
||||
| `Consumable` | `dict` | 'Consumable' MicroResources in Odyssey, `int` count each. |
|
||||
| `Data` | `dict` | 'Data' MicroResources in Odyssey, `int` count each. |
|
||||
| `BackPack` | `dict` | `dict` of Odyssey MicroResources in backpack. |
|
||||
| `SuitCurrent` | `dict` | CAPI-returned data of currently worn suit. NB: May be `None` if no data. |
|
||||
| `Suits` | `dict` | CAPI-returned data of owned suits. NB: May be `None` if no data. |
|
||||
| `SuitLoadoutCurrent` | `dict` | CAPI-returned data of current Suit Loadout. NB: May be `None` if no data. |
|
||||
| `SuitLoadouts` | `dict` | CAPI-returned data of all Suit Loadouts. NB: May be `None` if no data. |
|
||||
|
||||
New in version 4.1.6:
|
||||
|
||||
@ -553,8 +562,12 @@ Journal `ModuleInfo` event.
|
||||
`OnFoot` is an indication as to if the player is on-foot, rather than in a
|
||||
vehicle.
|
||||
|
||||
`Component` is a dict tracking your 'on-foot' materials for upgrading Suits
|
||||
and on-foot weapons.
|
||||
`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your
|
||||
Odyssey MicroResources in your Ship Locker. `BacKPack` contains `dict`s for
|
||||
the same when you're on-foot.
|
||||
|
||||
`SuitCurrent`, `Suits`, `SuitLoadoutCurrent` & `SuitLoadouts` hold CAPI data
|
||||
relating to suits and their loadouts.
|
||||
|
||||
##### Synthetic Events
|
||||
|
||||
|
42
companion.py
42
companion.py
@ -589,6 +589,10 @@ class Session(object):
|
||||
# logger.trace('timestamp not in data, adding from response headers')
|
||||
data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) # type: ignore
|
||||
|
||||
# Update Odyssey Suit data
|
||||
if endpoint == URL_QUERY:
|
||||
self.suit_update(data)
|
||||
|
||||
return data
|
||||
|
||||
def profile(self) -> CAPIData:
|
||||
@ -632,6 +636,25 @@ class Session(object):
|
||||
|
||||
return data
|
||||
|
||||
def suit_update(self, data: CAPIData) -> None:
|
||||
"""
|
||||
Update monitor.state suit data.
|
||||
|
||||
:param data: CAPI data to extra suit data from.
|
||||
"""
|
||||
if (current_suit := data.get('suit')) is None:
|
||||
# Probably no Odyssey on the account, so point attempting more.
|
||||
return
|
||||
|
||||
monitor.state['SuitCurrent'] = current_suit
|
||||
monitor.state['Suits'] = data.get('suits')
|
||||
|
||||
if (suit_loadouts := data.get('loadouts')) is None:
|
||||
logger.warning('CAPI data had "suit" but no (suit) "loadouts"')
|
||||
|
||||
monitor.state['SuitLoadoutCurrent'] = data.get('loadout')
|
||||
monitor.state['SuitLoadouts'] = suit_loadouts
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close CAPI authorization session."""
|
||||
self.state = Session.STATE_INIT
|
||||
@ -656,6 +679,25 @@ class Session(object):
|
||||
"""Log, as error, status of requests.Response from CAPI request."""
|
||||
logger.error(f'Frontier CAPI Auth: {r.url} {r.status_code} {r.reason and r.reason or "None"} {r.text}')
|
||||
|
||||
def dump_capi_data(self, data: CAPIData) -> None:
|
||||
"""Dump CAPI data to file for examination."""
|
||||
if os.path.isdir('dump'):
|
||||
system = data['lastSystem']['name']
|
||||
|
||||
if data['commander'].get('docked'):
|
||||
station = f'.{data["lastStarport"]["name"]}'
|
||||
|
||||
else:
|
||||
station = ''
|
||||
|
||||
timestamp = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime())
|
||||
with open(f'dump/{system}{station}.{timestamp}.json', 'wb') as h:
|
||||
h.write(json.dumps(dict(data),
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
separators=(',', ': ')).encode('utf-8'))
|
||||
|
||||
|
||||
def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully simplified
|
||||
"""
|
||||
|
@ -23,6 +23,11 @@ companion_category_map = {
|
||||
'NonMarketable': False, # Don't appear in the in-game market so don't report
|
||||
}
|
||||
|
||||
# Map suit symbol names to English localised names
|
||||
companion_suit_type_map = {
|
||||
'TacticalSuit_Class1': 'Dominator Suit',
|
||||
}
|
||||
|
||||
# Map Coriolis's names to names displayed in the in-game shipyard.
|
||||
coriolis_ship_map = {
|
||||
'Cobra Mk III': 'Cobra MkIII',
|
||||
|
144
monitor.py
144
monitor.py
@ -68,7 +68,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
_RE_CANONICALISE = re.compile(r'\$(.+)_name;')
|
||||
_RE_CATEGORY = re.compile(r'\$MICRORESOURCE_CATEGORY_(.+);')
|
||||
_RE_LOGFILE = re.compile(r'^Journal(Alpha|Beta)?\.[0-9]{12}\.[0-9]{2}\.log$')
|
||||
_RE_SHIP_ONFOOT = re.compile(r'^(FlightSuit|UtilitySuit_Class.)$')
|
||||
_RE_SHIP_ONFOOT = re.compile(r'^(FlightSuit|UtilitySuit_Class.|TacticalSuit_Class.|ExplorationSuit_Class.)$')
|
||||
|
||||
def __init__(self) -> None:
|
||||
# TODO(A_D): A bunch of these should be switched to default values (eg '' for strings) and no longer be Optional
|
||||
@ -114,42 +114,46 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
# Cmdr state shared with EDSM and plugins
|
||||
# If you change anything here update PLUGINS.md documentation!
|
||||
self.state: Dict = {
|
||||
'Captain': None, # On a crew
|
||||
'Cargo': defaultdict(int),
|
||||
'Credits': None,
|
||||
'FID': None, # Frontier Cmdr ID
|
||||
'Horizons': None, # Does this user have Horizons?
|
||||
'Loan': None,
|
||||
'Raw': defaultdict(int),
|
||||
'Manufactured': defaultdict(int),
|
||||
'Encoded': defaultdict(int),
|
||||
'Engineers': {},
|
||||
'Rank': {},
|
||||
'Reputation': {},
|
||||
'Statistics': {},
|
||||
'Role': None, # Crew role - None, Idle, FireCon, FighterCon
|
||||
'Friends': set(), # Online friends
|
||||
'ShipID': None,
|
||||
'ShipIdent': None,
|
||||
'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
|
||||
'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
|
||||
'Captain': None, # On a crew
|
||||
'Cargo': defaultdict(int),
|
||||
'Credits': None,
|
||||
'FID': None, # Frontier Cmdr ID
|
||||
'Horizons': None, # Does this user have Horizons?
|
||||
'Loan': None,
|
||||
'Raw': defaultdict(int),
|
||||
'Manufactured': defaultdict(int),
|
||||
'Encoded': defaultdict(int),
|
||||
'Engineers': {},
|
||||
'Rank': {},
|
||||
'Reputation': {},
|
||||
'Statistics': {},
|
||||
'Role': None, # Crew role - None, Idle, FireCon, FighterCon
|
||||
'Friends': set(), # Online friends
|
||||
'ShipID': None,
|
||||
'ShipIdent': None,
|
||||
'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
|
||||
'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
|
||||
'Component': defaultdict(int), # BackPack Components
|
||||
'Consumable': defaultdict(int), # BackPack Consumables
|
||||
'Item': defaultdict(int), # BackPack Items
|
||||
'Data': defaultdict(int), # Backpack Data
|
||||
},
|
||||
'SuitCurrent': None,
|
||||
'Suits': None,
|
||||
'SuitLoadoutCurrent': None,
|
||||
'SuitLoadouts': None,
|
||||
}
|
||||
|
||||
def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001
|
||||
@ -823,6 +827,63 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
if self.state['BackPack'][c][m] < 0:
|
||||
self.state['BackPack'][c][m] = 0
|
||||
|
||||
elif event_type == 'SwitchSuitLoadout':
|
||||
loadoutid = entry['LoadoutID']
|
||||
new_slot = self.suit_loadout_id_from_loadoutid(loadoutid)
|
||||
try:
|
||||
self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][f'{new_slot}']
|
||||
|
||||
except KeyError:
|
||||
logger.exception(f"Getting suit loadout after switch, bad slot: {new_slot} ({loadoutid})")
|
||||
# Might mean that a new suit loadout was created and we need a new CAPI fetch ?
|
||||
self.state['SuitCurrent'] = None
|
||||
self.state['SuitLoadoutCurrent'] = None
|
||||
|
||||
else:
|
||||
try:
|
||||
new_suitid = self.state['SuitLoadoutCurrent']['suit']['suitId']
|
||||
|
||||
except KeyError:
|
||||
logger.exception(f"Getting switched-to suit ID from slot {new_slot} ({loadoutid})")
|
||||
|
||||
else:
|
||||
try:
|
||||
self.state['SuitCurrent'] = self.state['Suits'][f'{new_suitid}']
|
||||
|
||||
except KeyError:
|
||||
logger.exception(f"Getting switched-to suit from slot {new_slot} ({loadoutid}")
|
||||
|
||||
elif event_type == 'DeleteSuitLoadout':
|
||||
# We should remove this from the monitor.state record of loadouts. The slotid
|
||||
# could end up valid due to CreateSuitLoadout events, but we won't have the
|
||||
# correct new loadout data until next CAPI pull.
|
||||
loadoutid = entry['LoadoutID']
|
||||
slotid = self.suit_loadout_id_from_loadoutid(loadoutid)
|
||||
# This might be a Loadout that was created after our last CAPI pull.
|
||||
try:
|
||||
self.state['SuitLoadouts'].pop(f'{slotid}')
|
||||
|
||||
except KeyError:
|
||||
logger.exception(f"slot id {slotid} doesn't exist, not in last CAPI pull ?")
|
||||
|
||||
elif event_type == 'CreateSuitLoadout':
|
||||
# We know we won't have data for this new one
|
||||
pass
|
||||
|
||||
# `BuySuit` has no useful info as of 4.0.0.13
|
||||
|
||||
elif event_type == 'SellSuit':
|
||||
# Remove from known suits
|
||||
# As of Odyssey Alpha Phase 2, Hotfix 5 (4.0.0.13) this isn't possible as this event
|
||||
# doesn't contain the specific suit ID as per CAPI `suits` dict.
|
||||
pass
|
||||
|
||||
# `LoadoutEquipModule` has no instance-specific ID as of 4.0.0.13
|
||||
|
||||
# `BuyWeapon` has no instance-specific ID as of 4.0.0.13
|
||||
# `SellWeapon` has no instance-specific ID as of 4.0.0.13
|
||||
# `UpgradeWeapon` has no instance-specific ID as of 4.0.0.13
|
||||
|
||||
elif event_type == 'NavRoute':
|
||||
# Added in ED 3.7 - multi-hop route details in NavRoute.json
|
||||
with open(join(self.currentdir, 'NavRoute.json'), 'rb') as rf: # type: ignore
|
||||
@ -1027,6 +1088,19 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
logger.debug(f'Invalid journal entry:\n{line!r}\n', exc_info=ex)
|
||||
return {'event': None}
|
||||
|
||||
def suit_loadout_id_from_loadoutid(self, journal_loadoutid: int) -> int:
|
||||
"""
|
||||
Determine the CAPI-oriented numeric slot id for a Suit Loadout.
|
||||
|
||||
:param journal_loadoutid: Journal `LoadoutID` integer value.
|
||||
:return:
|
||||
"""
|
||||
# Observed LoadoutID in SwitchSuitLoadout events are, e.g.
|
||||
# 4293000005 for CAPI slot 5.
|
||||
# This *might* actually be "lower 6 bits", but maybe it's not.
|
||||
slotid = journal_loadoutid - 4293000000
|
||||
return slotid
|
||||
|
||||
def canonicalise(self, item: Optional[str]) -> str:
|
||||
"""
|
||||
Produce canonical name for a ship module.
|
||||
|
Loading…
x
Reference in New Issue
Block a user