mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-24 12:40:52 +03:00
Merge branch 'stable' into releases
This commit is contained in:
commit
1e79e59ba9
70
ChangeLog.md
70
ChangeLog.md
@ -1,6 +1,76 @@
|
||||
This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first).
|
||||
---
|
||||
|
||||
* We now test against, and package with, Python 3.9.5.
|
||||
|
||||
**As a consequence of this we no longer support Windows 7.
|
||||
This is due to
|
||||
[Python 3.9.x itself not supporting Windows 7](https://www.python.org/downloads/windows/).
|
||||
The application (both EDMarketConnector.exe and EDMC.exe) will crash on
|
||||
startup due to a missing DLL.**
|
||||
|
||||
This should have no other impact on users or plugin developers, other
|
||||
than the latter now being free to use features that were introduced since the
|
||||
Python 3.7 series.
|
||||
|
||||
Developers can check the contents of the `.python-version` file
|
||||
in the source (it's not distributed with the Windows installer) for the
|
||||
currently used version in a given branch.
|
||||
|
||||
Release 5.0.2
|
||||
===
|
||||
|
||||
This release is primarily aimed at getting the UI "`Suit: ...`" line working
|
||||
properly.
|
||||
|
||||
* The "`Suit: ...`" UI line should now function as best it can given the
|
||||
available data from the game. It should not appear if you have launched
|
||||
the Horizons version of the game, even if your account has Odyssey
|
||||
enabled. You might see "`<Unknown>`" as the text when this application
|
||||
does not yet have the required data.
|
||||
|
||||
* Changed the less than obvious "`unable to get endpoint: /profile`" error
|
||||
message to "`Frontier CAPI query failure: /profile`", and similarly for the
|
||||
other CAPI endpoints we attempt to access. This new form is potentially
|
||||
translated, but translators need time to do that.
|
||||
|
||||
In addition the old message "`Received error {r.status_code} from server`"
|
||||
has been changed to "`Frontier CAPI server error: {r.status_code}`" and is
|
||||
potentially translated.
|
||||
|
||||
* The filenames used for 'Market data in CSV format file' will now be sane,
|
||||
and as they were before 5.0.0.
|
||||
|
||||
* Linux: 'Shipyard provider' will no longer default to showing 'False' if
|
||||
no specific provider has been selected.
|
||||
|
||||
Plugin Developers
|
||||
---
|
||||
|
||||
* Extra `Flagse` values added in the live release of Odyssey have been added to
|
||||
`edmc_data.py`.
|
||||
|
||||
* Odyssey 'BackPack' values should now track better, but might still not be
|
||||
perfect due to Journal bugs/shortcomings.
|
||||
|
||||
* `state` passed to `journal_entry()` now has a `BackpackJSON` (note the case)
|
||||
member which is a copy of the data from the `Backpack.json` (yes, that's
|
||||
currently the correct case) file that is written when there's a `BackPack`
|
||||
(guess what, yes, that is currently the correct case) event written to
|
||||
the Journal.
|
||||
|
||||
* `state['Credits']` tracking is almost certainly not perfect. We're
|
||||
accounting for the credits component of `SuitUpgrade` now, but there
|
||||
might be other such we've yet accounted for.
|
||||
|
||||
* `state['Suits']` and associated other keys should now be tracking from
|
||||
Journal events, where possible, as well as CAPI data.
|
||||
|
||||
* There is a section in PLUGINS.md about how to package an extra Python
|
||||
module with your plugin. Note the new caveat in
|
||||
[PLUGINS.md:Avoiding-pitfalls](./PLUGINS.md#avoiding-potential-pitfalls)
|
||||
about the name of your plugin's directory.
|
||||
|
||||
Release 5.0.1
|
||||
===
|
||||
|
||||
|
@ -620,6 +620,11 @@ class AppWindow(object):
|
||||
|
||||
def update_suit_text(self) -> None:
|
||||
"""Update the suit text for current type and loadout."""
|
||||
if not monitor.state['Odyssey']:
|
||||
# Odyssey not detected, no text should be set so it will hide
|
||||
self.suit['text'] = ''
|
||||
return
|
||||
|
||||
if (suit := monitor.state.get('SuitCurrent')) is None:
|
||||
self.suit['text'] = f'<{_("Unknown")}>'
|
||||
return
|
||||
@ -633,6 +638,14 @@ class AppWindow(object):
|
||||
loadout_name = suitloadout['name']
|
||||
self.suit['text'] = f'{suitname} ({loadout_name})'
|
||||
|
||||
def suit_show_if_set(self) -> None:
|
||||
"""Show UI Suit row if we have data, else hide."""
|
||||
if self.suit['text'] != '':
|
||||
self.toggle_suit_row(visible=True)
|
||||
|
||||
else:
|
||||
self.toggle_suit_row(visible=False)
|
||||
|
||||
def toggle_suit_row(self, visible: Optional[bool] = None) -> None:
|
||||
"""
|
||||
Toggle the visibility of the 'Suit' row.
|
||||
@ -922,10 +935,7 @@ class AppWindow(object):
|
||||
|
||||
self.suit['text'] = f'{suitname} ({loadout_name})'
|
||||
|
||||
self.toggle_suit_row(visible=True)
|
||||
|
||||
else:
|
||||
self.toggle_suit_row(visible=False)
|
||||
self.suit_show_if_set()
|
||||
|
||||
if data['commander'].get('credits') is not None:
|
||||
monitor.state['Credits'] = data['commander']['credits']
|
||||
@ -1052,6 +1062,7 @@ class AppWindow(object):
|
||||
self.cmdr['text'] += ' (beta)'
|
||||
|
||||
self.update_suit_text()
|
||||
self.suit_show_if_set()
|
||||
|
||||
self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED) # Copy
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Chyba: Frontier CAPI neodpovídá";
|
||||
|
||||
/* Language name */
|
||||
"!Language" = "Čeština";
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Chyba: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Chyba: Frontier CAPI neodpovídá";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Chyba: Server Frontieru nejede";
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Fehler: Frontier CAPI antwortet nicht";
|
||||
|
||||
/* Language name */
|
||||
"!Language" = "Deutsch";
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Fehler: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Fehler: Frontier CAPI antwortet nicht";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Fehler: Frontier-Server ist offline";
|
||||
|
||||
|
@ -230,6 +230,12 @@
|
||||
/* Section heading in settings. [prefs.py] */
|
||||
"File location" = "File location";
|
||||
|
||||
/* Failures to access Frontier CAPI endpoints [companion.py] */
|
||||
"Frontier CAPI query failure" = "Frontier CAPI query failure";
|
||||
|
||||
/* Server errors from Frontier CAPI server [companion.py] */
|
||||
"Frontier CAPI server error" = "Frontier CAPI server error";
|
||||
|
||||
/* CQC rank. [stats.py] */
|
||||
"Gladiator" = "Gladiator";
|
||||
|
||||
|
@ -124,6 +124,15 @@
|
||||
/* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */
|
||||
"E:D journal file location" = "Localización del archivo de Journal de E:D";
|
||||
|
||||
/* When EDDB functionality has been killswitched. [plugins/eddb.py] */
|
||||
"EDDB Journal processing disabled. See Log." = "Procesamiento de Journal para EDDB desactivado. Ver Log.";
|
||||
|
||||
/* When EDDN journal handling has been killswitched [plugins/eddn.py] */
|
||||
"EDDN journal handler disabled. See Log." = "Manejo de Journal para EDDN desactivado. Ver Log.";
|
||||
|
||||
/* When EDSM functionality has been killswitched [plugins/edsm.py] */
|
||||
"EDSM Handler disabled. See Log." = "Manejo de Journal para EDSM desactivado. Ver Log.";
|
||||
|
||||
/* Empire rank. [stats.py] */
|
||||
"Earl" = "Conde Palatino";
|
||||
|
||||
@ -173,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Error: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Error: El CAPI de Frontier no respondió";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Error: El servidor de Frontier está caído";
|
||||
|
||||
@ -251,6 +263,9 @@
|
||||
/* Section heading in settings. [inara.py] */
|
||||
"Inara credentials" = "Credenciales de Inara";
|
||||
|
||||
/* When Inara support has been killswitched [plugins/inara.py] */
|
||||
"Inara disabled. See Log." = "Inara desactivado. Ver Log.";
|
||||
|
||||
/* Settings>Plugins>Information on migrating plugins [prefs.py] */
|
||||
"Information on migrating plugins" = "Información sobre migración de plugins";
|
||||
|
||||
@ -506,6 +521,9 @@
|
||||
/* Menu item. [EDMarketConnector.py] */
|
||||
"Status" = "Estado";
|
||||
|
||||
/* 'Suit' label in main UI' [EDMarketConnector.py] */
|
||||
"Suit" = "Traje";
|
||||
|
||||
/* Explorer rank. [stats.py] */
|
||||
"Surveyor" = "Topógrafo";
|
||||
|
||||
|
@ -124,6 +124,15 @@
|
||||
/* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */
|
||||
"E:D journal file location" = "E:Dゲームクライアントのジャーナルファイル出力先";
|
||||
|
||||
/* When EDDB functionality has been killswitched. [plugins/eddb.py] */
|
||||
"EDDB Journal processing disabled. See Log." = "EDDBジャーナル処理が無効です。ログを確認してください。";
|
||||
|
||||
/* When EDDN journal handling has been killswitched [plugins/eddn.py] */
|
||||
"EDDN journal handler disabled. See Log." = "EDDNジャーナルハンドラーが無効です。ログを確認してください。";
|
||||
|
||||
/* When EDSM functionality has been killswitched [plugins/edsm.py] */
|
||||
"EDSM Handler disabled. See Log." = "EDSMハンドラーが無効です。ログを確認してください。";
|
||||
|
||||
/* Empire rank. [stats.py] */
|
||||
"Earl" = "Earl";
|
||||
|
||||
@ -173,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "エラー: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "エラー: Frontier CAPIからの応答がありません";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "エラー: Frontier社のサーバがダウンしています";
|
||||
|
||||
@ -218,6 +230,12 @@
|
||||
/* Section heading in settings. [prefs.py] */
|
||||
"File location" = "ファイルの出力先";
|
||||
|
||||
/* Failures to access Frontier CAPI endpoints [companion.py] */
|
||||
"Frontier CAPI query failure" = "Frontier CAPIクエリ失敗";
|
||||
|
||||
/* Server errors from Frontier CAPI server [companion.py] */
|
||||
"Frontier CAPI server error" = "Frontier CAPIサーバエラー";
|
||||
|
||||
/* CQC rank. [stats.py] */
|
||||
"Gladiator" = "Gladiator";
|
||||
|
||||
@ -251,6 +269,9 @@
|
||||
/* Section heading in settings. [inara.py] */
|
||||
"Inara credentials" = "Inara認証情報";
|
||||
|
||||
/* When Inara support has been killswitched [plugins/inara.py] */
|
||||
"Inara disabled. See Log." = "Inaraが無効です。ログを確認してください。";
|
||||
|
||||
/* Settings>Plugins>Information on migrating plugins [prefs.py] */
|
||||
"Information on migrating plugins" = "プラグインの移行に関する情報";
|
||||
|
||||
|
@ -124,6 +124,15 @@
|
||||
/* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */
|
||||
"E:D journal file location" = "E:D 저널(journal) 파일 위치";
|
||||
|
||||
/* When EDDB functionality has been killswitched. [plugins/eddb.py] */
|
||||
"EDDB Journal processing disabled. See Log." = "EDDB 저널 처리 비활성화됨. 로그 참고 바람.";
|
||||
|
||||
/* When EDDN journal handling has been killswitched [plugins/eddn.py] */
|
||||
"EDDN journal handler disabled. See Log." = "EDDN 저널 처리 비활성화됨. 로그 참고 바람.";
|
||||
|
||||
/* When EDSM functionality has been killswitched [plugins/edsm.py] */
|
||||
"EDSM Handler disabled. See Log." = "EDSM Handler 비활성화됨. 로그 참고 바람.";
|
||||
|
||||
/* Empire rank. [stats.py] */
|
||||
"Earl" = "Earl";
|
||||
|
||||
@ -173,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "오류: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "오류: Frontier CAPI가 응답이 없음";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "오류: Frontier 서버 CAPI 연결 불가";
|
||||
|
||||
@ -251,6 +263,9 @@
|
||||
/* Section heading in settings. [inara.py] */
|
||||
"Inara credentials" = "Inara 계정 정보";
|
||||
|
||||
/* When Inara support has been killswitched [plugins/inara.py] */
|
||||
"Inara disabled. See Log." = "Inara 비활성화됨. 로그 참고 바람.";
|
||||
|
||||
/* Settings>Plugins>Information on migrating plugins [prefs.py] */
|
||||
"Information on migrating plugins" = "플러그인 위치 변경에 대한 정보";
|
||||
|
||||
|
@ -124,6 +124,15 @@
|
||||
/* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */
|
||||
"E:D journal file location" = "E:D Lokacja pliku dziennika";
|
||||
|
||||
/* When EDDB functionality has been killswitched. [plugins/eddb.py] */
|
||||
"EDDB Journal processing disabled. See Log." = "Wyłączone przetwarzanie dziennika EDDB. Sprawdź logi.";
|
||||
|
||||
/* When EDDN journal handling has been killswitched [plugins/eddn.py] */
|
||||
"EDDN journal handler disabled. See Log." = "Wyłączona obsługa dziennika EDDB. Sprawdź logi.";
|
||||
|
||||
/* When EDSM functionality has been killswitched [plugins/edsm.py] */
|
||||
"EDSM Handler disabled. See Log." = "Wyłączona obsługa EDDB. Sprawdź logi.";
|
||||
|
||||
/* Empire rank. [stats.py] */
|
||||
"Earl" = "Earl";
|
||||
|
||||
@ -173,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Błąd: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Błąd: Brak odpowiedzi od CAPI Frontier";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Błąd: Serwer Frontiera nie odpowiada";
|
||||
|
||||
@ -251,6 +263,9 @@
|
||||
/* Section heading in settings. [inara.py] */
|
||||
"Inara credentials" = "Inara - uprawnienia";
|
||||
|
||||
/* When Inara support has been killswitched [plugins/inara.py] */
|
||||
"Inara disabled. See Log." = "Inara jest wyłączona. Sprawdź logi.";
|
||||
|
||||
/* Settings>Plugins>Information on migrating plugins [prefs.py] */
|
||||
"Information on migrating plugins" = "Informacja o migracji pluginów";
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Erro: o CAPI da Frontier não respondeu";
|
||||
|
||||
/* Language name */
|
||||
"!Language" = "Português (Brasil)";
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Erro: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Erro: o CAPI da Frontier não respondeu";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Erro: O servidor da Frontier está offline";
|
||||
|
||||
@ -230,6 +230,12 @@
|
||||
/* Section heading in settings. [prefs.py] */
|
||||
"File location" = "Localização do Arquivo";
|
||||
|
||||
/* Failures to access Frontier CAPI endpoints [companion.py] */
|
||||
"Frontier CAPI query failure" = "Falha na requisição ao CAPI da Frontier";
|
||||
|
||||
/* Server errors from Frontier CAPI server [companion.py] */
|
||||
"Frontier CAPI server error" = "Erro de servidor do CAPI da Frontier";
|
||||
|
||||
/* CQC rank. [stats.py] */
|
||||
"Gladiator" = "Gladiador";
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Erro: CAPI da Frontier não respondeu.";
|
||||
|
||||
/* Language name */
|
||||
"!Language" = "Português (Portugal)";
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Erro: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Erro: CAPI da Frontier não respondeu.";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Erro: O servidor da Frontier está offline";
|
||||
|
||||
@ -230,6 +230,12 @@
|
||||
/* Section heading in settings. [prefs.py] */
|
||||
"File location" = "Localização do Ficheiro";
|
||||
|
||||
/* Failures to access Frontier CAPI endpoints [companion.py] */
|
||||
"Frontier CAPI query failure" = "Falha ao consultar a CAPI da Frontier.";
|
||||
|
||||
/* Server errors from Frontier CAPI server [companion.py] */
|
||||
"Frontier CAPI server error" = "Erro do servidor CAPI da Frontier.";
|
||||
|
||||
/* CQC rank. [stats.py] */
|
||||
"Gladiator" = "Gladiador";
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Ошибка: CAPI Frontier не отвечает";
|
||||
|
||||
/* Language name */
|
||||
"!Language" = "Русский";
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Ошибка: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Ошибка: CAPI Frontier не отвечает";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Ошибка: сервера Frontier недоступны";
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio";
|
||||
|
||||
/* Language name */
|
||||
"!Language" = "Srpski (Latinica, Bosna i Hercegovina)";
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Greška: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Greška: Frontier server je nedostupan";
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio";
|
||||
|
||||
/* Language name */
|
||||
"!Language" = "Srpski (Latinica)";
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
"Error: EDSM {MSG}" = "Greška: EDSM {MSG}";
|
||||
|
||||
|
||||
/* Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio";
|
||||
|
||||
/* OLD: Raised when cannot contact the Companion API server. [companion.py] */
|
||||
"Error: Frontier server is down" = "Greška: Frontier server je nedostupan";
|
||||
|
||||
|
89
PLUGINS.md
89
PLUGINS.md
@ -270,6 +270,11 @@ There are a number of things that your code should either do or avoiding
|
||||
doing so as to play nicely with the core EDMC code and not risk causing
|
||||
application crashes or hangs.
|
||||
|
||||
### Be careful about the name of your plugin directory
|
||||
|
||||
You might want your plugin directory name to be usable in import statements.
|
||||
See the section on [packaging extra modules](#your-plugin-directory-name-must-be-importable).
|
||||
|
||||
### Use a thread for long-running code
|
||||
|
||||
By default, your plugin code will be running in the main thread. So, if you
|
||||
@ -594,6 +599,7 @@ Content of `state` (updated to the current journal entry):
|
||||
| `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. |
|
||||
| `BackpackJSON` | `dict` | Content of Backpack.json as of last read. |
|
||||
| `SuitCurrent` | `dict` | CAPI-returned data of currently worn suit. NB: May be `None` if no data. |
|
||||
| `Suits` | `dict`[1] | 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. |
|
||||
@ -924,12 +930,91 @@ plugin's folder:
|
||||
→ Compressed (zipped) folder.
|
||||
- Mac: In Finder right click on your plugin's folder and choose Compress.
|
||||
|
||||
If there are any external dependencies then include them in the plugin's
|
||||
folder.
|
||||
If there are any external dependencies then
|
||||
[include them](#packaging-extra-modules) in the plugin's folder.
|
||||
|
||||
Optionally, for tidiness delete any `.pyc` and `.pyo` files in the archive, as
|
||||
well as the `__pycache__` directory.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Packaging extra modules
|
||||
EDMarketConnector's Windows installs only package a minimal set of modules.
|
||||
All of the 'stdlib' of Python is provided, plus any modules the core
|
||||
application code uses and a small number of additional modules for the
|
||||
use of plugins. See
|
||||
[Plugins:Available imports](https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#available-imports)
|
||||
for a list.
|
||||
|
||||
As such if your plugin requires additional modules you will need to package
|
||||
them with your plugin. There is no general Python interpreter in which to
|
||||
rely on [pip](https://pypi.org/project/pip/) to install them.
|
||||
|
||||
### Environment
|
||||
|
||||
It will be easier of you are using a Python
|
||||
[virtual environment](https://docs.python.org/3/library/venv.html) for
|
||||
actually testing the plugin. This is so that you can be sure it is working
|
||||
*because* you have copied all the correct Python modules inside your
|
||||
plugin, and not because they are installed within the Python site-packages
|
||||
in some applicable location (system level or user level).
|
||||
|
||||
So, setup a virtual environment to use when running EDMarketConnector
|
||||
code to test your plugin, and use the 'system' non-virtual Python to
|
||||
install modules in order to have somewhere to copy them from.
|
||||
|
||||
NB: If you use PyCharm it's possible to have it do the work of
|
||||
[creating a virtual environment](https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html)
|
||||
for your project.
|
||||
|
||||
### Install the modules for the system Python
|
||||
Technically you could also do this within an additional virtual environment.
|
||||
If they were in your plugin testing virtual environment then you can't be
|
||||
sure you have all the necessary files copied into your plugin so it will
|
||||
work within a vanilla Windows EDMarketConnector install.
|
||||
|
||||
We'll use `xml_dataclasses` for this example.
|
||||
|
||||
pip install xml_dataclasses
|
||||
|
||||
### Copy the module files into your plugin directory
|
||||
|
||||
1. Assuming it's a 'simple' module with no caveats, now we copy:
|
||||
1. `pip show xml_dataclasses` - `Location` is where it was installed.
|
||||
1. If you have a POSIX-compliant command-line environment:
|
||||
|
||||
cp -pr <Location> <plugin_dir>
|
||||
|
||||
or just use Windows File Explorer, or other GUI means, to copy.
|
||||
|
||||
### Your plugin directory name **must** be importable
|
||||
You're going to have to refer to your plugin directory in order to import
|
||||
anything within it. This means it should be compatible with such.
|
||||
|
||||
1. Do **not** use hyphens (`-`) as word separators, or full-stops (`.`).
|
||||
1. You can use underscore (`_`) as a word separator.
|
||||
|
||||
So:
|
||||
|
||||
- `EDMC-My-Plugin` **BAD**.
|
||||
- `EDMC.My.Plugin` **BAD**.
|
||||
- `EDMC_My_Plugin` **GOOD**.
|
||||
|
||||
NB: No, you can't use `from . import xml_dataclasses` because the way
|
||||
EDMarketConnector:plug.py loads 'found' plugins prevents this from working.
|
||||
|
||||
### Test the module import
|
||||
|
||||
Add an import of this module to your plugin code:
|
||||
|
||||
from EDMC_My_Plugin import xml_dataclasses
|
||||
|
||||
If you're lucky you won't have the "surprise!" of learning your chosen
|
||||
extra module itself requires other modules. If you are gifted such a surprise
|
||||
then you will need to repeat the [Copy](#copy-the-module-files-into-your-plugin-directory)
|
||||
step for the extra module(s) until it works.
|
||||
|
||||
---
|
||||
|
||||
## Disable a plugin
|
||||
|
@ -24,9 +24,9 @@ def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None:
|
||||
querytime = config.get_int('querytime', default=int(time.time()))
|
||||
|
||||
if not filename:
|
||||
filename_system = data['lastSystem']['name'].strip(),
|
||||
filename_starport = data['lastStarport']['name'].strip(),
|
||||
filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)),
|
||||
filename_system = data['lastSystem']['name'].strip()
|
||||
filename_starport = data['lastStarport']['name'].strip()
|
||||
filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))
|
||||
filename_kind = 'csv'
|
||||
filename = f'{filename_system}.{filename_starport}.{filename_time}.{filename_kind}'
|
||||
filename = join(config.get_str('outdir'), filename)
|
||||
|
@ -541,7 +541,7 @@ class Session(object):
|
||||
|
||||
except Exception as e:
|
||||
logger.debug('Attempting GET', exc_info=e)
|
||||
raise ServerError(f'unable to get endpoint {endpoint}') from e
|
||||
raise ServerError(f'{_("Frontier CAPI query failure")}: {endpoint}') from e
|
||||
|
||||
if r.url.startswith(SERVER_AUTH):
|
||||
logger.info('Redirected back to Auth Server')
|
||||
@ -556,7 +556,7 @@ class Session(object):
|
||||
# Server error. Typically 500 "Internal Server Error" if server is down
|
||||
logger.debug('500 status back from CAPI')
|
||||
self.dump(r)
|
||||
raise ServerError(f'Received error {r.status_code} from server')
|
||||
raise ServerError(f'{_("Frontier CAPI server error")}: {r.status_code}')
|
||||
|
||||
try:
|
||||
r.raise_for_status() # Typically 403 "Forbidden" on token expiry
|
||||
|
16
config.py
16
config.py
@ -33,7 +33,7 @@ appcmdname = 'EDMC'
|
||||
# <https://semver.org/#semantic-versioning-specification-semver>
|
||||
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
||||
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
||||
_static_appversion = '5.0.1'
|
||||
_static_appversion = '5.0.2'
|
||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD'
|
||||
|
||||
update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml'
|
||||
@ -96,9 +96,21 @@ def git_shorthash_from_head() -> str:
|
||||
"""
|
||||
Determine short hash for current git HEAD.
|
||||
|
||||
Includes -DIRTY if any changes have been made from HEAD
|
||||
|
||||
:return: str - None if we couldn't determine the short hash.
|
||||
"""
|
||||
shorthash: str = None # type: ignore
|
||||
dirty = False
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
result = subprocess.run('git diff --stat HEAD'.split(), capture_output=True)
|
||||
if len(result.stdout) > 0:
|
||||
dirty = True
|
||||
|
||||
if len(result.stderr) > 0:
|
||||
logger.warning(f'Data from git on stderr:\n{str(result.stderr)}')
|
||||
|
||||
try:
|
||||
git_cmd = subprocess.Popen('git rev-parse --short HEAD'.split(),
|
||||
stdout=subprocess.PIPE,
|
||||
@ -115,7 +127,7 @@ def git_shorthash_from_head() -> str:
|
||||
logger.error(f"'{shorthash}' doesn't look like a valid git short hash, forcing to None")
|
||||
shorthash = None # type: ignore
|
||||
|
||||
return shorthash
|
||||
return shorthash + ('-WORKING-DIR-IS-DIRTY' if dirty else '')
|
||||
|
||||
|
||||
def appversion() -> semantic_version.Version:
|
||||
|
@ -429,6 +429,11 @@ Flags2Cold = 1 << 8
|
||||
Flags2Hot = 1 << 9
|
||||
Flags2VeryCold = 1 << 10
|
||||
Flags2VeryHot = 1 << 11
|
||||
Flags2GlideMode = 1 << 12
|
||||
Flags2OnFootInHangar = 1 << 13
|
||||
Flags2OnFootSocialSpace = 1 << 14
|
||||
Flags2OnFootExterior = 1 << 15
|
||||
Flags2BreathableAtmosphere = 1 << 16
|
||||
|
||||
# Dashboard GuiFocus constants
|
||||
GuiFocusNoFocus = 0
|
||||
|
@ -32,6 +32,14 @@ def export(data, filename=None):
|
||||
querytime = config.get_int('querytime', default=int(time.time()))
|
||||
|
||||
# Write
|
||||
#
|
||||
# When this is refactored into multi-line CHECK IT WORKS, avoiding the
|
||||
# brainfart we had with dangling commas in commodity.py:export() !!!
|
||||
#
|
||||
filename = join(config.get_str('outdir'), '%s.%s.txt' % (ship, time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))))
|
||||
#
|
||||
# When this is refactored into multi-line CHECK IT WORKS, avoiding the
|
||||
# brainfart we had with dangling commas in commodity.py:export() !!!
|
||||
#
|
||||
with open(filename, 'wt') as h:
|
||||
h.write(string)
|
||||
|
352
monitor.py
352
monitor.py
@ -151,6 +151,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
'Item': defaultdict(int), # BackPack Items
|
||||
'Data': defaultdict(int), # Backpack Data
|
||||
},
|
||||
'BackpackJSON': None, # Raw JSON from `Backpack.json` file, if available
|
||||
'SuitCurrent': None,
|
||||
'Suits': {},
|
||||
'SuitLoadoutCurrent': None,
|
||||
@ -762,20 +763,24 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
self.state[event_type] = payload
|
||||
|
||||
elif event_type == 'EngineerProgress':
|
||||
engineers = self.state['Engineers']
|
||||
if 'Engineers' in entry: # Startup summary
|
||||
self.state['Engineers'] = {
|
||||
e['Engineer']: ((e['Rank'], e.get('RankProgress', 0)) if 'Rank' in e else e['Progress'])
|
||||
for e in entry['Engineers']
|
||||
}
|
||||
# Sanity check - at least once the 'Engineer' (name) was missing from this in early
|
||||
# Odyssey 4.0.0.100. Might only have been a server issue causing incomplete data.
|
||||
|
||||
else: # Promotion
|
||||
engineer = entry['Engineer']
|
||||
if 'Rank' in entry:
|
||||
engineers[engineer] = (entry['Rank'], entry.get('RankProgress', 0))
|
||||
if self.event_valid_engineerprogress(entry):
|
||||
engineers = self.state['Engineers']
|
||||
if 'Engineers' in entry: # Startup summary
|
||||
self.state['Engineers'] = {
|
||||
e['Engineer']: ((e['Rank'], e.get('RankProgress', 0)) if 'Rank' in e else e['Progress'])
|
||||
for e in entry['Engineers']
|
||||
}
|
||||
|
||||
else:
|
||||
engineers[engineer] = entry['Progress']
|
||||
else: # Promotion
|
||||
engineer = entry['Engineer']
|
||||
if 'Rank' in entry:
|
||||
engineers[engineer] = (entry['Rank'], entry.get('RankProgress', 0))
|
||||
|
||||
else:
|
||||
engineers[engineer] = entry['Progress']
|
||||
|
||||
elif event_type == 'Cargo' and entry.get('Vessel') == 'Ship':
|
||||
self.state['Cargo'] = defaultdict(int)
|
||||
@ -835,6 +840,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
{self.canonicalise(x['Name']): x['Count'] for x in clean_data}
|
||||
)
|
||||
|
||||
# Journal v31 implies this was removed before Odyssey launch
|
||||
elif event_type == 'BackPackMaterials':
|
||||
# alpha4 -
|
||||
# Lists the contents of the backpack, eg when disembarking from ship
|
||||
@ -865,6 +871,80 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
{self.canonicalise(x['Name']): x['Count'] for x in clean_data}
|
||||
)
|
||||
|
||||
elif event_type == 'BackPack':
|
||||
# TODO: v31 doc says this is`backpack.json` ... but Howard Chalkley
|
||||
# said it's `Backpack.json`
|
||||
with open(join(self.currentdir, 'Backpack.json'), 'rb') as backpack: # type: ignore
|
||||
try:
|
||||
# Preserve property order because why not?
|
||||
entry = json.load(backpack, object_pairs_hook=OrderedDict)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
logger.exception('Failed decoding Backpack.json', exc_info=True)
|
||||
|
||||
else:
|
||||
# Store in monitor.state
|
||||
self.state['BackpackJSON'] = entry
|
||||
|
||||
# Assume this reflects the current state when written
|
||||
self.state['BackPack']['Component'] = defaultdict(int)
|
||||
self.state['BackPack']['Consumable'] = defaultdict(int)
|
||||
self.state['BackPack']['Item'] = defaultdict(int)
|
||||
self.state['BackPack']['Data'] = defaultdict(int)
|
||||
|
||||
clean_components = self.coalesce_cargo(entry['Components'])
|
||||
self.state['BackPack']['Component'].update(
|
||||
{self.canonicalise(x['Name']): x['Count'] for x in clean_components}
|
||||
)
|
||||
|
||||
clean_consumables = self.coalesce_cargo(entry['Consumables'])
|
||||
self.state['BackPack']['Consumable'].update(
|
||||
{self.canonicalise(x['Name']): x['Count'] for x in clean_consumables}
|
||||
)
|
||||
|
||||
clean_items = self.coalesce_cargo(entry['Items'])
|
||||
self.state['BackPack']['Item'].update(
|
||||
{self.canonicalise(x['Name']): x['Count'] for x in clean_items}
|
||||
)
|
||||
|
||||
clean_data = self.coalesce_cargo(entry['Data'])
|
||||
self.state['BackPack']['Data'].update(
|
||||
{self.canonicalise(x['Name']): x['Count'] for x in clean_data}
|
||||
)
|
||||
|
||||
elif event_type == 'BackpackChange':
|
||||
# Changes to Odyssey Backpack contents *other* than from a Transfer
|
||||
# See TransferMicroResources event for that.
|
||||
|
||||
if entry.get('Added') is not None:
|
||||
changes = 'Added'
|
||||
|
||||
elif entry.get('Removed') is not None:
|
||||
changes = 'Removed'
|
||||
|
||||
else:
|
||||
logger.warning(f'BackpackChange with neither Added nor Removed: {entry=}')
|
||||
changes = ''
|
||||
|
||||
if changes != '':
|
||||
for c in entry[changes]:
|
||||
category = self.category(c['Type'])
|
||||
name = self.canonicalise(c['Name'])
|
||||
|
||||
if changes == 'Removed':
|
||||
self.state['BackPack'][category][name] -= c['Count']
|
||||
|
||||
elif changes == 'Added':
|
||||
self.state['BackPack'][category][name] += c['Count']
|
||||
|
||||
# Paranoia check to see if anything has gone negative.
|
||||
# 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
|
||||
|
||||
elif event_type == 'BuyMicroResources':
|
||||
# Buying from a Pioneer Supplies, goes directly to ShipLocker.
|
||||
# One event per Item, not an array.
|
||||
@ -948,80 +1028,70 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
self.state['BackPack'][entry['Type']][i] = 0
|
||||
|
||||
elif event_type == 'UseConsumable':
|
||||
# alpha4
|
||||
# When using an item from the player’s inventory (backpack)
|
||||
# TODO: XXX: From v31 doc
|
||||
# 12.2 BackpackChange
|
||||
# This is written when there is any change to the contents of the
|
||||
# suit backpack – note this can be written at the same time as other
|
||||
# events like UseConsumable
|
||||
|
||||
# In 4.0.0.100 it is observed that:
|
||||
#
|
||||
# Parameters:
|
||||
# • Name
|
||||
# • Type
|
||||
for c in self.state['BackPack']['Consumable']:
|
||||
if c == entry['Name']:
|
||||
self.state['BackPack']['Consumable'][c] -= 1
|
||||
# Paranoia in case we lost track
|
||||
if self.state['BackPack']['Consumable'][c] < 0:
|
||||
self.state['BackPack']['Consumable'][c] = 0
|
||||
# 1. Throw of any grenade type *only* causes a BackpackChange event, no
|
||||
# accompanying 'UseConsumable'.
|
||||
# 2. Using an Energy Cell causes both UseConsumable and BackpackChange,
|
||||
# in that order.
|
||||
# 3. Medkit acts the same as Energy Cell.
|
||||
#
|
||||
# Thus we'll just ignore 'UseConsumable' for now.
|
||||
# for c in self.state['BackPack']['Consumable']:
|
||||
# if c == entry['Name']:
|
||||
# self.state['BackPack']['Consumable'][c] -= 1
|
||||
# # Paranoia in case we lost track
|
||||
# if self.state['BackPack']['Consumable'][c] < 0:
|
||||
# self.state['BackPack']['Consumable'][c] = 0
|
||||
pass
|
||||
|
||||
# TODO:
|
||||
# <https://forums.frontier.co.uk/threads/575010/>
|
||||
# also there's one additional journal event that was missed out from
|
||||
# this version of the docs: "SuitLoadout": # when starting on foot, or
|
||||
# when disembarking from a ship, with the same info as found in "CreateSuitLoadout"
|
||||
elif event_type == 'SuitLoadout':
|
||||
suit_slotid, suitloadout_slotid = self.suitloadout_store_from_event(entry)
|
||||
if not self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid):
|
||||
logger.error(f"Event was: {entry}")
|
||||
|
||||
elif event_type == 'SwitchSuitLoadout':
|
||||
loadoutid = entry['LoadoutID']
|
||||
new_slot = self.suit_loadout_id_from_loadoutid(loadoutid)
|
||||
# If this application is run with the latest Journal showing such an event then we won't
|
||||
# yet have the CAPI data, so no idea about Suits or Loadouts.
|
||||
if self.state['Suits'] and self.state['SuitLoadouts']:
|
||||
try:
|
||||
self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][f'{new_slot}']
|
||||
|
||||
except KeyError:
|
||||
logger.debug(f"KeyError getting suit loadout after switch, bad slot: {new_slot} ({loadoutid})")
|
||||
self.state['SuitCurrent'] = None
|
||||
self.state['SuitLoadoutCurrent'] = None
|
||||
|
||||
else:
|
||||
try:
|
||||
new_suitid = self.state['SuitLoadoutCurrent']['suit']['suitId']
|
||||
|
||||
except KeyError:
|
||||
logger.debug(f"KeyError getting switched-to suit ID from slot {new_slot} ({loadoutid})")
|
||||
|
||||
else:
|
||||
try:
|
||||
self.state['SuitCurrent'] = self.state['Suits'][f'{new_suitid}']
|
||||
|
||||
except KeyError:
|
||||
logger.debug(f"KeyError getting switched-to suit from slot {new_slot} ({loadoutid}")
|
||||
# 4.0.0.101
|
||||
#
|
||||
# { "timestamp":"2021-05-21T10:39:43Z", "event":"SwitchSuitLoadout",
|
||||
# "SuitID":1700217809818876, "SuitName":"utilitysuit_class1",
|
||||
# "SuitName_Localised":"Maverick Suit", "LoadoutID":4293000002,
|
||||
# "LoadoutName":"K/P", "Modules":[ { "SlotName":"PrimaryWeapon1",
|
||||
# "SuitModuleID":1700217863661544,
|
||||
# "ModuleName":"wpn_m_assaultrifle_kinetic_fauto",
|
||||
# "ModuleName_Localised":"Karma AR-50" },
|
||||
# { "SlotName":"SecondaryWeapon", "SuitModuleID":1700216180036986,
|
||||
# "ModuleName":"wpn_s_pistol_plasma_charged",
|
||||
# "ModuleName_Localised":"Manticore Tormentor" } ] }
|
||||
#
|
||||
suit_slotid, suitloadout_slotid = self.suitloadout_store_from_event(entry)
|
||||
if not self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid):
|
||||
logger.error(f"Event was: {entry}")
|
||||
|
||||
elif event_type == 'CreateSuitLoadout':
|
||||
# We know we won't have data for this new one
|
||||
# Parameters:
|
||||
# • SuitID
|
||||
# • SuitName
|
||||
# • LoadoutID
|
||||
# • LoadoutName
|
||||
# alpha4:
|
||||
# { "timestamp":"2021-04-29T09:37:08Z", "event":"CreateSuitLoadout", "SuitID":1698364940285172,
|
||||
# "SuitName":"tacticalsuit_class1", "SuitName_Localised":"Dominator Suit", "LoadoutID":4293000001,
|
||||
# "LoadoutName":"Dom L/K/K", "Modules":[
|
||||
# {
|
||||
# "SlotName":"PrimaryWeapon1",
|
||||
# "SuitModuleID":1698364962722310,
|
||||
# "ModuleName":"wpn_m_assaultrifle_laser_fauto",
|
||||
# "ModuleName_Localised":"TK Aphelion"
|
||||
# },
|
||||
# { "SlotName":"PrimaryWeapon2",
|
||||
# "SuitModuleID":1698364956302993, "ModuleName":"wpn_m_assaultrifle_kinetic_fauto",
|
||||
# "ModuleName_Localised":"Karma AR-50" }, { "SlotName":"SecondaryWeapon",
|
||||
# "SuitModuleID":1698292655291850, "ModuleName":"wpn_s_pistol_kinetic_sauto",
|
||||
# "ModuleName_Localised":"Karma P-15" } ] }
|
||||
new_loadout = {
|
||||
'loadoutSlotId': self.suit_loadout_id_from_loadoutid(entry['LoadoutID']),
|
||||
'suit': {
|
||||
'name': entry['SuitName'],
|
||||
'locName': entry.get('SuitName_Localised', entry['SuitName']),
|
||||
'suitId': entry['SuitID'],
|
||||
},
|
||||
'name': entry['LoadoutName'],
|
||||
'slots': self.suit_loadout_slots_array_to_dict(entry['Modules']),
|
||||
}
|
||||
self.state['SuitLoadouts'][new_loadout['loadoutSlotId']] = new_loadout
|
||||
# 4.0.0.101
|
||||
#
|
||||
# { "timestamp":"2021-05-21T11:13:15Z", "event":"CreateSuitLoadout", "SuitID":1700216165682989,
|
||||
# "SuitName":"tacticalsuit_class1", "SuitName_Localised":"Dominator Suit", "LoadoutID":4293000004,
|
||||
# "LoadoutName":"P/P/K", "Modules":[ { "SlotName":"PrimaryWeapon1", "SuitModuleID":1700216182854765,
|
||||
# "ModuleName":"wpn_m_assaultrifle_plasma_fauto", "ModuleName_Localised":"Manticore Oppressor" },
|
||||
# { "SlotName":"PrimaryWeapon2", "SuitModuleID":1700216190363340,
|
||||
# "ModuleName":"wpn_m_shotgun_plasma_doublebarrel", "ModuleName_Localised":"Manticore Intimidator" },
|
||||
# { "SlotName":"SecondaryWeapon", "SuitModuleID":1700217869872834,
|
||||
# "ModuleName":"wpn_s_pistol_kinetic_sauto", "ModuleName_Localised":"Karma P-15" } ] }
|
||||
#
|
||||
_, _ = self.suitloadout_store_from_event(entry)
|
||||
|
||||
elif event_type == 'DeleteSuitLoadout':
|
||||
# alpha4:
|
||||
@ -1113,10 +1183,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
# • SuitID
|
||||
# • Class
|
||||
# • Cost
|
||||
# Update credits total ? It shouldn't even involve credits!
|
||||
# Actual alpha4 - need to grind mats
|
||||
# if self.state['Suits']:
|
||||
pass
|
||||
# TODO: Update self.state['Suits'] when we have an example to work from
|
||||
self.state['Credits'] -= entry.get('Cost', 0)
|
||||
|
||||
elif event_type == 'LoadoutEquipModule':
|
||||
# alpha4:
|
||||
@ -1513,6 +1581,120 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
|
||||
logger.debug(f'Invalid journal entry:\n{line!r}\n', exc_info=ex)
|
||||
return {'event': None}
|
||||
|
||||
def suitloadout_store_from_event(self, entry) -> Tuple[int, int]:
|
||||
"""
|
||||
Store Suit and SuitLoadout data from a journal event.
|
||||
|
||||
Also use set currently in-use instances of them as being as per this
|
||||
event.
|
||||
|
||||
:param entry: Journal entry - 'SwitchSuitLoadout' or 'SuitLoadout'
|
||||
:return Tuple[suit_slotid, suitloadout_slotid]: The IDs we set data for.
|
||||
"""
|
||||
suit_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID'])
|
||||
# Initial suit containing just the data that is then embedded in
|
||||
# the loadout
|
||||
new_suit = {
|
||||
'name': entry['SuitName'],
|
||||
'locName': entry.get('SuitName_Localised', entry['SuitName']),
|
||||
'suitId': entry['SuitID'],
|
||||
}
|
||||
# Make the new loadout, in the CAPI format
|
||||
new_loadout = {
|
||||
'loadoutSlotId': suit_slotid,
|
||||
'suit': new_suit,
|
||||
'name': entry['LoadoutName'],
|
||||
'slots': self.suit_loadout_slots_array_to_dict(
|
||||
entry['Modules']),
|
||||
}
|
||||
# Assign this loadout into our state
|
||||
self.state['SuitLoadouts'][f"{new_loadout['loadoutSlotId']}"] = new_loadout
|
||||
|
||||
# Now add in the extra fields for new_suit to be a 'full' Suit structure
|
||||
new_suit['id'] = None # Not available in 4.0.0.100 journal event
|
||||
new_suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI
|
||||
# Ensure new_suit is in self.state['Suits']
|
||||
self.state['Suits'][f"{suit_slotid}"] = new_suit
|
||||
|
||||
return suit_slotid, new_loadout['loadoutSlotId']
|
||||
|
||||
def suit_and_loadout_setcurrent(self, suit_slotid: int, suitloadout_slotid: int) -> bool:
|
||||
"""
|
||||
Set self.state for SuitCurrent and SuitLoadoutCurrent as requested.
|
||||
|
||||
If the specified slots are unknown we abort and return False, else
|
||||
return True.
|
||||
|
||||
:param suit_slotid: Numeric ID of the slot for the suit.
|
||||
: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"{suit_slotid}"
|
||||
str_suitloadoutid = f"{suitloadout_slotid}"
|
||||
|
||||
if (self.state['Suits'].get(str_suitid, False)
|
||||
and self.state['SuitLoadouts'].get(str_suitloadoutid, False)):
|
||||
self.state['SuitCurrent'] = self.state['Suits'][str_suitid]
|
||||
self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][str_suitloadoutid]
|
||||
return True
|
||||
|
||||
logger.error(f"Tried to set a suit and suitloadout where we didn't know about both: {suit_slotid=}, "
|
||||
f"{str_suitloadoutid=}")
|
||||
return False
|
||||
|
||||
# TODO: *This* will need refactoring and a proper validation infrastructure
|
||||
# designed for this in the future. This is a bandaid for a known issue.
|
||||
def event_valid_engineerprogress(self, entry) -> bool: # noqa: CCR001 C901
|
||||
"""
|
||||
Check an `EngineerProgress` Journal event for validity.
|
||||
|
||||
:param entry: Journal event dict
|
||||
:return: True if passes validation, else False.
|
||||
"""
|
||||
# The event should have at least one of thes
|
||||
if 'Engineers' not in entry and 'Progress' not in entry:
|
||||
logger.warning(f"EngineerProgress has neither 'Engineers' nor 'Progress': {entry=}")
|
||||
return False
|
||||
|
||||
# But not both of them
|
||||
if 'Engineers' in entry and 'Progress' in entry:
|
||||
logger.warning(f"EngineerProgress has BOTH 'Engineers' and 'Progress': {entry=}")
|
||||
return False
|
||||
|
||||
if 'Engineers' in entry:
|
||||
# 'Engineers' version should have a list as value
|
||||
if not isinstance(entry['Engineers'], list):
|
||||
logger.warning(f"EngineerProgress 'Engineers' is not a list: {entry=}")
|
||||
return False
|
||||
|
||||
# It should have at least one entry? This might still be valid ?
|
||||
if len(entry['Engineers']) < 1:
|
||||
logger.warning(f"EngineerProgress 'Engineers' list is empty ?: {entry=}")
|
||||
# TODO: As this might be valid, we might want to only log
|
||||
return False
|
||||
|
||||
# And that list should have all of these keys
|
||||
for e in entry['Engineers']:
|
||||
for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'):
|
||||
if f not in e:
|
||||
# For some Progress there's no Rank/RankProgress yet
|
||||
if f in ('Rank', 'RankProgress'):
|
||||
if (progress := e.get('Progress', None)) is not None:
|
||||
if progress in ('Invited', 'Known'):
|
||||
continue
|
||||
|
||||
logger.warning(f"Engineer entry without '{f}' key: {e=} in {entry=}")
|
||||
return False
|
||||
|
||||
if 'Progress' in entry:
|
||||
for e in entry['Engineers']:
|
||||
for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'):
|
||||
if f not in e:
|
||||
logger.warning(f"Engineer entry without '{f}' key: {e=} in {entry=}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def suit_loadout_id_from_loadoutid(self, journal_loadoutid: int) -> int:
|
||||
"""
|
||||
Determine the CAPI-oriented numeric slot id for a Suit Loadout.
|
||||
|
@ -432,6 +432,11 @@ def journal_entry(
|
||||
elif entry['event'] == 'NavBeaconScan':
|
||||
this.navbeaconscan = entry['NumBodies']
|
||||
|
||||
elif entry['event'] == 'BackPack':
|
||||
# Use the stored file contents, not the empty journal event
|
||||
if state['BackpackJSON']:
|
||||
entry = state['BackpackJSON']
|
||||
|
||||
# Send interesting events to EDSM
|
||||
if (
|
||||
config.get_int('edsm_out') and not is_beta and not this.multicrew and credentials(cmdr) and
|
||||
|
@ -449,14 +449,17 @@ def journal_entry( # noqa: C901, CCR001
|
||||
)
|
||||
|
||||
# Update location
|
||||
new_add_event(
|
||||
'setCommanderTravelLocation',
|
||||
entry['timestamp'],
|
||||
OrderedDict([
|
||||
('starsystemName', system),
|
||||
('stationName', station), # Can be None
|
||||
])
|
||||
)
|
||||
# Might not be available if this event is a 'StartUp' and we're replaying
|
||||
# a log.
|
||||
if system:
|
||||
new_add_event(
|
||||
'setCommanderTravelLocation',
|
||||
entry['timestamp'],
|
||||
OrderedDict([
|
||||
('starsystemName', system),
|
||||
('stationName', station), # Can be None
|
||||
])
|
||||
)
|
||||
|
||||
# Update ship
|
||||
if state['ShipID']: # Unknown if started in Fighter or SRV
|
||||
|
5
prefs.py
5
prefs.py
@ -535,10 +535,9 @@ class PreferencesDialog(tk.Toplevel):
|
||||
)
|
||||
|
||||
with row as cur_row:
|
||||
shipyard_provider = config.get_str('shipyard_provider')
|
||||
self.shipyard_provider = tk.StringVar(
|
||||
value=str(
|
||||
config.get_str('shipyard_provider') in plug.provides('shipyard_url')
|
||||
and config.get_str('shipyard_provider', default='EDSY'))
|
||||
value=str(shipyard_provider if shipyard_provider in plug.provides('shipyard_url') else 'EDSY')
|
||||
)
|
||||
# Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis
|
||||
nb.Label(config_frame, text=_('Shipyard')).grid(padx=self.PADX, pady=2*self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
@ -1,6 +1,6 @@
|
||||
certifi==2020.12.5
|
||||
requests==2.25.1
|
||||
watchdog==2.1.1
|
||||
watchdog==2.1.2
|
||||
# Commented out because this doesn't package well with py2exe
|
||||
# infi.systray==0.1.12; sys_platform == 'win32'
|
||||
# argh==0.26.2 watchdog dep
|
||||
|
8
td.py
8
td.py
@ -25,7 +25,15 @@ def export(data):
|
||||
|
||||
querytime = config.get_int('querytime', default=int(time.time()))
|
||||
|
||||
#
|
||||
# When this is refactored into multi-line CHECK IT WORKS, avoiding the
|
||||
# brainfart we had with dangling commas in commodity.py:export() !!!
|
||||
#
|
||||
filename = join(config.get_str('outdir'), '%s.%s.%s.prices' % (data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip(), time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))))
|
||||
#
|
||||
# When this is refactored into multi-line CHECK IT WORKS, avoiding the
|
||||
# brainfart we had with dangling commas in commodity.py:export() !!!
|
||||
#
|
||||
|
||||
timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.strptime(data['timestamp'], '%Y-%m-%dT%H:%M:%SZ'))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user