diff --git a/EDMC.py b/EDMC.py index 2b9f7bc3..b9153564 100755 --- a/EDMC.py +++ b/EDMC.py @@ -22,7 +22,6 @@ from commodity import COMMODITY_DEFAULT import outfitting import loadout import edshipyard -import coriolis import shipyard import eddn import stats @@ -43,7 +42,6 @@ try: parser = argparse.ArgumentParser(prog=appcmdname, description='Prints the current system and station (if docked) to stdout and optionally writes player status, ship locations, ship loadout and/or station data to file. Requires prior setup through the accompanying GUI app.') parser.add_argument('-v', '--version', help='print program version and exit', action='store_const', const=True) parser.add_argument('-a', metavar='FILE', help='write ship loadout to FILE in Companion API json format') - parser.add_argument('-c', metavar='FILE', help='write ship loadout to FILE in Coriolis json format') parser.add_argument('-e', metavar='FILE', help='write ship loadout to FILE in E:D Shipyard plain text format') parser.add_argument('-l', metavar='FILE', help='write ship locations to FILE in CSV format') parser.add_argument('-m', metavar='FILE', help='write station commodity market data to FILE in CSV format') @@ -118,8 +116,6 @@ try: h.write(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')).encode('utf-8')) if args.a: loadout.export(data, args.a) - if args.c: - coriolis.export(data, args.c) if args.e: edshipyard.export(data, args.e) if args.l: diff --git a/README.md b/README.md index 69d6184d..02db6d40 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,6 @@ Arguments: -h, --help show this help message and exit -v, --version print program version and exit -a FILE write ship loadout to FILE in Companion API json format - -c FILE write ship loadout to FILE in Coriolis json format -e FILE write ship loadout to FILE in E:D Shipyard plain text format -l FILE write ship locations to FILE in CSV format -m FILE write station commodity market data to FILE in CSV format diff --git a/coriolis.py b/coriolis.py index d419d054..240e595c 100755 --- a/coriolis.py +++ b/coriolis.py @@ -7,11 +7,7 @@ import base64 from collections import OrderedDict import cPickle import json -import os -from os.path import join -import re import StringIO -import time import gzip from config import config @@ -19,225 +15,6 @@ import outfitting import companion -# Map API slot names to Coriolis categories -# http://cdn.coriolis.io/schemas/ship-loadout/2.json - -slot_map = { - 'hugehardpoint' : 'hardpoints', - 'largehardpoint' : 'hardpoints', - 'mediumhardpoint' : 'hardpoints', - 'smallhardpoint' : 'hardpoints', - 'tinyhardpoint' : 'utility', - 'armour' : 'standard', - 'powerplant' : 'standard', - 'mainengines' : 'standard', - 'frameshiftdrive' : 'standard', - 'lifesupport' : 'standard', - 'powerdistributor' : 'standard', - 'radar' : 'standard', - 'fueltank' : 'standard', - 'military' : 'internal', - 'slot' : 'internal', -} - - -# Map API ship names to Coriolis names -ship_map = dict(companion.ship_map) -ship_map['cobramkiii'] = 'Cobra Mk III' -ship_map['cobramkiv'] = 'Cobra Mk IV', -ship_map['viper'] = 'Viper' -ship_map['viper_mkiv'] = 'Viper Mk IV' - - -# Map EDDN outfitting schema / in-game names to Coriolis names -# https://raw.githubusercontent.com/jamesremuscat/EDDN/master/schemas/outfitting-v1.0.json - -standard_map = OrderedDict([ # in output order - ('Armour', 'bulkheads'), - (None, 'cargoHatch'), # not available in the Companion API data - ('Power Plant', 'powerPlant'), - ('Thrusters', 'thrusters'), - ('Frame Shift Drive', 'frameShiftDrive'), - ('Life Support', 'lifeSupport'), - ('Power Distributor', 'powerDistributor'), - ('Sensors', 'sensors'), - ('Fuel Tank', 'fuelTank'), -]) - -weaponmount_map = { - 'Fixed' : 'Fixed', - 'Gimballed' : 'Gimballed', - 'Turreted' : 'Turret', -} - - -# Modules that have a name as well as a group -bulkheads = outfitting.armour_map.values() -fixup_map = {} -fixup_map.update({ x[0] : ('Scanner', x[0]) for x in outfitting.misc_internal_map.values() }) -fixup_map.update({ x[0] : ('Countermeasure', x[0]) for x in outfitting.countermeasure_map.values() }) -fixup_map.update({ - 'Advanced Plasma Accelerator' : ('Plasma Accelerator', 'Advanced Plasma Accelerator'), - 'Corrosion Resistant Cargo Rack': ('Cargo Rack', 'Corrosion Resistant'), - 'Cytoscrambler Burst Laser' : ('Burst Laser', 'Cytoscrambler'), - 'Enforcer Cannon' : ('Multi-cannon', 'Enforcer'), - 'Enhanced Performance Thrusters': ('Thrusters', 'Enhanced Performance'), - 'Imperial Hammer Rail Gun' : ('Rail Gun', 'Imperial Hammer'), - 'Luxury Class Passenger Cabin' : ('Luxury Passenger Cabin', None), - 'Mining Lance Beam Laser' : ('Mining Laser', 'Mining Lance'), - 'Multi-Cannon' : ('Multi-cannon', None), - 'Pacifier Frag-Cannon' : ('Fragment Cannon', 'Pacifier'), - 'Pack-Hound Missile Rack' : ('Missile Rack', 'Pack-Hound'), - 'Pulse Disruptor Laser' : ('Pulse Laser', 'Disruptor'), - 'Retributor Beam Laser' : ('Beam Laser', 'Retributor'), - 'Rocket Propelled FSD Disruptor': ('Missile Rack', 'Rocket Propelled FSD Disruptor'), - 'Shock Mine Launcher' : ('Mine Launcher', 'Shock Mine Launcher'), - 'Standard Docking Computer' : ('Docking Computer', 'Standard Docking Computer'), -}) - - -# Ship masses -ships = cPickle.load(open(join(config.respath, 'ships.p'), 'rb')) - - -def export(data, filename=None): - - querytime = config.getint('querytime') or int(time.time()) - - loadout = OrderedDict([ # Mimic Coriolis export ordering - ('$schema', 'http://cdn.coriolis.io/schemas/ship-loadout/2.json#'), - ('name', ship_map.get(data['ship']['name'].lower(), data['ship']['name'])), - ('ship', ship_map.get(data['ship']['name'].lower(), data['ship']['name'])), - ('components', OrderedDict([ - ('standard', OrderedDict([(x,None) for x in standard_map.values()])), - ('hardpoints', []), - ('utility', []), - ('internal', []), - ])), - ]) - maxpri = 0 - mass = 0.0 - fsd = None - - # Correct module ordering relies on the fact that "Slots" in the data are correctly ordered alphabetically. - # Correct hardpoint ordering additionally relies on the fact that "Huge" < "Large" < "Medium" < "Small" - for slot in sorted(data['ship']['modules']): - - v = data['ship']['modules'][slot] - try: - for s in slot_map: - if slot.lower().startswith(s): - category = slot_map[s] - break - else: - if __debug__: - for skip in ['bobble', 'decal', 'paintjob', 'planetaryapproachsuite', 'enginecolour', 'shipkit', 'weaponcolour']: - if slot.lower().startswith(skip): - break - else: - print 'Coriolis: Unknown slot %s' % slot - continue - - if not v: - # Need to add nulls for empty slots. Assumes that standard slots can't be empty. - loadout['components'][category].append(None) - continue - - module = outfitting.lookup(v['module'], ship_map) - if not module: - raise AssertionError('Unknown module %s' % v) # Shouldn't happen - mass += module.get('mass', 0) - - thing = OrderedDict([ - ('class',int(module['class'])), - ('rating', module['rating']), - ('enabled', module['enabled']), - ('priority', module['priority']+1), # make 1-based - ]) - maxpri = max(maxpri, thing['priority']) - - if category == 'standard': - # Standard items are indexed by "group" rather than containing a "group" member - if module['name'] in bulkheads: - loadout['components'][category]['bulkheads'] = module['name'] # Bulkheads are just strings - else: - loadout['components'][category][standard_map[module['name']]] = thing - if module['name'] == 'Frame Shift Drive': - fsd = module # save for range calculation - else: - # All other items have a "group" member, some also have a "name" - if module['name'] in fixup_map: - thing['group'], name = fixup_map[module['name']] - if name: thing['name'] = name - else: - thing['group'] = module['name'] - - if 'mount' in module: - thing['mount'] = weaponmount_map[module['mount']] - if 'guidance' in module: - thing['missile'] = module['guidance'][0] # not mentioned in schema - - loadout['components'][category].append(thing) - - except AssertionError as e: - # Silently skip unrecognized modules - if __debug__: print 'Coriolis: %s' % e - if category != 'standard': - loadout['components'][category].append(None) - except: - if __debug__: raise - - # Cargo Hatch status is not available in the data - fake something up - loadout['components']['standard']['cargoHatch'] = OrderedDict([ - ('enabled', True), - ('priority', maxpri), - ]) - - # Add mass and range - assert data['ship']['name'].lower() in companion.ship_map, data['ship']['name'] - assert companion.ship_map[data['ship']['name'].lower()] in ships, companion.ship_map[data['ship']['name'].lower()] - try: - # https://github.com/cmmcleod/coriolis/blob/master/app/js/shipyard/module-shipyard.js#L184 - hullMass = ships[companion.ship_map[data['ship']['name'].lower()]]['hullMass'] - mass += hullMass - multiplier = pow(min(data['ship']['fuel']['main']['capacity'], fsd['maxfuel']) / fsd['fuelmul'], 1.0 / fsd['fuelpower']) * fsd['optmass'] - - loadout['stats'] = OrderedDict([ - ('hullMass', hullMass), - ('fuelCapacity', data['ship']['fuel']['main']['capacity']), - ('cargoCapacity', data['ship']['cargo']['capacity']), - ('ladenMass', mass + data['ship']['fuel']['main']['capacity'] + data['ship']['cargo']['capacity']), - ('unladenMass', mass), - ('unladenRange', round(multiplier / (mass + min(data['ship']['fuel']['main']['capacity'], fsd['maxfuel'])), 2)), # fuel for one jump - ('fullTankRange', round(multiplier / (mass + data['ship']['fuel']['main']['capacity']), 2)), - ('ladenRange', round(multiplier / (mass + data['ship']['fuel']['main']['capacity'] + data['ship']['cargo']['capacity']), 2)), - ]) - except: - if __debug__: raise - - # Construct description - string = json.dumps(loadout, indent=2, separators=(',', ': ')) - - if filename: - with open(filename, 'wt') as h: - h.write(string) - return - - # Look for last ship of this type - ship = companion.ship_map.get(data['ship']['name'].lower(), data['ship']['name']) # Use in-game name - regexp = re.compile(re.escape(ship) + '\.\d\d\d\d\-\d\d\-\d\dT\d\d\.\d\d\.\d\d\.json') - oldfiles = sorted([x for x in os.listdir(config.get('outdir')) if regexp.match(x)]) - if oldfiles: - with open(join(config.get('outdir'), oldfiles[-1]), 'rU') as h: - if h.read() == string: - return # same as last time - don't write - - # Write - filename = join(config.get('outdir'), '%s.%s.json' % (ship, time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)))) - with open(filename, 'wt') as h: - h.write(string) - - # Return a URL for the current ship def url(data, is_beta): @@ -246,16 +23,41 @@ def url(data, is_beta): out = StringIO.StringIO() with gzip.GzipFile(fileobj=out, mode='w') as f: f.write(string) - return (is_beta and 'http://beta.coriolis.edcd.io/import?data=' or 'https://coriolis.edcd.io/import?data=') + base64.urlsafe_b64encode(out.getvalue()).replace('=', '%3D') + return (is_beta and 'https://beta.coriolis.edcd.io/import?data=' or 'https://coriolis.edcd.io/import?data=') + base64.urlsafe_b64encode(out.getvalue()).replace('=', '%3D') # -# build ship and module databases from https://github.com/cmmcleod/coriolis-data +# build ship and module databases from https://github.com/EDCD/coriolis-data/ # if __name__ == "__main__": - import json data = json.load(open('coriolis-data/dist/index.json')) + bulkheads = outfitting.armour_map.values() + + # Modules that have a name as well as a group + fixup_map = {} + fixup_map.update({ x[0] : ('Scanner', x[0]) for x in outfitting.misc_internal_map.values() }) + fixup_map.update({ x[0] : ('Countermeasure', x[0]) for x in outfitting.countermeasure_map.values() }) + fixup_map.update({ + 'Advanced Plasma Accelerator' : ('Plasma Accelerator', 'Advanced Plasma Accelerator'), + 'Corrosion Resistant Cargo Rack': ('Cargo Rack', 'Corrosion Resistant'), + 'Cytoscrambler Burst Laser' : ('Burst Laser', 'Cytoscrambler'), + 'Enforcer Cannon' : ('Multi-cannon', 'Enforcer'), + 'Enhanced Performance Thrusters': ('Thrusters', 'Enhanced Performance'), + 'Imperial Hammer Rail Gun' : ('Rail Gun', 'Imperial Hammer'), + 'Luxury Class Passenger Cabin' : ('Luxury Passenger Cabin', None), + 'Mining Lance Beam Laser' : ('Mining Laser', 'Mining Lance'), + 'Multi-Cannon' : ('Multi-cannon', None), + 'Pacifier Frag-Cannon' : ('Fragment Cannon', 'Pacifier'), + 'Pack-Hound Missile Rack' : ('Missile Rack', 'Pack-Hound'), + 'Pulse Disruptor Laser' : ('Pulse Laser', 'Disruptor'), + 'Retributor Beam Laser' : ('Beam Laser', 'Retributor'), + 'Rocket Propelled FSD Disruptor': ('Missile Rack', 'Rocket Propelled FSD Disruptor'), + 'Shock Mine Launcher' : ('Mine Launcher', 'Shock Mine Launcher'), + 'Standard Docking Computer' : ('Docking Computer', 'Standard Docking Computer'), + }) + specials = { v:k for k,v in fixup_map.items() } + # Map Coriolis's names to names displayed in the in-game shipyard coriolis_ship_map = { 'Cobra Mk III' : 'Cobra MkIII', @@ -326,8 +128,6 @@ if __name__ == "__main__": 'tp' : 'Torpedo Pylon' }; - specials = { v:k for k,v in fixup_map.items() } - ships = {} modules = {} @@ -363,5 +163,10 @@ if __name__ == "__main__": else: modules[key] = { 'mass': m.get('mass', 0) } # Some modules don't have mass + # Repair Limpet Controller not yet present in coriolis-data. Same masses as Prospector + for k in modules.keys(): + if k[0] == 'Prospector Limpet Controller': + modules[('Repair Limpet Controller',) + k[1:]] = modules[k] + modules = OrderedDict([(k,modules[k]) for k in sorted(modules)]) # sort for easier diffing cPickle.dump(modules, open('modules.p', 'wb'))