1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-15 00:30:33 +03:00

Add mass and range to E:D Shipyard and Coriolis.

Means that these files can be directly loaded into ETN.
This commit is contained in:
Jonathan Harris 2016-01-22 05:09:19 +00:00
parent 2cd52e8377
commit c5b2c4b34c
11 changed files with 169 additions and 11 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "coriolis-data"]
path = coriolis-data
url = git@github.com:cmmcleod/coriolis-data.git

View File

@ -5,7 +5,8 @@ This app downloads commodity market and other station data from the game [Elite:
* sends the data to the [Elite Dangerous Data Network](http://eddn-gateway.elite-markets.net/) (“EDDN”) from where you and others can use it via online trading tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc. * sends the data to the [Elite Dangerous Data Network](http://eddn-gateway.elite-markets.net/) (“EDDN”) from where you and others can use it via online trading tools such as [eddb](http://eddb.io/), [Elite Trade Net](http://etn.io/), [Inara](http://inara.cz), [ED-TD](http://ed-td.space/), [Roguey's](http://roguey.co.uk/elite-dangerous/), etc.
* saves the data to files on your computer that you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081), [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz), [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools), etc. * saves the data to files on your computer that you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081), [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home), [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz), [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools), etc.
* saves a record of your ship loadout and/or flight log. * saves a record of your ship loadout to files on your computer that you can load into [E:D Shipyard](http://www.edshipyard.com), [Coriolis](http://coriolis.io) or [Elite Trade Net](http://etn.io/).
* saves your flight log to a file on your computer and/or sends it to [Elite: Dangerous Star Map](http://www.edsm.net/).
Usage Usage
-------- --------
@ -61,7 +62,7 @@ This app can save a variety of data in a variety of formats:
* CSV format file - saves commodity market data as files that you can upload to [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz) or [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools). * CSV format file - saves commodity market data as files that you can upload to [Thrudd's Trading Tools](http://www.elitetradingtool.co.uk/), [Inara](http://inara.cz) or [mEDI's Elite Tools](https://github.com/mEDI-S/mEDI_s-Elite-Tools).
* Ship loadout * Ship loadout
* After every outfitting change saves a record of your ship loadout as a file that you can open in a text editor and that you can import into [E:D Shipyard](http://www.edshipyard.com) or [Coriolis](http://coriolis.io). * After every outfitting change saves a record of your ship loadout as a file that you can open in a text editor and that you can import into [E:D Shipyard](http://www.edshipyard.com), [Coriolis](http://coriolis.io) or [Elite Trade Net](http://etn.io/).
* Flight log * Flight log
* Elite Dangerous Star Map - sends a record of your location to “[EDSM](http://www.edsm.net/)” where you can view your logs under My account → Exploration Logs, and optionally add private comments about a system. * Elite Dangerous Star Map - sends a record of your location to “[EDSM](http://www.edsm.net/)” where you can view your logs under My account → Exploration Logs, and optionally add private comments about a system.

1
coriolis-data Submodule

@ -0,0 +1 @@
Subproject commit 618127dbb4effb6ad7b6a9352ef32b162d5560f6

View File

@ -1,6 +1,7 @@
# Export ship loadout in Coriolis format # Export ship loadout in Coriolis format
from collections import OrderedDict from collections import OrderedDict
import cPickle
import json import json
import os import os
from os.path import join from os.path import join
@ -82,6 +83,10 @@ fixup_map = {
} }
# Ship masses
ships = cPickle.load(open(join(config.respath, 'ships.p'), 'rb'))
def export(data, filename=None): def export(data, filename=None):
querytime = config.getint('querytime') or int(time.time()) querytime = config.getint('querytime') or int(time.time())
@ -98,6 +103,8 @@ def export(data, filename=None):
])), ])),
]) ])
maxpri = 0 maxpri = 0
mass = 0.0
fsd = None
# Correct module ordering relies on the fact that "Slots" in the data are correctly ordered alphabetically. # 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" # Correct hardpoint ordering additionally relies on the fact that "Huge" < "Large" < "Medium" < "Small"
@ -123,6 +130,7 @@ def export(data, filename=None):
module = outfitting.lookup(v['module'], ship_map) module = outfitting.lookup(v['module'], ship_map)
if not module: if not module:
raise AssertionError('Unknown module %s' % v) # Shouldn't happen raise AssertionError('Unknown module %s' % v) # Shouldn't happen
mass += module.get('mass', 0)
thing = OrderedDict([ thing = OrderedDict([
('class',int(module['class'])), ('class',int(module['class'])),
@ -138,6 +146,8 @@ def export(data, filename=None):
loadout['components'][category]['bulkheads'] = module['name'] # Bulkheads are just strings loadout['components'][category]['bulkheads'] = module['name'] # Bulkheads are just strings
else: else:
loadout['components'][category][standard_map[module['name']]] = thing loadout['components'][category][standard_map[module['name']]] = thing
if module['name'] == 'Frame Shift Drive':
fsd = module # save for range calculation
else: else:
# All other items have a "group" member, some also have a "name" # All other items have a "group" member, some also have a "name"
if module['name'] in fixup_map: if module['name'] in fixup_map:
@ -173,8 +183,30 @@ def export(data, filename=None):
('priority', maxpri), ('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 # Construct description
string = json.dumps(loadout, indent=2) string = json.dumps(loadout, indent=2, separators=(',', ': '))
if filename: if filename:
with open(filename, 'wt') as h: with open(filename, 'wt') as h:

80
eddb.py
View File

@ -31,21 +31,23 @@ class EDDB:
return (station_id, bool(flags & EDDB.HAS_MARKET), bool(flags & EDDB.HAS_OUTFITTING), bool(flags & EDDB.HAS_SHIPYARD)) return (station_id, bool(flags & EDDB.HAS_MARKET), bool(flags & EDDB.HAS_OUTFITTING), bool(flags & EDDB.HAS_SHIPYARD))
# build system & station database from files systems.json and stations.json from http://eddb.io/api # build databases from files systems.json, stations.json and modules.json from http://eddb.io/api
# and from https://github.com/cmmcleod/coriolis-data
if __name__ == "__main__": if __name__ == "__main__":
import json import json
# still send market and outfitting for currently "suspended" stations - # still send market and outfitting for currently "suspended" stations -
# https://community.elitedangerous.com/galnet/uid/568a999f9657ba5e0986a8de # https://community.elitedangerous.com/galnet/uid/568a999f9657ba5e0986a8de
suspended = set([659, 39328, 5672, 30402, 30653, 21901, 11335]) # https://community.elitedangerous.com/galnet/uid/569f610b9657ba7d3461ba04
suspended = set([7, 10035, 9765, 659, 39328, 5672, 30402, 30653, 21901, 11335])
# system_name by system_id # system_name by system_id
systems = dict([(x['id'], str(x['name'])) for x in json.loads(open('systems.json').read())]) systems = dict([(x['id'], str(x['name'])) for x in json.load(open('systems.json'))])
stations = json.loads(open('stations.json').read()) stations = json.load(open('stations.json'))
# check that all populated systems have known coordinates # check that all populated systems have known coordinates
coords = dict([(x['id'], x['x'] or x['y'] or x['z']) for x in json.loads(open('systems.json').read())]) coords = dict([(x['id'], x['x'] or x['y'] or x['z']) for x in json.load(open('systems.json'))])
for x in stations: for x in stations:
assert x['system_id'] == 17072 or coords[x['system_id']], (x['system_id'], systems[x['system_id']]) assert x['system_id'] == 17072 or coords[x['system_id']], (x['system_id'], systems[x['system_id']])
@ -63,3 +65,71 @@ if __name__ == "__main__":
for x in stations]) for x in stations])
cPickle.dump(station_ids, open('stations.p', 'wb'), protocol = cPickle.HIGHEST_PROTOCOL) cPickle.dump(station_ids, open('stations.p', 'wb'), protocol = cPickle.HIGHEST_PROTOCOL)
# Map eddb's names to names displayed in the in-game shipyard
eddb_ship_map = {
'Sidewinder Mk. I' : 'Sidewinder',
'Eagle Mk. II' : 'Eagle',
'Cobra Mk. III' : 'Cobra MkIII',
'Cobra MK IV' : 'Cobra MkIV',
'Viper Mk III' : 'Viper MkIII',
'Viper MK IV' : 'Viper MkIV',
}
# PP modules (see weapon-map in outfitting.py)
specials = {
'Retributor' : 'Retributor Beam Laser',
'Pack-Hound' : 'Pack-Hound Missile Rack',
'Mining Lance' : 'Mining Lance Beam Laser',
'Enforcer' : 'Enforcer Cannon',
'Advanced' : 'Advanced Plasma Accelerator',
'Distruptor' : 'Pulse Disruptor Laser',
'Cytoscrambler' : 'Cytoscrambler Burst Laser',
'Imperial Hammer' : 'Imperial Hammer Rail Gun',
'Pacifier' : 'Pacifier Frag-Cannon',
'Prismatic' : 'Prismatic Shield Generator',
}
# Module masses
modules = {}
for m in json.load(open('modules.json')):
# ignore mount and guidance, and convert strings to ascii to save space
key = (specials.get(m['name'], str(m['name'] or m['group']['name'])),
m['ship'] and eddb_ship_map.get(m['ship'], str(m['ship'])),
str(m['class']),
str(m['rating']))
if key in modules:
# Test our assumption that mount and guidance don't affect mass
assert modules[key]['mass'] == m.get('mass', 0), '%s !=\n%s' % (key, m)
else:
modules[key] = { 'mass': m.get('mass', 0) } # Some modules don't have mass
# Add FSD data from Coriolis
for m in json.load(open('coriolis-data/components/standard/frame_shift_drive.json')).values():
key = ('Frame Shift Drive', None, str(m['class']), str(m['rating']))
assert key in modules, key
modules[key].update({
'optmass' : m['optmass'],
'maxfuel' : m['maxfuel'],
'fuelmul' : m['fuelmul'],
'fuelpower' : m['fuelpower'],
})
cPickle.dump(modules, open('modules.p', 'wb'), protocol = cPickle.HIGHEST_PROTOCOL)
# Map Coriolis's names to names displayed in the in-game shipyard
coriolis_ship_map = {
'Cobra Mk III' : 'Cobra MkIII',
'Cobra Mk IV' : 'Cobra MkIV',
'Viper' : 'Viper MkIII',
'Viper Mk IV' : 'Viper MkIV',
}
# Ship masses
ships = {}
for f in os.listdir('coriolis-data/ships'):
if not f.endswith('.json'): continue
for m in json.load(open(join('coriolis-data/ships', f))).values():
ships[coriolis_ship_map.get(m['properties']['name'], str(m['properties']['name']))] = { 'hullMass' : m['properties']['hullMass'] }
cPickle.dump(ships, open('ships.p', 'wb'), protocol = cPickle.HIGHEST_PROTOCOL)

View File

@ -73,12 +73,13 @@ def export_commodities(data):
def export_outfitting(data): def export_outfitting(data):
# *Do* send empty modules list - implies station has no outfitting # *Do* send empty modules list - implies station has no outfitting
schemakeys = ['category', 'name', 'mount', 'guidance', 'ship', 'class', 'rating']
modules = [] modules = []
for v in data['lastStarport'].get('modules', {}).itervalues(): for v in data['lastStarport'].get('modules', {}).itervalues():
try: try:
module = outfitting.lookup(v, ship_map) module = outfitting.lookup(v, ship_map)
if module: if module:
modules.append(module) modules.append({ k: module[k] for k in schemakeys if k in module }) # just the relevant keys
except AssertionError as e: except AssertionError as e:
if __debug__: print 'Outfitting: %s' % e # Silently skip unrecognized modules if __debug__: print 'Outfitting: %s' % e # Silently skip unrecognized modules
except: except:

View File

@ -1,6 +1,7 @@
# Export ship loadout in E:D Shipyard format # Export ship loadout in E:D Shipyard format
from collections import defaultdict from collections import defaultdict
import cPickle
import os import os
from os.path import join from os.path import join
import re import re
@ -36,6 +37,11 @@ slot_map = {
'fueltank' : 'FS', 'fueltank' : 'FS',
} }
# Ship masses
ships = cPickle.load(open(join(config.respath, 'ships.p'), 'rb'))
def export(data, filename=None): def export(data, filename=None):
def class_rating(module): def class_rating(module):
@ -49,6 +55,8 @@ def export(data, filename=None):
querytime = config.getint('querytime') or int(time.time()) querytime = config.getint('querytime') or int(time.time())
loadout = defaultdict(list) loadout = defaultdict(list)
mass = 0.0
fsd = None
for slot in sorted(data['ship']['modules']): for slot in sorted(data['ship']['modules']):
@ -60,6 +68,7 @@ def export(data, filename=None):
if not module: continue if not module: continue
cr = class_rating(module) cr = class_rating(module)
mass += module.get('mass', 0)
# Specials # Specials
if module['name'] in ['Fuel Tank', 'Cargo Rack']: if module['name'] in ['Fuel Tank', 'Cargo Rack']:
@ -67,6 +76,9 @@ def export(data, filename=None):
else: else:
name = module['name'] name = module['name']
if name == 'Frame Shift Drive':
fsd = module # save for range calculation
for s in slot_map: for s in slot_map:
if slot.lower().startswith(s): if slot.lower().startswith(s):
loadout[slot_map[s]].append(cr + name) loadout[slot_map[s]].append(cr + name)
@ -92,6 +104,20 @@ def export(data, filename=None):
string += '%s: %s\n' % (slot, name) string += '%s: %s\n' % (slot, name)
string += '---\nCargo : %d T\nFuel : %d T\n' % (data['ship']['cargo']['capacity'], data['ship']['fuel']['main']['capacity']) string += '---\nCargo : %d T\nFuel : %d T\n' % (data['ship']['cargo']['capacity'], data['ship']['fuel']['main']['capacity'])
# 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
mass += ships[companion.ship_map[data['ship']['name'].lower()]]['hullMass']
string += 'Mass : %.1f T empty\n %.1f T full\n' % (mass, mass + data['ship']['fuel']['main']['capacity']+ data['ship']['cargo']['capacity'])
multiplier = pow(min(data['ship']['fuel']['main']['capacity'], fsd['maxfuel']) / fsd['fuelmul'], 1.0 / fsd['fuelpower']) * fsd['optmass']
string += 'Range : %.2f LY unladen\n %.2f LY laden\n' % (
multiplier / (mass + data['ship']['fuel']['main']['capacity']),
multiplier / (mass + data['ship']['fuel']['main']['capacity'] + data['ship']['cargo']['capacity']))
except:
if __debug__: raise
if filename: if filename:
with open(filename, 'wt') as h: with open(filename, 'wt') as h:
h.write(string) h.write(string)

