1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-17 01:22:19 +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")}>'
return
suitname = suit['locName']
suitname = suit['edmcName']
if (suitloadout := monitor.state.get('SuitLoadoutCurrent')) is None:
self.suit['text'] = ''
@ -927,7 +927,7 @@ class AppWindow(object):
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:
if (suitname := suit.get('edmcName')) is not None:
# We've been paranoid about loadout->suit->suitname, now just assume loadouts is there
loadout_name = index_possibly_sparse_list(
data['loadouts'], loadout['loadoutSlotId']

View File

@ -566,6 +566,9 @@ Content of `state` (updated to the current journal entry):
| 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 |
| `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. |
@ -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
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
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.
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.
"""
@ -122,7 +122,7 @@ def git_shorthash_from_head() -> str:
with contextlib.suppress(Exception):
result = subprocess.run('git diff --stat HEAD'.split(), capture_output=True)
if len(result.stdout) > 0:
shorthash += '-WORKING-DIR-IS-DIRTY'
shorthash += '+DIRTY'
if len(result.stderr) > 0:
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',
'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
from config import config
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised
from EDMCLogging import 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
# If you change anything here update PLUGINS.md documentation!
self.state: Dict = {
'GameLanguage': None, # From `Fileheader
'GameVersion': None, # From `Fileheader
'GameBuild': None, # From `Fileheader
'Captain': None, # On a crew
'Cargo': defaultdict(int),
'Credits': None,
@ -496,13 +500,15 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.systemaddress = None
self.started = None
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':
self.live = True # First event in 3.0
elif event_type == 'LoadGame':
# alpha4
# Odyssey: bool
self.cmdr = entry['Commander']
# 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event)
self.mode = entry.get('GameMode')
@ -1075,8 +1081,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# "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):
suitid, suitloadout_slotid = self.suitloadout_store_from_event(entry)
if not self.suit_and_loadout_setcurrent(suitid, suitloadout_slotid):
logger.error(f"Event was: {entry}")
elif event_type == 'CreateSuitLoadout':
@ -1091,7 +1097,10 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# { "SlotName":"SecondaryWeapon", "SuitModuleID":1700217869872834,
# "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':
# alpha4:
@ -1131,10 +1140,12 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# alpha4 :
# { "timestamp":"2021-04-29T09:03:37Z", "event":"BuySuit", "Name":"UtilitySuit_Class1",
# "Name_Localised":"Maverick Suit", "Price":150000, "SuitID":1698364934364699 }
loc_name = entry.get('Name_Localised', entry['Name'])
self.state['Suits'][entry['SuitID']] = {
'name': entry['Name'],
'locName': entry.get('Name_Localised', entry['Name']),
'id': None, # Is this an FDev ID for suit type ?
'locName': loc_name,
'edmcName': self.suit_sane_name(loc_name),
'id': None, # Is this an FDev ID for suit type ?
'suitId': entry['SuitID'],
'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)
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]:
"""
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'
: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'],
}
# This is the full ID from Frontier, it's not a sparse array slot id
suitid = entry['SuitID']
# Check if this looks like a suit we already have stored, so as
# to avoid 'bad' Journal localised names.
suit = self.state['Suits'].get(f"{suitid}", None)
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
new_loadout = {
'loadoutSlotId': suit_slotid,
'suit': new_suit,
'loadoutSlotId': suitloadout_slotid,
'suit': suit,
'name': entry['LoadoutName'],
'slots': self.suit_loadout_slots_array_to_dict(
entry['Modules']),
'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
self.state['SuitLoadouts'][f"{suitloadout_slotid}"] = 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
suit['id'] = suit.get('id') # Not available in 4.0.0.100 journal event
suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI
# Ensure the suit is in self.state['Suits']
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.
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 suitid: Numeric ID of 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_suitid = f"{suitid}"
str_suitloadoutid = f"{suitloadout_slotid}"
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]
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=}")
return False