1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-06-09 11:52:27 +03:00

Merge pull request #1108 from EDCD/fix/1102/frontier-non-localised-suit-names

Suits: Ensure a succinct and sane name for Suits in UI
This commit is contained in:
Athanasius 2021-05-27 11:22:19 +01:00 committed by GitHub
commit aed1a22245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 178 additions and 33 deletions

View File

@ -629,7 +629,7 @@ class AppWindow(object):
self.suit['text'] = f'<{_("Unknown")}>' self.suit['text'] = f'<{_("Unknown")}>'
return return
suitname = suit['locName'] suitname = suit['edmcName']
if (suitloadout := monitor.state.get('SuitLoadoutCurrent')) is None: if (suitloadout := monitor.state.get('SuitLoadoutCurrent')) is None:
self.suit['text'] = '' self.suit['text'] = ''
@ -927,7 +927,7 @@ class AppWindow(object):
if monitor.state.get('SuitCurrent') is not None: if monitor.state.get('SuitCurrent') is not None:
if (loadout := data.get('loadout')) is not None: if (loadout := data.get('loadout')) is not None:
if (suit := loadout.get('suit')) is not None: if (suit := loadout.get('suit')) is not None:
if (suitname := suit.get('locName')) is not None: if (suitname := suit.get('edmcName')) is not None:
# We've been paranoid about loadout->suit->suitname, now just assume loadouts is there # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there
loadout_name = index_possibly_sparse_list( loadout_name = index_possibly_sparse_list(
data['loadouts'], loadout['loadoutSlotId'] data['loadouts'], loadout['loadoutSlotId']

View File

@ -566,6 +566,9 @@ Content of `state` (updated to the current journal entry):
| Field | Type | Description | | Field | Type | Description |
| :------------- | :-------------------------: | :-------------------------------------------------------------------------------------------------------------- | | :------------- | :-------------------------: | :-------------------------------------------------------------------------------------------------------------- |
| `GameLanguage` | `Optional[str]` | `language` value from `Fileheader` event. |
| `GameVersion` | `Optional[str]` | `version` value from `Fileheader` event. |
| `GameBuild` | `Optional[str]` | `build` value from `Fileheader` event. |
| `Captain` | `Optional[str]` | Name of the commander who's crew you're on, if any | | `Captain` | `Optional[str]` | Name of the commander who's crew you're on, if any |
| `Cargo` | `dict` | Current cargo. Note that this will be totals, and any mission specific duplicates will be counted together | | `Cargo` | `dict` | Current cargo. Note that this will be totals, and any mission specific duplicates will be counted together |
| `CargoJSON` | `dict` | content of cargo.json as of last read. | | `CargoJSON` | `dict` | content of cargo.json as of last read. |
@ -652,6 +655,11 @@ this is **NOT** the same as the return from
because CAPI data doesn't (didn't always?) have an indication of Horizons or because CAPI data doesn't (didn't always?) have an indication of Horizons or
not. not.
New in version 5.0.3:
The `Suits` members have an additional key:value pair `edmcName` which is our
preferred name for display on the UI, for the in-use game language.
##### Synthetic Events ##### Synthetic Events
A special "StartUp" entry is sent if EDMC is started while the game is already A special "StartUp" entry is sent if EDMC is started while the game is already

View File

@ -96,7 +96,7 @@ def git_shorthash_from_head() -> str:
""" """
Determine short hash for current git HEAD. Determine short hash for current git HEAD.
Includes -DIRTY if any changes have been made from HEAD Includes +DIRTY if any changes have been made from HEAD
:return: str - None if we couldn't determine the short hash. :return: str - None if we couldn't determine the short hash.
""" """
@ -122,7 +122,7 @@ def git_shorthash_from_head() -> str:
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
result = subprocess.run('git diff --stat HEAD'.split(), capture_output=True) result = subprocess.run('git diff --stat HEAD'.split(), capture_output=True)
if len(result.stdout) > 0: if len(result.stdout) > 0:
shorthash += '-WORKING-DIR-IS-DIRTY' shorthash += '+DIRTY'
if len(result.stderr) > 0: if len(result.stderr) > 0:
logger.warning(f'Data from git on stderr:\n{str(result.stderr)}') logger.warning(f'Data from git on stderr:\n{str(result.stderr)}')

View File

