1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-13 07:47:14 +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.
* 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
--------
@ -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).
* 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
* 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
from collections import OrderedDict
import cPickle
import json
import os
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):
querytime = config.getint('querytime') or int(time.time())
@ -98,6 +103,8 @@ def export(data, filename=None):
])),
])
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"
@ -123,6 +130,7 @@ def export(data, filename=None):
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'])),
@ -138,6 +146,8 @@ def export(data, filename=None):
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:
@ -173,8 +183,30 @@ def export(data, filename=None):
('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)
string = json.dumps(loadout, indent=2, separators=(',', ': '))
if filename:
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))
# 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__":
import json
# still send market and outfitting for currently "suspended" stations -
# 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
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
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:
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])
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):
# *Do* send empty modules list - implies station has no outfitting
schemakeys = ['category', 'name', 'mount', 'guidance', 'ship', 'class', 'rating']
modules = []
for v in data['lastStarport'].get('modules', {}).itervalues():
try:
module = outfitting.lookup(v, ship_map)
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:
if __debug__: print 'Outfitting: %s' % e # Silently skip unrecognized modules
except:

View File

@ -1,6 +1,7 @@
# Export ship loadout in E:D Shipyard format
from collections import defaultdict
import cPickle
import os
from os.path import join
import re
@ -36,6 +37,11 @@ slot_map = {
'fueltank' : 'FS',
}
# Ship masses
ships = cPickle.load(open(join(config.respath, 'ships.p'), 'rb'))
def export(data, filename=None):
def class_rating(module):
@ -49,6 +55,8 @@ def export(data, filename=None):
querytime = config.getint('querytime') or int(time.time())
loadout = defaultdict(list)
mass = 0.0
fsd = None
for slot in sorted(data['ship']['modules']):
@ -60,6 +68,7 @@ def export(data, filename=None):
if not module: continue
cr = class_rating(module)
mass += module.get('mass', 0)
# Specials
if module['name'] in ['Fuel Tank', 'Cargo Rack']:
@ -67,6 +76,9 @@ def export(data, filename=None):
else:
name = module['name']
if name == 'Frame Shift Drive':
fsd = module # save for range calculation
for s in slot_map:
if slot.lower().startswith(s):
loadout[slot_map[s]].append(cr + name)
@ -92,6 +104,20 @@ def export(data, filename=None):
string += '%s: %s\n' % (slot, name)
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:
with open(filename, 'wt') as h:
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 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
# 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 }.
@ -319,8 +325,19 @@ def lookup(module, ship_map):
if 'on' in module and 'priority' in module:
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
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 new['category'] == 'hardpoint' and not new.get('mount'):
raise AssertionError('%s: failed to set %s' % (module['id'], 'mount'))

View File

@ -69,7 +69,7 @@ if sys.platform=='darwin':
'frameworks': [ 'Sparkle.framework' ],
'excludes': [ 'PIL', 'simplejson' ],
'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,
'site_packages': False,
'plist': {
@ -106,6 +106,8 @@ elif sys.platform=='win32':
'WinSparkle.pdb', # For debugging - don't include in package
'snd_good.wav',
'snd_bad.wav',
'modules.p',
'ships.p',
'stations.p',
'systems.p',
'%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.