BIN
modules.p Normal file

Binary file not shown.

View File

@ -1,3 +1,5 @@
import cPickle
from os.path import join
import time import time
import companion import companion
@ -209,6 +211,10 @@ internal_map = {
} }
# Module mass, FSD data etc
moduledata = cPickle.load(open(join(config.respath, 'modules.p'), 'rb'))
# Given a module description from the Companion API returns a description of the module in the form of a # Given a module description from the Companion API returns a description of the module in the form of a
# dict { category, name, [mount], [guidance], [ship], rating, class } using the same terms found in the # dict { category, name, [mount], [guidance], [ship], rating, class } using the same terms found in the
# English langauge game. For fitted modules, dict also includes { enabled, priority }. # English langauge game. For fitted modules, dict also includes { enabled, priority }.
@ -319,8 +325,19 @@ def lookup(module, ship_map):
if 'on' in module and 'priority' in module: if 'on' in module and 'priority' in module:
new['enabled'], new['priority'] = module['on'], module['priority'] # priority is zero-based new['enabled'], new['priority'] = module['on'], module['priority'] # priority is zero-based
# Extra module data
key = (new['name'], 'ship' in new and companion.ship_map.get(name[0]) or None, new['class'], new['rating'])
if __debug__:
assert key in moduledata, key
m = moduledata.get(key, {})
if new['name'] == 'Frame Shift Drive':
assert 'mass' in m and 'optmass' in m and 'maxfuel' in m and 'fuelmul' in m and 'fuelpower' in m, m
else:
assert 'mass' in m, m
new.update(moduledata.get(key, {}))
# check we've filled out mandatory fields # check we've filled out mandatory fields
for thing in ['category', 'name', 'class', 'rating']: for thing in ['category', 'name', 'class', 'rating']: # Don't consider mass etc as mandatory
if not new.get(thing): raise AssertionError('%s: failed to set %s' % (module['id'], thing)) if not new.get(thing): raise AssertionError('%s: failed to set %s' % (module['id'], thing))
if new['category'] == 'hardpoint' and not new.get('mount'): if new['category'] == 'hardpoint' and not new.get('mount'):
raise AssertionError('%s: failed to set %s' % (module['id'], 'mount')) raise AssertionError('%s: failed to set %s' % (module['id'], 'mount'))