@ -495,3 +495,78 @@ ship_name_map = {
'viper_mkiv': 'Viper MkIV', 'viper_mkiv': 'Viper MkIV',
'vulture': 'Vulture', 'vulture': 'Vulture',
} }
# Odyssey Suit Names
edmc_suit_shortnames = {
'Flight Suit': 'Flight', # EN
'Artemis Suit': 'Artemis', # EN
'Dominator Suit': 'Dominator', # EN
'Maverick Suit': 'Maverick', # EN
'Flug-Anzug': 'Flug', # DE
'Artemis-Anzug': 'Artemis', # DE
'Dominator-Anzug': 'Dominator', # DE
'Maverick-Anzug': 'Maverick', # DE
'Traje de vuelo': 'de vuelo', # ES
'Traje Artemis': 'Artemis', # ES
'Traje Dominator': 'Dominator', # ES
'Traje Maverick': 'Maverick', # ES
'Combinaison de vol': 'de vol', # FR
'Combinaison Artemis': 'Artemis', # FR
'Combinaison Dominator': 'Dominator', # FR
'Combinaison Maverick': 'Maverick', # FR
'Traje voador': 'voador', # PT-BR
# These are duplicates of the ES ones, but kept here for clarity
# 'Traje Artemis': 'Artemis', # PT-BR
# 'Traje Dominator': 'Dominator', # PT-BR
# 'Traje Maverick': 'Maverick', # PT-BR
'Летный комбинезон': 'Летный', # RU
'Комбинезон Artemis': 'Artemis', # RU
'Комбинезон Dominator': 'Dominator', # RU
'Комбинезон Maverick': 'Maverick', # RU
}
edmc_suit_symbol_localised = {
# The key here should match what's seen in Fileheader 'language', but with
# any in-file `\\` already unescaped to a single `\`.
r'English\UK': {
'flightsuit': 'Flight Suit',
'explorationsuit': 'Artemis Suit',
'tacticalsuit': 'Dominator Suit',
'utilitysuit': 'Maverick Suit',
},
r'German\DE': {
'flightsuit': 'Flug-Anzug',
'explorationsuit': 'Artemis-Anzug',
'tacticalsuit': 'Dominator-Anzug',
'utilitysuit': 'Maverick-Anzug',
},
r'French\FR': {
'flightsuit': 'Combinaison de vol',
'explorationsuit': 'Combinaison Artemis',
'tacticalsuit': 'Combinaison Dominator',
'utilitysuit': 'Combinaison Maverick',
},
r'Portuguese\BR': {
'flightsuit': 'Traje voador',
'explorationsuit': 'Traje Artemis',
'tacticalsuit': 'Traje Dominator',
'utilitysuit': 'Traje Maverick',
},
r'Russian\RU': {
'flightsuit': 'Летный комбинезон',
'explorationsuit': 'Комбинезон Artemis',
'tacticalsuit': 'Комбинезон Dominator',
'utilitysuit': 'Комбинезон Maverick',
},
r'Spanish\ES': {
'flightsuit': 'Traje de vuelo',
'explorationsuit': 'Traje Artemis',
'tacticalsuit': 'Traje Dominator',
'utilitysuit': 'Traje Maverick',
},
}

View File

