diff --git a/EDMarketConnector.py b/EDMarketConnector.py index ed0ad063..4b64c30e 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -17,6 +17,7 @@ import companion import bpc import td import eddn +import loadout import prefs from config import appname, applongname, config @@ -174,13 +175,19 @@ class AppWindow: # Validation if not data.get('commander') or not data['commander'].get('name','').strip(): self.status['text'] = "Who are you?!" # Shouldn't happen - elif not data['commander'].get('docked'): - self.status['text'] = "You're not docked at a station!" elif not data.get('lastSystem') or not data['lastSystem'].get('name','').strip() or not data.get('lastStarport') or not data['lastStarport'].get('name','').strip(): self.status['text'] = "Where are you?!" # Shouldn't happen + elif not data.get('ship') or not data['ship'].get('modules') or not data['ship'].get('name','').strip(): + self.status['text'] = "What are you flying?!" # Shouldn't happen + elif not data['commander'].get('docked'): + if config.getint('output') & config.OUT_SHIP: + loadout.export(data) # do loadout even if not docked + self.status['text'] = "You're not docked at a station!" elif not data['lastStarport'].get('commodities'): self.status['text'] = "Station doesn't have a market!" else: + if config.getint('output') & config.OUT_SHIP: + loadout.export(data) if config.getint('output') & config.OUT_CSV: bpc.export(data, True) if config.getint('output') & config.OUT_TD: diff --git a/companion.py b/companion.py index 08884648..8f01fcb6 100644 --- a/companion.py +++ b/companion.py @@ -23,22 +23,51 @@ holdoff = 120 # be nice # Map values reported by the Companion interface to names displayed in-game and recognized by trade tools -categorymap = { 'Narcotics': 'Legal Drugs', - 'Slaves': 'Slavery', - 'NonMarketable': False, } +category_map = { + 'Narcotics' : 'Legal Drugs', + 'Slaves' : 'Slavery', + 'NonMarketable' : False, +} -commoditymap= { 'Agricultural Medicines': 'Agri-Medicines', - 'Ai Relics' : 'AI Relics', - 'Atmospheric Extractors': 'Atmospheric Processors', - 'Auto Fabricators': 'Auto-Fabricators', - 'Basic Narcotics': 'Narcotics', - 'Bio Reducing Lichen': 'Bioreducing Lichen', - 'Hazardous Environment Suits': 'H.E. Suits', - 'Heliostatic Furnaces': 'Microbial Furnaces', - 'Marine Supplies': 'Marine Equipment', - 'Non Lethal Weapons': 'Non-Lethal Weapons', - 'S A P8 Core Container': 'SAP 8 Core Container', - 'Terrain Enrichment Systems': 'Land Enrichment Systems', } +commodity_map= { + 'Agricultural Medicines' : 'Agri-Medicines', + 'Ai Relics' : 'AI Relics', + 'Atmospheric Extractors' : 'Atmospheric Processors', + 'Auto Fabricators' : 'Auto-Fabricators', + 'Basic Narcotics' : 'Narcotics', + 'Bio Reducing Lichen' : 'Bioreducing Lichen', + 'Hazardous Environment Suits' : 'H.E. Suits', + 'Heliostatic Furnaces' : 'Microbial Furnaces', + 'Marine Supplies' : 'Marine Equipment', + 'Non Lethal Weapons' : 'Non-Lethal Weapons', + 'S A P8 Core Container' : 'SAP 8 Core Container', + 'Terrain Enrichment Systems' : 'Land Enrichment Systems', +} + +ship_map = { + 'Adder' : 'Adder', + 'Anaconda' : 'Anaconda', + 'Asp' : 'Asp', + 'CobraMkIII' : 'Cobra Mk III', + 'DiamondBack' : 'Diamondback Scout', + 'DiamondBackXL' : 'Diamondback Explorer', + 'Eagle' : 'Eagle', + 'Empire_Courier' : 'Imperial Courier', + 'Empire_Fighter' : 'Imperial Fighter', + 'Empire_Trader' : 'Imperial Clipper', + 'Federation_Dropship' : 'Federal Dropship', + 'Federation_Fighter' : 'F63 Condor', + 'FerDeLance' : 'Fer-de-Lance', + 'Hauler' : 'Hauler', + 'Orca' : 'Orca', + 'Python' : 'Python', + 'SideWinder' : 'Sidewinder', + 'Type6' : 'Type-6 Transporter', + 'Type7' : 'Type-7 Transporter', + 'Type9' : 'Type-9 Heavy', + 'Viper' : 'Viper', + 'Vulture' : 'Vulture', +} class ServerError(Exception): @@ -165,7 +194,7 @@ class Session: if __debug__: print 'Invalid "%s":"%s" (%s) for "%s"' % (thing, commodity.get(thing), type(commodity.get(thing)), commodity.get('name', '')) break else: - if not categorymap.get(commodity['categoryname'], True): # Check marketable + if not category_map.get(commodity['categoryname'], True): # Check marketable pass elif not commodity.get('categoryname', '').strip(): if __debug__: print 'Missing "categoryname" for "%s"' % commodity.get('name', '') @@ -177,10 +206,10 @@ class Session: if __debug__: print 'Invalid "stockBracket":"%s" for "%s"' % (commodity['stockBracket'], commodity['name']) else: # Rewrite text fields - commodity['categoryname'] = categorymap.get(commodity['categoryname'].strip(), - commodity['categoryname'].strip()) - commodity['name'] = commoditymap.get(commodity['name'].strip(), - commodity['name'].strip()) + commodity['categoryname'] = category_map.get(commodity['categoryname'].strip(), + commodity['categoryname'].strip()) + commodity['name'] = commodity_map.get(commodity['name'].strip(), + commodity['name'].strip()) # Force demand and stock to zero if their corresponding bracket is zero # Fixes spurious "demand": 1 in ED 1.3 diff --git a/config.py b/config.py index 4e5066cb..e69cfff3 100644 --- a/config.py +++ b/config.py @@ -69,6 +69,7 @@ class Config: OUT_BPC = 2 OUT_TD = 4 OUT_CSV = 8 + OUT_SHIP = 16 if platform=='darwin': diff --git a/loadout.py b/loadout.py new file mode 100644 index 00000000..8ca557fe --- /dev/null +++ b/loadout.py @@ -0,0 +1,98 @@ +# Export ship loadout + +from collections import defaultdict +import os +from os.path import join +import re +import time + +from config import config +import outfitting +from companion import ship_map + + +# API slot names to E:D Shipyard slot names +slot_map = { + 'LargeHardpoint' : 'L', + 'MediumHardpoint' : 'M', + 'SmallHardpoint' : 'S', + 'TinyHardpoint' : 'U', + 'Armour' : 'BH', + 'PowerPlant' : 'RB', + 'MainEngines' : 'TM', + 'FrameShiftDrive' : 'FH', + 'LifeSupport' : 'EC', + 'PowerDistributor' : 'PC', + 'Radar' : 'SS', + 'FuelTank' : 'FS', +} + +def export(data): + + def class_rating(module): + if 'guidance' in module: + return module['class'] + module['rating'] + '/' + module.get('mount', 'F')[0] + module['guidance'][0] + ' ' + elif 'mount' in module: + return module['class'] + module['rating'] + '/' + module['mount'][0] + ' ' + else: + return module['class'] + module['rating'] + ' ' + + querytime = config.getint('querytime') or int(time.time()) + + ship = ship_map.get(data['ship']['name'], data['ship']['name']) + + loadout = defaultdict(list) + + for slot in sorted(data['ship']['modules']): + + v = data['ship']['modules'][slot] + if not v or not v.get('module'): + continue + try: + module = outfitting.lookup(v['module']) + if not module: continue + except AssertionError as e: + if __debug__: print 'Loadout: %s' % e + continue # Silently skip unrecognized modules + except: + if __debug__: raise + + cr = class_rating(module) + + # Specials + if module['name'] in ['Fuel Tank', 'Cargo Rack']: + name = '%s (Capacity: %d)' % (module['name'], 2**int(module['class'])) + else: + name = module['name'] + + for s in slot_map: + if slot.startswith(s): + loadout[slot_map[s]].append(cr + name) + break + else: + if slot.startswith('Slot'): + loadout[slot[-1]].append(cr + name) + elif __debug__: print 'Loadout: Unknown slot %s' % slot + + # Construct description + string = '[%s]\n' % ship + for slot in ['L', 'M', 'S', 'U', None, 'BH', 'RB', 'TM', 'FH', 'EC', 'PC', 'SS', 'FS', None, '9', '8', '7', '6', '5', '4', '3', '2', '1']: + if not slot: + string += '\n' + elif slot in loadout: + for name in loadout[slot]: + string += '%s: %s\n' % (slot, name) + string += '---\nCargo : %d T\nFuel : %d T\nPrice : %s CR\n' % (data['ship']['cargo']['capacity'], data['ship']['fuel']['capacity'], '{:,}'.format(data['ship']['value']['total'])) + + # Look for last ship of this type + regexp = re.compile(re.escape(ship) + '\.\d\d\d\d\-\d\d\-\d\dT\d\d\.\d\d\.\d\d\.txt') + 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.txt' % (ship, time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)))) + with open(filename, 'wt') as h: + h.write(string) diff --git a/outfitting.py b/outfitting.py new file mode 100755 index 00000000..eec669f7 --- /dev/null +++ b/outfitting.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# +# Script for building table ID->module mapping table from a dump of the Companion API output +# + +import csv +import json +import os +from os.path import exists, isfile +import sys + +from companion import ship_map + + +outfile = 'outfitting.csv' +outfitting = {} + +armour_map = { + 'Grade1' : 'Lightweight Alloy', + 'Grade2' : 'Reinforced Alloy', + 'Grade3' : 'Military Grade Composite', + 'Mirrored' : 'Mirrored Surface Composite', + 'Reactive' : 'Reactive Surface Composite', +} + +weapon_map = { + 'AdvancedTorpPylon' : 'Torpedo Pylon', + 'BasicMissileRack' : 'Missile Rack', + 'BeamLaser' : 'Beam Laser', + 'Cannon' : 'Cannon', + 'DumbfireMissileRack' : 'Missile Rack', + 'MineLauncher' : 'Mine Launcher', + 'MiningLaser' : 'Mining Laser', + 'MultiCannon' : 'Multi-Cannon', + 'PlasmaAccelerator' : 'Plasma Accelerator', + 'PulseLaser' : 'Pulse Laser', + 'PulseLaserBurst' : 'Burst Laser', + 'Railgun' : 'Rail Gun', + 'Slugshot' : 'Fragment Cannon', +} + +missiletype_map = { + 'AdvancedTorpPylon' : 'Seeker', + 'BasicMissileRack' : 'Seeker', + 'DumbfireMissileRack' : 'Dumbfire', +} + +weaponmount_map = { + 'Fixed' : 'Fixed', + 'Gimbal' : 'Gimballed', + 'Turret' : 'Turreted', +} + +weaponclass_map = { + 'Tiny' : '0', + 'Small' : '1', + 'Medium' : '2', + 'Large' : '3', + 'Huge' : '4', +} + +# There's no discernable pattern for weapon ratings, so here's a lookup table +weaponrating_map = { + 'Hpt_AdvancedTorpPylon_Fixed_Small': 'I', + 'Hpt_AdvancedTorpPylon_Fixed_Medium': 'I', + 'Hpt_BasicMissileRack_Fixed_Small': 'B', + 'Hpt_BasicMissileRack_Fixed_Medium': 'B', + 'Hpt_BeamLaser_Fixed_Small': 'E', + 'Hpt_BeamLaser_Fixed_Medium': 'D', + 'Hpt_BeamLaser_Fixed_Large': 'C', + 'Hpt_BeamLaser_Gimbal_Small': 'E', + 'Hpt_BeamLaser_Gimbal_Medium': 'D', + 'Hpt_BeamLaser_Gimbal_Large': 'C', + 'Hpt_BeamLaser_Turret_Small': 'F', + 'Hpt_BeamLaser_Turret_Medium': 'E', + 'Hpt_BeamLaser_Turret_Large': 'D', + 'Hpt_Cannon_Fixed_Small': 'D', + 'Hpt_Cannon_Fixed_Medium': 'D', + 'Hpt_Cannon_Fixed_Large': 'C', + 'Hpt_Cannon_Fixed_Huge': 'B', + 'Hpt_Cannon_Gimbal_Small': 'E', + 'Hpt_Cannon_Gimbal_Medium': 'D', + 'Hpt_Cannon_Gimbal_Large': 'C', + 'Hpt_Cannon_Gimbal_Huge': 'B', + 'Hpt_Cannon_Turret_Small': 'F', + 'Hpt_Cannon_Turret_Medium': 'E', + 'Hpt_Cannon_Turret_Large': 'D', + 'Hpt_DumbfireMissileRack_Fixed_Small': 'B', + 'Hpt_DumbfireMissileRack_Fixed_Medium': 'B', + 'Hpt_MineLauncher_Fixed_Small': 'I', + 'Hpt_MineLauncher_Fixed_Medium': 'I', + 'Hpt_MiningLaser_Fixed_Small': 'D', + 'Hpt_MiningLaser_Fixed_Medium': 'D', + 'Hpt_MultiCannon_Fixed_Small': 'F', + 'Hpt_MultiCannon_Fixed_Medium': 'E', + 'Hpt_MultiCannon_Gimbal_Small': 'G', + 'Hpt_MultiCannon_Gimbal_Medium': 'F', + 'Hpt_MultiCannon_Turret_Small': 'G', + 'Hpt_MultiCannon_Turret_Medium': 'F', + 'Hpt_PlasmaAccelerator_Fixed_Medium': 'C', + 'Hpt_PlasmaAccelerator_Fixed_Large': 'B', + 'Hpt_PlasmaAccelerator_Fixed_Huge': 'A', + 'Hpt_PulseLaser_Fixed_Small': 'F', + 'Hpt_PulseLaser_Fixed_Medium': 'E', + 'Hpt_PulseLaser_Fixed_Large': 'D', + 'Hpt_PulseLaser_Gimbal_Small': 'G', + 'Hpt_PulseLaser_Gimbal_Medium': 'F', + 'Hpt_PulseLaser_Gimbal_Large': 'E', + 'Hpt_PulseLaser_Turret_Small': 'G', + 'Hpt_PulseLaser_Turret_Medium': 'F', + 'Hpt_PulseLaser_Turret_Large': 'F', + 'Hpt_PulseLaserBurst_Fixed_Small': 'F', + 'Hpt_PulseLaserBurst_Fixed_Medium': 'E', + 'Hpt_PulseLaserBurst_Fixed_Large': 'D', + 'Hpt_PulseLaserBurst_Gimbal_Small': 'G', + 'Hpt_PulseLaserBurst_Gimbal_Medium': 'F', + 'Hpt_PulseLaserBurst_Gimbal_Large': 'E', + 'Hpt_PulseLaserBurst_Turret_Small': 'G', + 'Hpt_PulseLaserBurst_Turret_Medium': 'F', + 'Hpt_PulseLaserBurst_Turret_Large': 'E', + 'Hpt_Railgun_Fixed_Small': 'D', + 'Hpt_Railgun_Fixed_Medium': 'B', + 'Hpt_Slugshot_Fixed_Small': 'E', + 'Hpt_Slugshot_Fixed_Medium': 'A', + 'Hpt_Slugshot_Fixed_Large': 'C', + 'Hpt_Slugshot_Gimbal_Small': 'E', + 'Hpt_Slugshot_Gimbal_Medium': 'D', + 'Hpt_Slugshot_Gimbal_Large': 'C', + 'Hpt_Slugshot_Turret_Small': 'E', + 'Hpt_Slugshot_Turret_Medium': 'D', + 'Hpt_Slugshot_Turret_Large': 'C', +} + +utility_map = { + 'CargoScanner' : 'Cargo Scanner', + 'ChaffLauncher' : 'Chaff Launcher', + 'CloudScanner' : 'Frame Shift Wake Scanner', + 'CrimeScanner' : 'Kill Warrant Scanner', + 'ElectronicCountermeasure' : 'Electronic Countermeasure', + 'HeatSinkLauncher' : 'Heat Sink Launcher', + 'PlasmaPointDefence' : 'Point Defence', + 'ShieldBooster' : 'Shield Booster', +} + +rating_map = { + '1': 'E', + '2': 'D', + '3': 'C', + '4': 'B', + '5': 'A', +} + +standard_map = { + 'Armour' : 'Bulkheads', + 'Engine' : 'Thrusters', + 'FuelTank' : 'Fuel Tank', + 'Hyperdrive' : 'Frame Shift Drive', + 'LifeSupport' : 'Life Support', + 'PowerDistributor' : 'Power Distributor', + 'Powerplant' : 'Power Plant', + 'Sensors' : 'Sensors', +} + + +stellar_map = { + 'Standard' : ('Basic Discovery Scanner', 'E'), + 'Intermediate' : ('Intermediate Discovery Scanner', 'D'), + 'Advanced' : ('Advanced Discovery Scanner', 'C'), + 'Tiny' : ('Detailed Surface Scanner', 'C'), +} + +internal_map = { + 'CargoRack' : 'Cargo Rack', + 'Collection' : 'Collector Limpet Controller', + 'FSDInterdictor' : 'Frame Shift Drive Interdictor', + 'FuelScoop' : 'Fuel Scoop', + 'FuelTransfer' : 'Fuel Transfer Limpet Controller', + 'HullReinforcement' : 'Hull Reinforcement Package', + 'Prospector' : 'Prospector Limpet Controller', + 'Refinery' : 'Refinery', + 'Repairer' : 'Auto Field-Maintenance Unit', + 'ResourceSiphon' : 'Hatch Breaker Limpet Controller', + 'ShieldCellBank' : 'Shield Cell Bank', + 'ShieldGenerator' : 'Shield Generator', +} + + +# 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. +# Or returns None if the module is user-specific (i.e. decal, paintjob). +# (Given the ad-hocery in this implementation a big lookup table might have been simpler and clearer). +def lookup(module): + + # if not module.get('category'): raise AssertionError('%s: Missing category' % module['id']) # only present post 1.3, and not present in ship loadout + if not module.get('name'): raise AssertionError('%s: Missing name' % module['id']) + + name = module['name'].split('_') + new = {} + + # Armour - e.g. Federation_Dropship_Armour_Grade2 + if name[-2] == 'Armour': + name = module['name'].rsplit('_', 2) # Armour is ship-specific, and ship names can have underscores + new['category'] = 'standard' + new['name'] = armour_map[name[2]] + new['ship'] = ship_map.get(name[0], name[0]) + new['class'] = '1' + new['rating'] = 'I' + + # Skip uninteresting stuff + elif name[0].lower() in ['decal', 'paintjob']: # Have seen "paintjob" and "PaintJob" + return None + + # Shouldn't be listing player-specific paid stuff + elif module.get('sku'): + raise AssertionError('%s: Unexpected sku "%s"' % (module['id'], module['sku'])) + + # Hardpoints - e.g. Hpt_Slugshot_Fixed_Medium + elif name[0]=='Hpt' and name[1] in weapon_map: + if name[2] not in weaponmount_map: raise AssertionError('%s: Unknown weapon mount "%s"' % (module['id'], name[2])) + if name[3] not in weaponclass_map: raise AssertionError('%s: Unknown weapon class "%s"' % (module['id'], name[3])) + # if module['name'] not in weaponrating_map: raise AssertionError('%s: Unknown rating for this weapon' % module['id']) + new['category'] = 'hardpoint' + new['name'] = weapon_map[name[1]] + new['mount'] = weaponmount_map[name[2]] + if name[1] in missiletype_map: # e.g. Hpt_DumbfireMissileRack_Fixed_Small + new['guidance'] = missiletype_map[name[1]] + new['class'] = weaponclass_map[name[3]] + new['rating'] = weaponrating_map.get(module['name'], '?') # no obvious rule - needs lookup table + + # Utility - e.g. Hpt_CargoScanner_Size0_Class1 + elif name[0]=='Hpt' and name[1] in utility_map: + new['category'] = 'utility' + new['name'] = utility_map[name[1]] + if name[-1] in weaponclass_map: # e.g. Hpt_PlasmaPointDefence_Turret_Tiny + new['class'] = weaponclass_map[name[-1]] + new['rating'] = 'I' + else: + if not name[2].startswith('Size') or not name[3].startswith('Class'): raise AssertionError('%s: Unknown class/rating "%s/%s"' % (module['id'], name[2], name[3])) + new['class'] = name[2][4:] + new['rating'] = rating_map[name[3][5:]] + + elif name[0]=='Hpt': + raise AssertionError('%s: Unknown weapon "%s"' % (module['id'], name[1])) + + # Stellar scanners - e.g. Int_StellarBodyDiscoveryScanner_Standard + elif name[1] in ['StellarBodyDiscoveryScanner', 'DetailedSurfaceScanner']: + new['category'] = 'internal' + new['name'], new['rating'] = stellar_map[name[2]] + new['class'] = '1' + + # Docking Computer - e.g. Int_DockingComputer_Standard + elif name[1] == 'DockingComputer' and name[2] == 'Standard': + new['category'] = 'internal' + new['name'] = 'Standard Docking Computer' + new['class'] = '1' + new['rating'] = 'E' + + # Standard & Internal + else: + # Reported category is not necessarily helpful. e.g. "Int_DockingComputer_Standard" has category "utility" + if name[0] != 'Int': raise AssertionError('%s: Unknown prefix "%s"' % (module['id'], name[0])) + + if name[1] == 'DroneControl': # e.g. Int_DroneControl_Collection_Size1_Class1 + name.pop(0) + + if name[1] in standard_map: # e.g. Int_Engine_Size2_Class1 + new['category'] = 'standard' + new['name'] = standard_map[name[1]] + elif name[1] in internal_map: # e.g. Int_CargoRack_Size8_Class1 + new['category'] = 'internal' + new['name'] = internal_map[name[1]] + else: + raise AssertionError('%s: Unknown module "%s"' % (module['id'], name[1])) + + if not name[2].startswith('Size') or not name[3].startswith('Class'): raise AssertionError('%s: Unknown class/rating "%s/%s"' % (module['id'], name[2], name[3])) + new['class'] = name[2][4:] + new['rating'] = rating_map[name[3][5:]] + + # check we've filled out mandatory fields + for thing in ['category', 'name', 'class', 'rating']: + if not new.get('name'): raise AssertionError('%s: failed to set %s' % (module['id'], thing)) + + return new + + +# add all the modules +def addmodules(data): + if not data.get('lastStarport'): + print 'No Starport!' + return + elif not data['lastStarport'].get('modules'): + print 'No outfitting here' + return + + # read into outfitting + if isfile(outfile): + with open(outfile) as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + key = int(row.pop('id')) # index by int for easier lookup and sorting + outfitting[key] = row + size_pre = len(outfitting) + + for key,module in data['lastStarport'].get('modules').iteritems(): + # sanity check + if int(key) != module.get('id'): raise AssertionError('id: %s!=%s' % (key, module['id'])) + new = lookup(module) + if new: + old = outfitting.get(int(key)) + if old: + # check consistency with existing data + for thing in ['category', 'name', 'mount', 'guidance', 'ship', 'class', 'rating']: + if new.get(thing,'') != old.get(thing): raise AssertionError('%s: %s "%s"!="%s"' % (key, thing, new.get(thing), old.get(thing))) + else: + outfitting[int(key)] = new + + if len(outfitting) > size_pre: + + if isfile(outfile): + if isfile(outfile+'.bak'): + os.unlink(outfile+'.bak') + os.rename(outfile, outfile+'.bak') + + with open(outfile, 'wb') as csvfile: + writer = csv.DictWriter(csvfile, ['id', 'category', 'name', 'mount', 'guidance', 'ship', 'class', 'rating']) + writer.writeheader() + for key in sorted(outfitting): + row = outfitting[key] + row['id'] = key + writer.writerow(row) + + print 'Added %d new modules' % (len(outfitting) - size_pre) + + else: + print + +if __name__ == "__main__": + if len(sys.argv) <= 1: + print 'Usage: outfitting.py [dump.json]' + else: + # read from dumped json file(s) + for f in sys.argv[1:]: + with open(f) as h: + print f, + addmodules(json.loads(h.read())) diff --git a/prefs.py b/prefs.py index d0b64b62..68831974 100644 --- a/prefs.py +++ b/prefs.py @@ -76,8 +76,8 @@ class PreferencesDialog(tk.Toplevel): outframe.grid(padx=10, pady=10, sticky=tk.NSEW) outframe.columnconfigure(0, weight=1) - output = config.getint('output') or config.OUT_EDDN - ttk.Label(outframe, text="Please choose where you want the market data saved").grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W) + output = config.getint('output') or (config.OUT_EDDN | config.OUT_SHIP) + ttk.Label(outframe, text="Please choose where you want the data saved").grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W) self.out_eddn= tk.IntVar(value = (output & config.OUT_EDDN) and 1 or 0) ttk.Checkbutton(outframe, text="Online to the Elite Dangerous Data Network (EDDN)", variable=self.out_eddn).grid(row=1, columnspan=2, padx=5, sticky=tk.W) self.out_bpc = tk.IntVar(value = (output & config.OUT_BPC ) and 1 or 0) @@ -86,12 +86,14 @@ class PreferencesDialog(tk.Toplevel): ttk.Checkbutton(outframe, text="Offline in Trade Dangerous format", variable=self.out_td, command=self.outvarchanged).grid(row=3, columnspan=2, padx=5, sticky=tk.W) self.out_csv = tk.IntVar(value = (output & config.OUT_CSV ) and 1 or 0) ttk.Checkbutton(outframe, text="Offline in CSV format", variable=self.out_csv, command=self.outvarchanged).grid(row=4, columnspan=2, padx=5, sticky=tk.W) - ttk.Label(outframe, text=(platform=='darwin' and 'Where:' or 'File location:')).grid(row=5, padx=5, pady=(5,0), sticky=tk.NSEW) + self.out_ship= tk.IntVar(value = (output & config.OUT_SHIP) and 1 or 0) + ttk.Checkbutton(outframe, text="Offline loadout in E:D Shipyard format", variable=self.out_ship, command=self.outvarchanged).grid(row=5, columnspan=2, padx=5, sticky=tk.W) + ttk.Label(outframe, text=(platform=='darwin' and 'Where:' or 'File location:')).grid(row=6, padx=5, pady=(5,0), sticky=tk.NSEW) self.outbutton = ttk.Button(outframe, text=(platform=='darwin' and 'Change...' or 'Browse...'), command=self.outbrowse) - self.outbutton.grid(row=5, column=1, padx=5, pady=(5,0), sticky=tk.NSEW) + self.outbutton.grid(row=6, column=1, padx=5, pady=(5,0), sticky=tk.NSEW) self.outdir = ttk.Entry(outframe) self.outdir.insert(0, config.get('outdir')) - self.outdir.grid(row=6, columnspan=2, padx=5, pady=5, sticky=tk.EW) + self.outdir.grid(row=7, columnspan=2, padx=5, pady=5, sticky=tk.EW) self.outvarchanged() privacyframe = ttk.LabelFrame(frame, text='Privacy') @@ -118,7 +120,7 @@ class PreferencesDialog(tk.Toplevel): #self.wait_window(self) # causes duplicate events on OSX def outvarchanged(self): - local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() + local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() or self.out_ship.get() self.outbutton['state'] = local and tk.NORMAL or tk.DISABLED self.outdir['state'] = local and 'readonly' or tk.DISABLED @@ -157,7 +159,7 @@ class PreferencesDialog(tk.Toplevel): credentials = (config.get('username'), config.get('password')) config.set('username', self.username.get().strip()) config.set('password', self.password.get().strip()) - config.set('output', (self.out_eddn.get() and config.OUT_EDDN or 0) + (self.out_bpc.get() and config.OUT_BPC or 0) + (self.out_td.get() and config.OUT_TD or 0) + (self.out_csv.get() and config.OUT_CSV or 0)) + config.set('output', (self.out_eddn.get() and config.OUT_EDDN or 0) + (self.out_bpc.get() and config.OUT_BPC or 0) + (self.out_td.get() and config.OUT_TD or 0) + (self.out_csv.get() and config.OUT_CSV or 0) + (self.out_ship.get() and config.OUT_SHIP or 0)) config.set('outdir', self.outdir.get().strip()) config.set('anonymous', self.out_anon.get()) self.destroy()