View File

@ -69,7 +69,7 @@ if sys.platform=='darwin':
'frameworks': [ 'Sparkle.framework' ], 'frameworks': [ 'Sparkle.framework' ],
'excludes': [ 'PIL', 'simplejson' ], 'excludes': [ 'PIL', 'simplejson' ],
'iconfile': '%s.icns' % APPNAME, 'iconfile': '%s.icns' % APPNAME,
'resources': ['snd_good.wav', 'snd_bad.wav', 'stations.p', 'systems.p'], 'resources': ['snd_good.wav', 'snd_bad.wav', 'modules.p', 'ships.p', 'stations.p', 'systems.p'],
'semi_standalone': True, 'semi_standalone': True,
'site_packages': False, 'site_packages': False,
'plist': { 'plist': {
@ -106,6 +106,8 @@ elif sys.platform=='win32':
'WinSparkle.pdb', # For debugging - don't include in package 'WinSparkle.pdb', # For debugging - don't include in package
'snd_good.wav', 'snd_good.wav',
'snd_bad.wav', 'snd_bad.wav',
'modules.p',
'ships.p',
'stations.p', 'stations.p',
'systems.p', 'systems.p',
'%s.VisualElementsManifest.xml' % APPNAME, '%s.VisualElementsManifest.xml' % APPNAME,

5
ships.p Normal file
View File

@ -0,0 +1,5 @@
}q(UFederal Assault Ship}qUhullMassqMàsU
Viper MkIVq}qhK¾sUFederal Gunship}qhMDsU Viper MkIIIq}qhK<sUImperial Eagle}q hK2sUImperial Cutter}q
hMLsUEagle}q hK2sUPython}q hM^sUType-6 Transporter}q hKsU Fer-de-Lance}qhKúsUVulture}qhKæsUAdder}qhK#sUType-7 Transporter}qhM¤sUFederal Corvette}qhMsUDiamondback Scout}qhKªsUHauler}qhKsUDiamondback Explorer}qhM*sUFederal Dropship}qhMDsU Cobra MkIIIq}qhK´sUOrca}qhMDsU
Sidewinder}qhKsU Asp Explorer}qhMsU Type-9 Heavy}qhMèsU
Cobra MkIVq}qhKÒsUImperial Courier}qhK#sUAnaconda}q hM<EFBFBD>sUImperial Clipper}q!hM<EFBFBD>sUKeelback}q"hK´sU Asp Scout}q#hKsu.