@ -19,6 +19,7 @@ if TYPE_CHECKING:
import util_ships import util_ships
from config import config from config import config
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
logger = get_main_logger() logger = get_main_logger()
@ -114,6 +115,9 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# Cmdr state shared with EDSM and plugins # Cmdr state shared with EDSM and plugins
# If you change anything here update PLUGINS.md documentation! # If you change anything here update PLUGINS.md documentation!
self.state: Dict = { self.state: Dict = {
'GameLanguage': None, # From `Fileheader
'GameVersion': None, # From `Fileheader
'GameBuild': None, # From `Fileheader
'Captain': None, # On a crew 'Captain': None, # On a crew
'Cargo': defaultdict(int), 'Cargo': defaultdict(int),
'Credits': None, 'Credits': None,
@ -496,13 +500,15 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.systemaddress = None self.systemaddress = None
self.started = None self.started = None
self.__init_state() self.__init_state()
# In self.state as well, as that's what plugins get
self.state['GameLanguage'] = entry['language']
self.state['GameVersion'] = entry['gameversion']
self.state['GameBuild'] = entry['build']
elif event_type == 'Commander': elif event_type == 'Commander':
self.live = True # First event in 3.0 self.live = True # First event in 3.0
elif event_type == 'LoadGame': elif event_type == 'LoadGame':
# alpha4
# Odyssey: bool
self.cmdr = entry['Commander'] self.cmdr = entry['Commander']
# 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event)
self.mode = entry.get('GameMode') self.mode = entry.get('GameMode')
@ -1075,8 +1081,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# "ModuleName":"wpn_s_pistol_plasma_charged", # "ModuleName":"wpn_s_pistol_plasma_charged",
# "ModuleName_Localised":"Manticore Tormentor" } ] } # "ModuleName_Localised":"Manticore Tormentor" } ] }
# #
suit_slotid, suitloadout_slotid = self.suitloadout_store_from_event(entry) suitid, suitloadout_slotid = self.suitloadout_store_from_event(entry)
if not self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid): if not self.suit_and_loadout_setcurrent(suitid, suitloadout_slotid):
logger.error(f"Event was: {entry}") logger.error(f"Event was: {entry}")
elif event_type == 'CreateSuitLoadout': elif event_type == 'CreateSuitLoadout':
@ -1091,7 +1097,10 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# { "SlotName":"SecondaryWeapon", "SuitModuleID":1700217869872834, # { "SlotName":"SecondaryWeapon", "SuitModuleID":1700217869872834,
# "ModuleName":"wpn_s_pistol_kinetic_sauto", "ModuleName_Localised":"Karma P-15" } ] } # "ModuleName":"wpn_s_pistol_kinetic_sauto", "ModuleName_Localised":"Karma P-15" } ] }
# #
_, _ = self.suitloadout_store_from_event(entry) suitid, suitloadout_slotid = self.suitloadout_store_from_event(entry)
# Creation doesn't mean equipping it
# if not self.suit_and_loadout_setcurrent(suitid, suitloadout_slotid):
# logger.error(f"Event was: {entry}")
elif event_type == 'DeleteSuitLoadout': elif event_type == 'DeleteSuitLoadout':
# alpha4: # alpha4:
@ -1131,10 +1140,12 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# alpha4 : # alpha4 :
# { "timestamp":"2021-04-29T09:03:37Z", "event":"BuySuit", "Name":"UtilitySuit_Class1", # { "timestamp":"2021-04-29T09:03:37Z", "event":"BuySuit", "Name":"UtilitySuit_Class1",
# "Name_Localised":"Maverick Suit", "Price":150000, "SuitID":1698364934364699 } # "Name_Localised":"Maverick Suit", "Price":150000, "SuitID":1698364934364699 }
loc_name = entry.get('Name_Localised', entry['Name'])
self.state['Suits'][entry['SuitID']] = { self.state['Suits'][entry['SuitID']] = {
'name': entry['Name'], 'name': entry['Name'],
'locName': entry.get('Name_Localised', entry['Name']), 'locName': loc_name,
'id': None, # Is this an FDev ID for suit type ? 'edmcName': self.suit_sane_name(loc_name),
'id': None, # Is this an FDev ID for suit type ?
'suitId': entry['SuitID'], 'suitId': entry['SuitID'],
'slots': [], 'slots': [],
} }
@ -1581,6 +1592,44 @@ 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_sane_name(self, name: str) -> str:
"""
Given an input suit name return the best 'sane' name we can.
AS of 4.0.0.102 the Journal events are fine for a Grade 1 suit, but
anything above that has broken SuitName_Localised strings, e.g.
$TacticalSuit_Class1_Name;
Also, if there isn't a SuitName_Localised value at all we'll use the
plain SuitName which can be, e.g. tacticalsuit_class3
If the names were correct we would get 'Dominator Suit' in this instance,
however what we want to return is, e.g. 'Dominator'. As that's both
sufficient for disambiguation and more succinct.
:param name: Name that could be in any of the forms.
:return: Our sane version of this suit's name.
"""
# TODO: Localisation ?
# Stage 1: Is it in `$<type>_Class<X>_Name;` form ?
if m := re.fullmatch(r'(?i)^\$([^_]+)_Class([0-9]+)_Name;$', name):
n, c = m.group(1, 2)
name = n
# Stage 2: Is it in `<type>_class<x>` form ?
elif m := re.fullmatch(r'(?i)^([^_]+)_class([0-9]+)$', name):
n, c = m.group(1, 2)
name = n
# Now turn either of those into an English '<type> Suit' form
if loc_lookup := edmc_suit_symbol_localised.get(self.state['GameLanguage']):
name = loc_lookup.get(name.lower(), name)
# Finally, map that to a form without the verbose ' Suit' on the end
name = edmc_suit_shortnames.get(name, name)
return name
def suitloadout_store_from_event(self, entry) -> Tuple[int, int]: def suitloadout_store_from_event(self, entry) -> Tuple[int, int]:
""" """
Store Suit and SuitLoadout data from a journal event. Store Suit and SuitLoadout data from a journal event.
@ -1591,45 +1640,58 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
:param entry: Journal entry - 'SwitchSuitLoadout' or 'SuitLoadout' :param entry: Journal entry - 'SwitchSuitLoadout' or 'SuitLoadout'
:return Tuple[suit_slotid, suitloadout_slotid]: The IDs we set data for. :return Tuple[suit_slotid, suitloadout_slotid]: The IDs we set data for.
""" """
suit_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) # This is the full ID from Frontier, it's not a sparse array slot id
# Initial suit containing just the data that is then embedded in suitid = entry['SuitID']
# the loadout
new_suit = { # Check if this looks like a suit we already have stored, so as
'name': entry['SuitName'], # to avoid 'bad' Journal localised names.
'locName': entry.get('SuitName_Localised', entry['SuitName']), suit = self.state['Suits'].get(f"{suitid}", None)
'suitId': entry['SuitID'], if suit is None:
} # Initial suit containing just the data that is then embedded in
# the loadout
# TODO: Attempt to map SuitName_Localised to something sane, if it
# isn't already.
suitname = entry.get('SuitName_Localised', entry['SuitName'])
edmc_suitname = self.suit_sane_name(suitname)
suit = {
'edmcName': edmc_suitname,
'locName': suitname,
'suitId': entry['SuitID'],
'name': entry['SuitName'],
}
suitloadout_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID'])
# Make the new loadout, in the CAPI format # Make the new loadout, in the CAPI format
new_loadout = { new_loadout = {
'loadoutSlotId': suit_slotid, 'loadoutSlotId': suitloadout_slotid,
'suit': new_suit, 'suit': suit,
'name': entry['LoadoutName'], 'name': entry['LoadoutName'],
'slots': self.suit_loadout_slots_array_to_dict( 'slots': self.suit_loadout_slots_array_to_dict(entry['Modules']),
entry['Modules']),
} }
# Assign this loadout into our state # Assign this loadout into our state
self.state['SuitLoadouts'][f"{new_loadout['loadoutSlotId']}"] = new_loadout self.state['SuitLoadouts'][f"{suitloadout_slotid}"] = new_loadout
# Now add in the extra fields for new_suit to be a 'full' Suit structure # 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 suit['id'] = suit.get('id') # Not available in 4.0.0.100 journal event
new_suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI
# Ensure new_suit is in self.state['Suits'] # Ensure the suit is in self.state['Suits']
self.state['Suits'][f"{suit_slotid}"] = new_suit self.state['Suits'][f"{suitid}"] = suit
return suit_slotid, new_loadout['loadoutSlotId'] return suitid, suitloadout_slotid
def suit_and_loadout_setcurrent(self, suit_slotid: int, suitloadout_slotid: int) -> bool: def suit_and_loadout_setcurrent(self, suitid: int, suitloadout_slotid: int) -> bool:
""" """
Set self.state for SuitCurrent and SuitLoadoutCurrent as requested. Set self.state for SuitCurrent and SuitLoadoutCurrent as requested.
If the specified slots are unknown we abort and return False, else If the specified slots are unknown we abort and return False, else
return True. return True.
:param suit_slotid: Numeric ID of the slot for the suit. :param suitid: Numeric ID of the suit.
:param suitloadout_slotid: Numeric ID of the slot for the suit loadout. :param suitloadout_slotid: Numeric ID of the slot for the suit loadout.
:return: True if we could do this, False if not. :return: True if we could do this, False if not.
""" """
str_suitid = f"{suit_slotid}" str_suitid = f"{suitid}"
str_suitloadoutid = f"{suitloadout_slotid}" str_suitloadoutid = f"{suitloadout_slotid}"
if (self.state['Suits'].get(str_suitid, False) if (self.state['Suits'].get(str_suitid, False)
@ -1638,7 +1700,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][str_suitloadoutid] self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][str_suitloadoutid]
return True return True
logger.error(f"Tried to set a suit and suitloadout where we didn't know about both: {suit_slotid=}, " logger.error(f"Tried to set a suit and suitloadout where we didn't know about both: {suitid=}, "
f"{str_suitloadoutid=}") f"{str_suitloadoutid=}")
return False return False