mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-04 19:40:02 +03:00
208 lines
7.0 KiB
Python
208 lines
7.0 KiB
Python
"""Export ship loadout in ED Shipyard plain text format."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import time
|
|
from collections import defaultdict
|
|
from typing import Union
|
|
|
|
import outfitting
|
|
import util_ships
|
|
from config import config
|
|
from edmc_data import edshipyard_slot_map as slot_map
|
|
from edmc_data import ship_name_map
|
|
from EDMCLogging import get_main_logger
|
|
|
|
logger = get_main_logger()
|
|
|
|
__Module = dict[str, Union[str, list[str]]] # Have to keep old-style here for compatibility
|
|
|
|
# Map API ship names to ED Shipyard names
|
|
ship_map = ship_name_map.copy()
|
|
|
|
# Ship masses
|
|
ships_file = config.respath_path / "ships.json"
|
|
with open(ships_file, encoding="utf-8") as ships_file_handle:
|
|
ships = json.load(ships_file_handle)
|
|
|
|
|
|
def export(data, filename=None) -> None: # noqa: C901, CCR001
|
|
"""
|
|
Export ship loadout in E:D Shipyard plain text format.
|
|
|
|
:param data: CAPI data.
|
|
:param filename: Override default file name.
|
|
"""
|
|
def class_rating(module: __Module) -> str:
|
|
"""
|
|
Return a string representation of the class of the given module.
|
|
|
|
:param module: Module data dict.
|
|
:return: Rating of the module.
|
|
"""
|
|
mod_class = module['class']
|
|
mod_rating = module['rating']
|
|
mod_mount = module.get('mount')
|
|
mod_guidance: str = str(module.get('guidance'))
|
|
|
|
ret = f'{mod_class}{mod_rating}'
|
|
if 'guidance' in module: # Missiles
|
|
if mod_mount is not None:
|
|
mount = mod_mount[0]
|
|
|
|
else:
|
|
mount = 'F'
|
|
|
|
guidance = mod_guidance[0]
|
|
ret += f'/{mount}{guidance}'
|
|
|
|
elif 'mount' in module: # Hardpoints
|
|
ret += f'/{mod_mount}'
|
|
|
|
elif 'Cabin' in module['name']: # Passenger cabins
|
|
ret += f'/{module["name"][0]}'
|
|
|
|
return ret + ' '
|
|
|
|
querytime = config.get_int('querytime', default=int(time.time()))
|
|
|
|
loadout = defaultdict(list)
|
|
mass = 0.0
|
|
fuel = 0
|
|
cargo = 0
|
|
fsd = None
|
|
jumpboost = 0
|
|
|
|
for slot in sorted(data['ship']['modules']):
|
|
v = data['ship']['modules'][slot]
|
|
try:
|
|
if not v:
|
|
continue
|
|
|
|
module: __Module | None = outfitting.lookup(v['module'], ship_map)
|
|
if not module:
|
|
continue
|
|
|
|
cr = class_rating(module)
|
|
mods = v.get('modifications') or v.get('WorkInProgress_modifications') or {}
|
|
if mods.get('OutfittingFieldType_Mass'):
|
|
mass += float(module.get('mass', 0.0) * mods['OutfittingFieldType_Mass']['value'])
|
|
|
|
else:
|
|
mass += float(module.get('mass', 0.0)) # type: ignore
|
|
|
|
# Specials
|
|
if 'Fuel Tank' in module['name']:
|
|
fuel += 2**int(module['class']) # type: ignore
|
|
name = f'{module["name"]} (Capacity: {2**int(module["class"])})' # type: ignore
|
|
|
|
elif 'Cargo Rack' in module['name']:
|
|
cargo += 2**int(module['class']) # type: ignore
|
|
name = f'{module["name"]} (Capacity: {2**int(module["class"])})' # type: ignore
|
|
|
|
else:
|
|
name = module['name'] # type: ignore
|
|
|
|
if name == 'Frame Shift Drive' or name == 'Frame Shift Drive (SCO)':
|
|
fsd = module # save for range calculation
|
|
|
|
if mods.get('OutfittingFieldType_FSDOptimalMass'):
|
|
fsd['optmass'] *= mods['OutfittingFieldType_FSDOptimalMass']['value']
|
|
|
|
if mods.get('OutfittingFieldType_MaxFuelPerJump'):
|
|
fsd['maxfuel'] *= mods['OutfittingFieldType_MaxFuelPerJump']['value']
|
|
|
|
jumpboost += module.get('jumpboost', 0) # type: ignore
|
|
|
|
for slot_prefix, index in slot_map.items():
|
|
if slot.lower().startswith(slot_prefix):
|
|
loadout[index].append(cr + name)
|
|
break
|
|
|
|
else:
|
|
if slot.lower().startswith('slot'):
|
|
loadout[slot[-1]].append(cr + name)
|
|
elif not slot.lower().startswith('planetaryapproachsuite'):
|
|
logger.debug(f'EDShipyard: Unknown slot {slot}')
|
|
|
|
except AssertionError as e:
|
|
logger.debug(f'EDShipyard: {e!r}')
|
|
continue # Silently skip unrecognized modules
|
|
|
|
except Exception:
|
|
if __debug__:
|
|
raise
|
|
|
|
# Construct description
|
|
ship = ship_map.get(data['ship']['name'].lower(), data['ship']['name'])
|
|
if data['ship'].get('shipName') is not None:
|
|
_ships = f'{ship}, {data["ship"]["shipName"]}'
|
|
|
|
else:
|
|
_ships = ship
|
|
|
|
string = f'[{_ships}]\n'
|
|
|
|
slot_types = (
|
|
'H', 'L', 'M', 'S', 'U', None, 'BH', 'RB', 'TM', 'FH', 'EC', 'PC', 'SS', 'FS', None, 'MC', None, '9', '8',
|
|
'7', '6', '5', '4', '3', '2', '1'
|
|
)
|
|
for slot in slot_types:
|
|
if not slot:
|
|
string += '\n'
|
|
|
|
elif slot in loadout:
|
|
for name in loadout[slot]:
|
|
string += f'{slot}: {name}\n'
|
|
|
|
string += f'---\nCargo : {cargo} T\nFuel : {fuel} T\n'
|
|
|
|
# Add mass and range
|
|
assert data['ship']['name'].lower() in ship_name_map, data['ship']['name']
|
|
assert ship_name_map[data['ship']['name'].lower()] in ships, ship_name_map[data['ship']['name'].lower()]
|
|
|
|
try:
|
|
mass += ships[ship_name_map[data['ship']['name'].lower()]]['hullMass']
|
|
string += f'Mass : {mass:.2f} T empty\n {mass + fuel + cargo:.2f} T full\n'
|
|
maxfuel = fsd.get('maxfuel', 0) # type: ignore
|
|
fuelmul = fsd.get('fuelmul', 0) # type: ignore
|
|
|
|
try:
|
|
multiplier = pow(min(fuel, maxfuel) / fuelmul, 1.0 / fsd['fuelpower']) * fsd['optmass'] # type: ignore
|
|
range_unladen = multiplier / (mass + fuel) + jumpboost
|
|
range_laden = multiplier / (mass + fuel + cargo) + jumpboost
|
|
# As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with
|
|
# a locale that uses comma for decimal separator.
|
|
except ZeroDivisionError:
|
|
range_unladen = range_laden = 0.0
|
|
string += (f'Range : {range_unladen:.2f} LY unladen\n'
|
|
f' {range_laden:.2f} LY laden\n')
|
|
|
|
except Exception:
|
|
if __debug__:
|
|
raise
|
|
|
|
if filename:
|
|
with open(filename, 'wt') as h:
|
|
h.write(string)
|
|
return
|
|
|
|
# Look for last ship of this type
|
|
ship = util_ships.ship_file_name(data['ship'].get('shipName'), data['ship']['name'])
|
|
regexp = re.compile(re.escape(ship) + r'\.\d{4}-\d\d-\d\dT\d\d\.\d\d\.\d\d\.txt')
|
|
oldfiles = sorted([x for x in os.listdir(config.get_str('outdir')) if regexp.match(x)])
|
|
if oldfiles:
|
|
with (pathlib.Path(config.get_str('outdir')) / oldfiles[-1]).open() as h:
|
|
if h.read() == string:
|
|
return # same as last time - don't write
|
|
|
|
# Write
|
|
timestamp = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))
|
|
filename = pathlib.Path(config.get_str('outdir')) / f'{ship}.{timestamp}.txt'
|
|
|
|
with open(filename, 'wt') as h:
|
|
h.write(string)
|