mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-06-08 11:22:10 +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
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
.gitversion
|
.gitversion
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build
|
build
|
||||||
|
ChangeLog.html
|
||||||
dist.*
|
dist.*
|
||||||
dump
|
dump
|
||||||
*.bak
|
*.bak
|
||||||
|
@ -12,10 +12,10 @@ import sys
|
|||||||
import webbrowser
|
import webbrowser
|
||||||
from builtins import object, str
|
from builtins import object, str
|
||||||
from os import chdir, environ
|
from os import chdir, environ
|
||||||
from os.path import dirname, isdir, join
|
from os.path import dirname, join
|
||||||
from sys import platform
|
from sys import platform
|
||||||
from time import localtime, strftime, time
|
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
|
# 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
|
# 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
|
from config import appversion, appversion_nobuild, config, copyright
|
||||||
# isort: on
|
# isort: on
|
||||||
|
|
||||||
|
from companion import CAPIData
|
||||||
from EDMCLogging import edmclogger, logger, logging
|
from EDMCLogging import edmclogger, logger, logging
|
||||||
from journal_lock import JournalLock, JournalLockResult
|
from journal_lock import JournalLock, JournalLockResult
|
||||||
|
|
||||||
@ -316,6 +317,8 @@ class AppWindow(object):
|
|||||||
EVENT_BUTTON = 4
|
EVENT_BUTTON = 4
|
||||||
EVENT_VIRTUAL = 35
|
EVENT_VIRTUAL = 35
|
||||||
|
|
||||||
|
PADX = 5
|
||||||
|
|
||||||
def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out
|
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
|
self.holdofftime = config.get_int('querytime', default=0) + companion.holdoff
|
||||||
@ -350,37 +353,51 @@ class AppWindow(object):
|
|||||||
frame.columnconfigure(1, weight=1)
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
self.cmdr_label = tk.Label(frame)
|
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.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.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
|
# system and station text is set/updated by the 'provider' plugins
|
||||||
# eddb, edsm and inara. Look for:
|
# eddb, edsm and inara. Look for:
|
||||||
#
|
#
|
||||||
# parent.children['system'] / parent.children['station']
|
# 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)
|
ui_row = 1
|
||||||
self.ship.grid(row=2, column=1, sticky=tk.EW)
|
|
||||||
self.system.grid(row=3, column=1, sticky=tk.EW)
|
self.cmdr_label.grid(row=ui_row, column=0, sticky=tk.W)
|
||||||
self.station.grid(row=4, column=1, sticky=tk.EW)
|
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:
|
for plugin in plug.PLUGINS:
|
||||||
appitem = plugin.get_app(frame)
|
appitem = plugin.get_app(frame)
|
||||||
if appitem:
|
if appitem:
|
||||||
tk.Frame(frame, highlightthickness=1).grid(columnspan=2, sticky=tk.EW) # separator
|
tk.Frame(frame, highlightthickness=1).grid(columnspan=2, sticky=tk.EW) # separator
|
||||||
if isinstance(appitem, tuple) and len(appitem) == 2:
|
if isinstance(appitem, tuple) and len(appitem) == 2:
|
||||||
row = frame.grid_size()[1]
|
ui_row = frame.grid_size()[1]
|
||||||
appitem[0].grid(row=row, column=0, sticky=tk.W)
|
appitem[0].grid(row=ui_row, column=0, sticky=tk.W)
|
||||||
appitem[1].grid(row=row, column=1, sticky=tk.EW)
|
appitem[1].grid(row=ui_row, column=1, sticky=tk.EW)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
appitem.grid(columnspan=2, sticky=tk.EW)
|
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.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)
|
self.status = tk.Label(frame, name='status', anchor=tk.W)
|
||||||
|
|
||||||
row = frame.grid_size()[1]
|
ui_row = frame.grid_size()[1]
|
||||||
self.button.grid(row=row, columnspan=2, sticky=tk.NSEW)
|
self.button.grid(row=ui_row, columnspan=2, sticky=tk.NSEW)
|
||||||
self.theme_button.grid(row=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),
|
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.status.grid(columnspan=2, sticky=tk.EW)
|
||||||
self.button.bind('<Button-1>', self.getandsend)
|
self.button.bind('<Button-1>', self.getandsend)
|
||||||
theme.button_bind(self.theme_button, self.getandsend)
|
theme.button_bind(self.theme_button, self.getandsend)
|
||||||
|
|
||||||
for child in frame.winfo_children():
|
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
|
# The type needs defining for adding the menu entry, but won't be
|
||||||
# properly set until later
|
# properly set until later
|
||||||
@ -496,7 +513,7 @@ class AppWindow(object):
|
|||||||
theme_close.grid(row=0, column=4, padx=2)
|
theme_close.grid(row=0, column=4, padx=2)
|
||||||
theme.button_bind(theme_close, self.onexit, image=self.theme_close)
|
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 = 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,
|
theme.button_bind(self.theme_file_menu,
|
||||||
lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
|
lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
|
||||||
e.widget.winfo_rooty()
|
e.widget.winfo_rooty()
|
||||||
@ -513,7 +530,7 @@ class AppWindow(object):
|
|||||||
lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
|
lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
|
||||||
e.widget.winfo_rooty()
|
e.widget.winfo_rooty()
|
||||||
+ e.widget.winfo_height()))
|
+ 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_minimize) # images aren't automatically registered
|
||||||
theme.register(self.theme_close)
|
theme.register(self.theme_close)
|
||||||
self.blank_menubar = tk.Frame(frame)
|
self.blank_menubar = tk.Frame(frame)
|
||||||
@ -586,6 +603,52 @@ class AppWindow(object):
|
|||||||
config.delete('logdir', suppress=True)
|
config.delete('logdir', suppress=True)
|
||||||
|
|
||||||
self.postprefs(False) # Companion login happens in callback from monitor
|
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):
|
def postprefs(self, dologin: bool = True):
|
||||||
"""Perform necessary actions after the Preferences dialog is applied."""
|
"""Perform necessary actions after the Preferences dialog is applied."""
|
||||||
@ -615,6 +678,7 @@ class AppWindow(object):
|
|||||||
self.cmdr_label['text'] = _('Cmdr') + ':' # Main window
|
self.cmdr_label['text'] = _('Cmdr') + ':' # Main window
|
||||||
# Multicrew role label in main window
|
# Multicrew role label in main window
|
||||||
self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':' # 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.system_label['text'] = _('System') + ':' # Main window
|
||||||
self.station_label['text'] = _('Station') + ':' # Main window
|
self.station_label['text'] = _('Station') + ':' # Main window
|
||||||
self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window
|
self.button['text'] = self.theme_button['text'] = _('Update') # Update button in main window
|
||||||
@ -692,26 +756,7 @@ class AppWindow(object):
|
|||||||
|
|
||||||
self.cooldown()
|
self.cooldown()
|
||||||
|
|
||||||
def dump_capi_data(self, data: Mapping[str, Any]):
|
def export_market_data(self, data: CAPIData) -> bool: # noqa: CCR001
|
||||||
"""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
|
|
||||||
"""
|
"""
|
||||||
Export CAPI market data.
|
Export CAPI market data.
|
||||||
|
|
||||||
@ -839,7 +884,7 @@ class AppWindow(object):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
if __debug__: # Recording
|
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
|
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'])
|
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']:
|
if monitor.state['Modules']:
|
||||||
self.ship.configure(state=True)
|
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:
|
if data['commander'].get('credits') is not None:
|
||||||
monitor.state['Credits'] = data['commander']['credits']
|
monitor.state['Credits'] = data['commander']['credits']
|
||||||
monitor.state['Loan'] = data['commander'].get('debt', 0)
|
monitor.state['Loan'] = data['commander'].get('debt', 0)
|
||||||
@ -974,6 +1032,8 @@ class AppWindow(object):
|
|||||||
self.ship_label['text'] = _('Ship') + ':' # Main window
|
self.ship_label['text'] = _('Ship') + ':' # Main window
|
||||||
self.ship['text'] = ''
|
self.ship['text'] = ''
|
||||||
|
|
||||||
|
self.update_suit_text()
|
||||||
|
|
||||||
self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED) # Copy
|
self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED) # Copy
|
||||||
|
|
||||||
if entry['event'] in (
|
if entry['event'] in (
|
||||||
@ -1273,7 +1333,7 @@ class AppWindow(object):
|
|||||||
self.w.update_idletasks()
|
self.w.update_idletasks()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = companion.session.station()
|
data: CAPIData = companion.session.station()
|
||||||
self.status['text'] = ''
|
self.status['text'] = ''
|
||||||
default_extension: str = ''
|
default_extension: str = ''
|
||||||
|
|
||||||
|
@ -532,6 +532,9 @@
|
|||||||
/* Label for 'UI Scaling' option [prefs.py] */
|
/* Label for 'UI Scaling' option [prefs.py] */
|
||||||
"UI Scale Percentage" = "UI Scale Percentage";
|
"UI Scale Percentage" = "UI Scale Percentage";
|
||||||
|
|
||||||
|
/* General 'Unknown', e.g. suit loadout */
|
||||||
|
"Unknown" = "Unknown";
|
||||||
|
|
||||||
/* Update button in main window. [EDMarketConnector.py] */
|
/* Update button in main window. [EDMarketConnector.py] */
|
||||||
"Update" = "Update";
|
"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 |
|
| `NavRoute` | `dict` | Last plotted multi-hop route |
|
||||||
| `ModuleInfo` | `dict` | Last loaded ModulesInfo.json data |
|
| `ModuleInfo` | `dict` | Last loaded ModulesInfo.json data |
|
||||||
| `OnFoot` | `bool` | Whether the Cmdr is on foot |
|
| `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:
|
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
|
`OnFoot` is an indication as to if the player is on-foot, rather than in a
|
||||||
vehicle.
|
vehicle.
|
||||||
|
|
||||||
`Component` is a dict tracking your 'on-foot' materials for upgrading Suits
|
`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your
|
||||||
and on-foot weapons.
|
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
|
##### 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')
|
# 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
|
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
|
return data
|
||||||
|
|
||||||
def profile(self) -> CAPIData:
|
def profile(self) -> CAPIData:
|
||||||
@ -632,6 +636,25 @@ class Session(object):
|
|||||||
|
|
||||||
return data
|
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:
|
def close(self) -> None:
|
||||||
"""Close CAPI authorization session."""
|
"""Close CAPI authorization session."""
|
||||||
self.state = Session.STATE_INIT
|
self.state = Session.STATE_INIT
|
||||||
@ -656,6 +679,25 @@ class Session(object):
|
|||||||
"""Log, as error, status of requests.Response from CAPI request."""
|
"""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}')
|
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
|
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
|
'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.
|
# Map Coriolis's names to names displayed in the in-game shipyard.
|
||||||
coriolis_ship_map = {
|
coriolis_ship_map = {
|
||||||
'Cobra Mk III': 'Cobra MkIII',
|
'Cobra Mk III': 'Cobra MkIII',
|
||||||
|
76
monitor.py
76
monitor.py
@ -68,7 +68,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
|||||||
_RE_CANONICALISE = re.compile(r'\$(.+)_name;')
|
_RE_CANONICALISE = re.compile(r'\$(.+)_name;')
|
||||||
_RE_CATEGORY = re.compile(r'\$MICRORESOURCE_CATEGORY_(.+);')
|
_RE_CATEGORY = re.compile(r'\$MICRORESOURCE_CATEGORY_(.+);')
|
||||||
_RE_LOGFILE = re.compile(r'^Journal(Alpha|Beta)?\.[0-9]{12}\.[0-9]{2}\.log$')
|
_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:
|
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
|
# TODO(A_D): A bunch of these should be switched to default values (eg '' for strings) and no longer be Optional
|
||||||
@ -150,6 +150,10 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
|||||||
'Item': defaultdict(int), # BackPack Items
|
'Item': defaultdict(int), # BackPack Items
|
||||||
'Data': defaultdict(int), # Backpack Data
|
'Data': defaultdict(int), # Backpack Data
|
||||||
},
|
},
|
||||||
|
'SuitCurrent': None,
|
||||||
|
'Suits': None,
|
||||||
|
'SuitLoadoutCurrent': None,
|
||||||
|
'SuitLoadouts': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001
|
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:
|
if self.state['BackPack'][c][m] < 0:
|
||||||
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':
|
elif event_type == 'NavRoute':
|
||||||
# Added in ED 3.7 - multi-hop route details in NavRoute.json
|
# Added in ED 3.7 - multi-hop route details in NavRoute.json
|
||||||
with open(join(self.currentdir, 'NavRoute.json'), 'rb') as rf: # type: ignore
|
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)
|
logger.debug(f'Invalid journal entry:\n{line!r}\n', exc_info=ex)
|
||||||
return {'event': None}
|
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:
|
def canonicalise(self, item: Optional[str]) -> str:
|
||||||
"""
|
"""
|
||||||
Produce canonical name for a ship module.
|
Produce canonical name for a ship module.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user