mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-14 08:17:13 +03:00
Merge branch 'beyond'
This commit is contained in:
commit
c79398eb12
@ -57,6 +57,7 @@ import prefs
|
||||
import plug
|
||||
from hotkey import hotkeymgr
|
||||
from monitor import monitor
|
||||
from dashboard import dashboard
|
||||
from theme import theme
|
||||
|
||||
|
||||
@ -269,6 +270,7 @@ class AppWindow:
|
||||
self.w.bind('<KP_Enter>', self.getandsend)
|
||||
self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring
|
||||
self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring
|
||||
self.w.bind_all('<<DashboardEvent>>', self.dashboard_event) # Dashboard monitoring
|
||||
self.w.bind_all('<<PluginError>>', self.plugin_error) # Statusbar
|
||||
self.w.bind_all('<<Quit>>', self.onexit) # Updater
|
||||
|
||||
@ -627,6 +629,11 @@ class AppWindow:
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
|
||||
if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started:
|
||||
# Can start dashboard monitoring
|
||||
if not dashboard.start(self.w, monitor.started):
|
||||
print "Can't start Status monitoring"
|
||||
|
||||
# Don't send to EDDN while on crew
|
||||
if monitor.state['Captain']:
|
||||
return
|
||||
@ -678,6 +685,17 @@ class AppWindow:
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
|
||||
# Handle Status event
|
||||
def dashboard_event(self, event):
|
||||
entry = dashboard.status
|
||||
if entry:
|
||||
# Currently we don't do anything with these events
|
||||
err = plug.notify_dashboard_entry(monitor.cmdr, monitor.is_beta, entry)
|
||||
if err:
|
||||
self.status['text'] = err
|
||||
if not config.getint('hotkey_mute'):
|
||||
hotkeymgr.play_bad()
|
||||
|
||||
# Display asynchronous error from plugin
|
||||
def plugin_error(self, event=None):
|
||||
if plug.last_error.get('msg'):
|
||||
@ -691,6 +709,14 @@ class AppWindow:
|
||||
if not monitor.cmdr or not monitor.mode:
|
||||
return False # In CQC - do nothing
|
||||
|
||||
if config.getint('shipyard') == config.SHIPYARD_EDSHIPYARD:
|
||||
return edshipyard.url(monitor.is_beta)
|
||||
elif config.getint('shipyard') == config.SHIPYARD_CORIOLIS:
|
||||
pass # Fall through
|
||||
else:
|
||||
assert False, config.getint('shipyard')
|
||||
return False
|
||||
|
||||
self.status['text'] = _('Fetching data...')
|
||||
self.w.update_idletasks()
|
||||
try:
|
||||
@ -716,13 +742,7 @@ class AppWindow:
|
||||
self.status['text'] = _('Error: Frontier server is lagging') # Raised when Companion API server is returning old data, e.g. when the servers are too busy
|
||||
else:
|
||||
self.status['text'] = ''
|
||||
if config.getint('shipyard') == config.SHIPYARD_EDSHIPYARD:
|
||||
return edshipyard.url(data, monitor.is_beta)
|
||||
elif config.getint('shipyard') == config.SHIPYARD_CORIOLIS:
|
||||
return coriolis.url(data, monitor.is_beta)
|
||||
else:
|
||||
assert False, config.getint('shipyard')
|
||||
return False
|
||||
return coriolis.url(data, monitor.is_beta)
|
||||
|
||||
def cooldown(self):
|
||||
if time() < self.holdofftime:
|
||||
@ -779,6 +799,7 @@ class AppWindow:
|
||||
config.set('geometry', '+{1}+{2}'.format(*self.w.geometry().split('+')))
|
||||
self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen
|
||||
hotkeymgr.unregister()
|
||||
dashboard.close()
|
||||
monitor.close()
|
||||
plug.notify_stop()
|
||||
self.eddn.close()
|
||||
|
14
PLUGINS.md
14
PLUGINS.md
@ -106,7 +106,7 @@ this.status["text"] = "Happy!"
|
||||
|
||||
## Events
|
||||
|
||||
Once you have created your plugin and EDMC has loaded it there are two other functions you can define to be notified by EDMC when something happens: `journal_entry()` and `cmdr_data()`.
|
||||
Once you have created your plugin and EDMC has loaded it there are three other functions you can define to be notified by EDMC when something happens: `journal_entry()`, `status()` and `cmdr_data()`.
|
||||
|
||||
Your events all get called on the main tkinter loop so be sure not to block for very long or the EDMC will appear to freeze. If you have a long running operation then you should take a look at how to do background updates in tkinter - http://effbot.org/zone/tkinter-threads.htm
|
||||
|
||||
@ -128,6 +128,16 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
sys.stderr.write("Arrived at {}\n".format(entry['StarSystem']))
|
||||
```
|
||||
|
||||
### Player Dashboard
|
||||
|
||||
This gets called when something on the player's cockpit display changes - typically about once a second when in orbital flight
|
||||
|
||||
```python
|
||||
def dashboard_entry(cmdr, is_beta, entry):
|
||||
deployed = entry['Flags'] & 1<<6
|
||||
sys.stderr.write("Hardpoints {}\n", deployed and "deployed" or "stowed")
|
||||
```
|
||||
|
||||
### Getting Commander Data
|
||||
|
||||
This gets called when EDMC has just fetched fresh Cmdr and station data from Frontier's servers.
|
||||
@ -144,7 +154,7 @@ The data is a dictionary and full of lots of wonderful stuff!
|
||||
|
||||
## Error messages
|
||||
|
||||
You can display an error in EDMC's status area by returning a string from your `journal_entry()` or `cmdr_data()` function, or asynchronously (e.g. from a "worker" thread that is performing a long running operation) by calling `plug.show_error()`. Either method will cause the "bad" sound to be played (unless the user has muted sound).
|
||||
You can display an error in EDMC's status area by returning a string from your `journal_entry()`, `status_entry()` or `cmdr_data()` function, or asynchronously (e.g. from a "worker" thread that is performing a long running operation) by calling `plug.show_error()`. Either method will cause the "bad" sound to be played (unless the user has muted sound).
|
||||
|
||||
The status area is shared between EDMC itself and all other plugins, so your message won't be displayed for very long. Create a dedicated widget if you need to display routine status information.
|
||||
|
||||
|
@ -73,6 +73,7 @@ ship_map = {
|
||||
'type7' : 'Type-7 Transporter',
|
||||
'type9' : 'Type-9 Heavy',
|
||||
'type9_military' : 'Type-10 Defender',
|
||||
'typex' : 'Alliance Chieftain',
|
||||
'viper' : 'Viper MkIII',
|
||||
'viper_mkiv' : 'Viper MkIV',
|
||||
'vulture' : 'Vulture',
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a3ae3e34ad879d1504a7d2fdc3fce4854820051b
|
||||
Subproject commit 44d57f26a8099cfa8d91815f453c7bba303a63ec
|
@ -169,11 +169,16 @@ if __name__ == "__main__":
|
||||
else:
|
||||
modules[key] = { 'mass': m.get('mass', 0) } # Some modules don't have mass
|
||||
|
||||
# 2.5 additions not yet present in coriolis-data
|
||||
# 3.0 additions not yet present in coriolis-data
|
||||
modules[('Decontamination Limpet Controller', None, '1', 'E')] = {'mass': 1.3}
|
||||
modules[('Decontamination Limpet Controller', None, '3', 'E')] = {'mass': 2}
|
||||
modules[('Decontamination Limpet Controller', None, '5', 'E')] = {'mass': 20}
|
||||
modules[('Decontamination Limpet Controller', None, '7', 'E')] = {'mass': 128}
|
||||
modules[('Recon Limpet Controller', None, '1', 'E')] = {'mass': 1.3}
|
||||
modules[('Recon Limpet Controller', None, '3', 'E')] = {'mass': 2}
|
||||
modules[('Recon Limpet Controller', None, '5', 'E')] = {'mass': 20}
|
||||
modules[('Recon Limpet Controller', None, '7', 'E')] = {'mass': 128}
|
||||
modules[('Research Limpet Controller', None, '1', 'E')] = {'mass': 1.3}
|
||||
|
||||
modules = OrderedDict([(k,modules[k]) for k in sorted(modules)]) # sort for easier diffing
|
||||
cPickle.dump(modules, open('modules.p', 'wb'))
|
||||
|
131
dashboard.py
Normal file
131
dashboard.py
Normal file
@ -0,0 +1,131 @@
|
||||
import json
|
||||
from calendar import timegm
|
||||
from operator import itemgetter
|
||||
from os import listdir, stat
|
||||
from os.path import isdir, isfile, join
|
||||
from sys import platform
|
||||
import time
|
||||
|
||||
if __debug__:
|
||||
from traceback import print_exc
|
||||
|
||||
from config import config
|
||||
|
||||
|
||||
if platform=='darwin':
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
elif platform=='win32':
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
else:
|
||||
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
||||
FileSystemEventHandler = object # dummy
|
||||
|
||||
|
||||
# Status.json handler
|
||||
class Dashboard(FileSystemEventHandler):
|
||||
|
||||
_POLL = 1 # Fallback polling interval
|
||||
|
||||
def __init__(self):
|
||||
FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog
|
||||
self.root = None
|
||||
self.currentdir = None # The actual logdir that we're monitoring
|
||||
self.observer = None
|
||||
self.observed = None # a watchdog ObservedWatch, or None if polling
|
||||
self.status = {} # Current status for communicating status back to main thread
|
||||
|
||||
def start(self, root, started):
|
||||
self.root = root
|
||||
self.session_start = started
|
||||
|
||||
logdir = config.get('journaldir') or config.default_journal_dir
|
||||
if not logdir or not isdir(logdir):
|
||||
self.stop()
|
||||
return False
|
||||
|
||||
if self.currentdir and self.currentdir != logdir:
|
||||
self.stop()
|
||||
self.currentdir = logdir
|
||||
|
||||
# Set up a watchdog observer.
|
||||
# File system events are unreliable/non-existent over network drives on Linux.
|
||||
# We can't easily tell whether a path points to a network drive, so assume
|
||||
# any non-standard logdir might be on a network drive and poll instead.
|
||||
polling = bool(config.get('journaldir')) and platform != 'win32'
|
||||
if not polling and not self.observer:
|
||||
self.observer = Observer()
|
||||
self.observer.daemon = True
|
||||
self.observer.start()
|
||||
elif polling and self.observer:
|
||||
self.observer.stop()
|
||||
self.observer = None
|
||||
|
||||
if not self.observed and not polling:
|
||||
self.observed = self.observer.schedule(self, self.currentdir)
|
||||
|
||||
if __debug__:
|
||||
print '%s Dashboard "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir)
|
||||
|
||||
# Even if we're not intending to poll, poll at least once to process pre-existing
|
||||
# data and to check whether the watchdog thread has crashed due to events not
|
||||
# being supported on this filesystem.
|
||||
self.root.after(self._POLL * 1000/2, self.poll, True)
|
||||
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
if __debug__:
|
||||
print 'Stopping monitoring Dashboard'
|
||||
self.currentdir = None
|
||||
if self.observed:
|
||||
self.observed = None
|
||||
self.observer.unschedule_all()
|
||||
self.status = {}
|
||||
|
||||
def close(self):
|
||||
self.stop()
|
||||
if self.observer:
|
||||
self.observer.stop()
|
||||
if self.observer:
|
||||
self.observer.join()
|
||||
self.observer = None
|
||||
|
||||
def poll(self, first_time=False):
|
||||
if not self.currentdir:
|
||||
# Stopped
|
||||
self.status = {}
|
||||
else:
|
||||
self.process()
|
||||
|
||||
if first_time:
|
||||
# Watchdog thread
|
||||
emitter = self.observed and self.observer._emitter_for_watch[self.observed] # Note: Uses undocumented attribute
|
||||
if emitter and emitter.is_alive():
|
||||
return # Watchdog thread still running - stop polling
|
||||
|
||||
self.root.after(self._POLL * 1000, self.poll) # keep polling
|
||||
|
||||
def on_modified(self, event):
|
||||
# watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows
|
||||
if event.is_directory or (isfile(event.src_path) and stat(event.src_path).st_size): # Can get on_modified events when the file is emptied
|
||||
self.process(event.src_path if not event.is_directory else None)
|
||||
|
||||
# Can be called either in watchdog thread or, if polling, in main thread.
|
||||
def process(self, logfile=None):
|
||||
try:
|
||||
with open(join(self.currentdir, 'Status.json'), 'rb') as h:
|
||||
entry = json.load(h)
|
||||
|
||||
# Status file is shared between beta and live. So filter out status not in this game session.
|
||||
if timegm(time.strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) >= self.session_start and self.status != entry:
|
||||
self.status = entry
|
||||
self.root.event_generate('<<DashboardEvent>>', when="tail")
|
||||
except:
|
||||
if __debug__: print_exc()
|
||||
|
||||
# singleton
|
||||
dashboard = Dashboard()
|
@ -13,6 +13,7 @@ import gzip
|
||||
from config import config
|
||||
import companion
|
||||
import outfitting
|
||||
from monitor import monitor
|
||||
|
||||
# Map API ship names to E:D Shipyard ship names
|
||||
ship_map = dict(companion.ship_map)
|
||||
@ -162,9 +163,11 @@ def export(data, filename=None):
|
||||
|
||||
|
||||
# Return a URL for the current ship
|
||||
def url(data, is_beta):
|
||||
def url(is_beta):
|
||||
|
||||
string = json.dumps(companion.ship(data), ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode('utf-8') # most compact representation
|
||||
string = json.dumps(monitor.ship(), ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode('utf-8') # most compact representation
|
||||
if not string:
|
||||
return False
|
||||
|
||||
out = StringIO.StringIO()
|
||||
with gzip.GzipFile(fileobj=out, mode='w') as f:
|
||||
|
164
monitor.py
164
monitor.py
@ -113,14 +113,19 @@ class EDLogs(FileSystemEventHandler):
|
||||
'Raw' : defaultdict(int),
|
||||
'Manufactured' : defaultdict(int),
|
||||
'Encoded' : defaultdict(int),
|
||||
'PaintJob' : None,
|
||||
'Rank' : { 'Combat': None, 'Trade': None, 'Explore': None, 'Empire': None, 'Federation': None, 'CQC': None },
|
||||
'Rank' : {},
|
||||
'Reputation' : {},
|
||||
'Statistics' : {},
|
||||
'Role' : None, # Crew role - None, Idle, FireCon, FighterCon
|
||||
'Friends' : set(), # Online friends
|
||||
'ShipID' : None,
|
||||
'ShipIdent' : None,
|
||||
'ShipName' : None,
|
||||
'ShipType' : None,
|
||||
'HullValue' : None,
|
||||
'ModulesValue' : None,
|
||||
'Rebuy' : None,
|
||||
'Modules' : None,
|
||||
}
|
||||
|
||||
def start(self, root):
|
||||
@ -224,24 +229,18 @@ class EDLogs(FileSystemEventHandler):
|
||||
if self.live:
|
||||
if self.game_was_running:
|
||||
# Game is running locally
|
||||
entry = OrderedDict([
|
||||
('timestamp', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())),
|
||||
('event', 'StartUp'),
|
||||
('StarSystem', self.system),
|
||||
('StarPos', self.coordinates),
|
||||
])
|
||||
if self.planet:
|
||||
entry['Body'] = self.planet
|
||||
entry['Docked'] = bool(self.station)
|
||||
if self.station:
|
||||
entry = OrderedDict([
|
||||
('timestamp', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())),
|
||||
('event', 'StartUp'),
|
||||
('Docked', True),
|
||||
('StationName', self.station),
|
||||
('StationType', self.stationtype),
|
||||
('StarSystem', self.system),
|
||||
('StarPos', self.coordinates),
|
||||
])
|
||||
else:
|
||||
entry = OrderedDict([
|
||||
('timestamp', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())),
|
||||
('event', 'StartUp'),
|
||||
('Docked', False),
|
||||
('StarSystem', self.system),
|
||||
('StarPos', self.coordinates),
|
||||
])
|
||||
entry['StationName'] = self.station
|
||||
entry['StationType'] = self.stationtype
|
||||
self.event_queue.append(json.dumps(entry, separators=(', ', ':')))
|
||||
else:
|
||||
self.event_queue.append(None) # Generate null event to update the display (with possibly out-of-date info)
|
||||
@ -325,17 +324,23 @@ class EDLogs(FileSystemEventHandler):
|
||||
'Raw' : defaultdict(int),
|
||||
'Manufactured' : defaultdict(int),
|
||||
'Encoded' : defaultdict(int),
|
||||
'PaintJob' : None,
|
||||
'Rank' : { 'Combat': None, 'Trade': None, 'Explore': None, 'Empire': None, 'Federation': None, 'CQC': None },
|
||||
'Rank' : {},
|
||||
'Reputation' : {},
|
||||
'Statistics' : {},
|
||||
'Role' : None,
|
||||
'Friends' : set(),
|
||||
'ShipID' : None,
|
||||
'ShipIdent' : None,
|
||||
'ShipName' : None,
|
||||
'ShipType' : None,
|
||||
'HullValue' : None,
|
||||
'ModulesValue' : None,
|
||||
'Rebuy' : None,
|
||||
'Modules' : None,
|
||||
}
|
||||
elif entry['event'] == 'Commander':
|
||||
self.live = True # First event in 3.0
|
||||
elif entry['event'] == 'LoadGame':
|
||||
self.live = True
|
||||
self.cmdr = entry['Commander']
|
||||
self.mode = entry.get('GameMode') # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event)
|
||||
self.group = entry.get('Group')
|
||||
@ -350,7 +355,9 @@ class EDLogs(FileSystemEventHandler):
|
||||
'Captain' : None,
|
||||
'Credits' : entry['Credits'],
|
||||
'Loan' : entry['Loan'],
|
||||
'Rank' : { 'Combat': None, 'Trade': None, 'Explore': None, 'Empire': None, 'Federation': None, 'CQC': None },
|
||||
'Rank' : {},
|
||||
'Reputation' : {},
|
||||
'Statistics' : {},
|
||||
'Role' : None,
|
||||
})
|
||||
elif entry['event'] == 'NewCommander':
|
||||
@ -367,25 +374,45 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.state['ShipIdent'] = None
|
||||
self.state['ShipName'] = None
|
||||
self.state['ShipType'] = self.canonicalise(entry['ShipType'])
|
||||
self.state['PaintJob'] = None
|
||||
self.state['HullValue'] = None
|
||||
self.state['ModulesValue'] = None
|
||||
self.state['Rebuy'] = None
|
||||
self.state['Modules'] = None
|
||||
elif entry['event'] == 'ShipyardSwap':
|
||||
self.state['ShipID'] = entry['ShipID']
|
||||
self.state['ShipIdent'] = None
|
||||
self.state['ShipName'] = None
|
||||
self.state['ShipType'] = self.canonicalise(entry['ShipType'])
|
||||
self.state['PaintJob'] = None
|
||||
self.state['HullValue'] = None
|
||||
self.state['ModulesValue'] = None
|
||||
self.state['Rebuy'] = None
|
||||
self.state['Modules'] = None
|
||||
elif entry['event'] == 'Loadout': # Note: Precedes LoadGame, ShipyardNew, follows ShipyardSwap, ShipyardBuy
|
||||
self.state['ShipID'] = entry['ShipID']
|
||||
self.state['ShipIdent'] = entry['ShipIdent']
|
||||
self.state['ShipName'] = entry['ShipName']
|
||||
self.state['ShipType'] = self.canonicalise(entry['Ship'])
|
||||
# Ignore other Modules since they're missing Engineer modification details
|
||||
self.state['PaintJob'] = 'paintjob_%s_default_defaultpaintjob' % self.state['ShipType']
|
||||
for module in entry['Modules']:
|
||||
if module.get('Slot') == 'PaintJob' and module.get('Item'):
|
||||
self.state['PaintJob'] = self.canonicalise(module['Item'])
|
||||
elif entry['event'] in ['ModuleBuy', 'ModuleSell'] and entry['Slot'] == 'PaintJob':
|
||||
self.state['PaintJob'] = self.canonicalise(entry.get('BuyItem'))
|
||||
self.state['HullValue'] = entry.get('HullValue') # not present on exiting Outfitting
|
||||
self.state['ModulesValue'] = entry.get('ModulesValue') # "
|
||||
self.state['Rebuy'] = entry.get('Rebuy')
|
||||
self.state['Modules'] = dict([(thing['Slot'], thing) for thing in entry['Modules']])
|
||||
elif entry['event'] == 'ModuleBuy':
|
||||
self.state['Modules'][entry['Slot']] = { 'Slot' : entry['Slot'],
|
||||
'Item' : self.canonicalise(entry['BuyItem']),
|
||||
'On' : True,
|
||||
'Priority' : 1,
|
||||
'Health' : 1.0,
|
||||
'Value' : entry['BuyPrice'],
|
||||
}
|
||||
elif entry['event'] == 'ModuleSell':
|
||||
self.state['Modules'].pop(entry['Slot'], None)
|
||||
elif entry['event'] == 'ModuleSwap':
|
||||
toitem = self.state['Modules'].get(entry['ToSlot'])
|
||||
self.state['Modules'][entry['ToSlot']] = self.state['Modules'][entry['FromSlot']]
|
||||
if toitem:
|
||||
self.state['Modules'][entry['FromSlot']] = toitem
|
||||
else:
|
||||
self.state['Modules'].pop(entry['FromSlot'], None)
|
||||
elif entry['event'] in ['Undocked']:
|
||||
self.station = None
|
||||
self.stationtype = None
|
||||
@ -403,21 +430,30 @@ class EDLogs(FileSystemEventHandler):
|
||||
entry.get('StationName')) # May be None
|
||||
self.stationtype = entry.get('StationType') # May be None
|
||||
self.stationservices = entry.get('StationServices') # None under E:D < 2.4
|
||||
elif entry['event'] == 'ApproachBody':
|
||||
self.planet = entry['Body']
|
||||
elif entry['event'] == 'SupercruiseExit':
|
||||
self.planet = entry.get('Body') if entry.get('BodyType') == 'Planet' else None
|
||||
elif entry['event'] == 'SupercruiseEntry':
|
||||
elif entry['event'] in ['LeaveBody', 'SupercruiseEntry']:
|
||||
self.planet = None
|
||||
|
||||
elif entry['event'] in ['Rank', 'Promotion']:
|
||||
for k,v in entry.iteritems():
|
||||
if k in self.state['Rank']:
|
||||
self.state['Rank'][k] = (v,0)
|
||||
payload = dict(entry)
|
||||
payload.pop('event')
|
||||
payload.pop('timestamp')
|
||||
for k,v in payload.iteritems():
|
||||
self.state['Rank'][k] = (v,0)
|
||||
elif entry['event'] == 'Progress':
|
||||
for k,v in entry.iteritems():
|
||||
if self.state['Rank'].get(k) is not None:
|
||||
if k in self.state['Rank']:
|
||||
self.state['Rank'][k] = (self.state['Rank'][k][0], min(v, 100)) # perhaps not taken promotion mission yet
|
||||
elif entry['event'] in ['Reputation', 'Statistics']:
|
||||
payload = dict(entry)
|
||||
payload.pop('event')
|
||||
payload.pop('timestamp')
|
||||
self.state[entry['event']] = payload
|
||||
|
||||
elif entry['event'] == 'Cargo':
|
||||
self.live = True # First event in 2.3
|
||||
self.state['Cargo'] = defaultdict(int)
|
||||
self.state['Cargo'].update({ self.canonicalise(x['Name']): x['Count'] for x in entry['Inventory'] })
|
||||
elif entry['event'] in ['CollectCargo', 'MarketBuy', 'BuyDrones', 'MiningRefined']:
|
||||
@ -432,6 +468,11 @@ class EDLogs(FileSystemEventHandler):
|
||||
for reward in entry.get('CommodityReward', []):
|
||||
commodity = self.canonicalise(reward['Name'])
|
||||
self.state['Cargo'][commodity] += reward.get('Count', 1)
|
||||
for reward in entry.get('MaterialsReward', []):
|
||||
if 'Category' in reward: # FIXME: Category not present in E:D 3.0
|
||||
material = self.canonicalise(reward['Name'])
|
||||
self.state[reward['Category']][material] += reward.get('Count', 1)
|
||||
|
||||
elif entry['event'] == 'SearchAndRescue':
|
||||
for item in entry.get('Items', []):
|
||||
commodity = self.canonicalise(item['Name'])
|
||||
@ -451,15 +492,39 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.state[entry['Category']][material] -= entry['Count']
|
||||
if self.state[entry['Category']][material] <= 0:
|
||||
self.state[entry['Category']].pop(material)
|
||||
elif entry['event'] in ['EngineerCraft', 'Synthesis']:
|
||||
elif entry['event'] == 'Synthesis':
|
||||
for category in ['Raw', 'Manufactured', 'Encoded']:
|
||||
for x in entry[entry['event'] == 'EngineerCraft' and 'Ingredients' or 'Materials']:
|
||||
for x in entry['Materials']:
|
||||
material = self.canonicalise(x['Name'])
|
||||
if material in self.state[category]:
|
||||
self.state[category][material] -= x['Count']
|
||||
if self.state[category][material] <= 0:
|
||||
self.state[category].pop(material)
|
||||
|
||||
elif entry['event'] == 'EngineerCraft' or (entry['event'] == 'EngineerLegacyConvert' and not entry.get('IsPreview')):
|
||||
for category in ['Raw', 'Manufactured', 'Encoded']:
|
||||
for x in entry.get('Ingredients', []):
|
||||
material = self.canonicalise(x['Name'])
|
||||
if material in self.state[category]:
|
||||
self.state[category][material] -= x['Count']
|
||||
if self.state[category][material] <= 0:
|
||||
self.state[category].pop(material)
|
||||
module = self.state['Modules'][entry['Slot']]
|
||||
module['Engineering'] = {
|
||||
'Engineer' : entry['Engineer'],
|
||||
'EngineerID' : entry['EngineerID'],
|
||||
'BlueprintName' : entry['BlueprintName'],
|
||||
'BlueprintID' : entry['BlueprintID'],
|
||||
'Level' : entry['Level'],
|
||||
'Quality' : entry['Quality'],
|
||||
'Modifiers' : entry['Modifiers'],
|
||||
}
|
||||
if 'ExperimentalEffect' in entry:
|
||||
module['Engineering']['ExperimentalEffect'] = entry['ExperimentalEffect']
|
||||
module['Engineering']['ExperimentalEffect_Localised'] = entry['ExperimentalEffect_Localised']
|
||||
else:
|
||||
module['Engineering'].pop('ExperimentalEffect', None)
|
||||
module['Engineering'].pop('ExperimentalEffect_Localised', None)
|
||||
elif entry['event'] == 'EngineerContribution':
|
||||
commodity = self.canonicalise(entry.get('Commodity'))
|
||||
if commodity:
|
||||
@ -579,5 +644,24 @@ class EDLogs(FileSystemEventHandler):
|
||||
return False
|
||||
|
||||
|
||||
# Return a subset of the received data describing the current ship as a Loadout event
|
||||
def ship(self):
|
||||
if not self.state['Modules']:
|
||||
return None
|
||||
|
||||
d = OrderedDict([
|
||||
('timestamp', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())),
|
||||
('event', 'Loadout'),
|
||||
('Ship', self.state['ShipType']),
|
||||
('ShipID', self.state['ShipID']),
|
||||
])
|
||||
for thing in ['ShipName', 'ShipIdent', 'HullValue', 'ModulesValue', 'Rebuy']:
|
||||
if self.state[thing]:
|
||||
d[thing] = self.state[thing]
|
||||
d['Modules'] = self.state['Modules'].values()
|
||||
|
||||
return d
|
||||
|
||||
|
||||
# singleton
|
||||
monitor = EDLogs()
|
||||
|
@ -261,6 +261,7 @@ internal_map = {
|
||||
'passengercabin' : 'Passenger Cabin',
|
||||
'prospector' : 'Prospector Limpet Controller',
|
||||
'refinery' : 'Refinery',
|
||||
'recon' : 'Recon Limpet Controller',
|
||||
'repair' : 'Repair Limpet Controller',
|
||||
'repairer' : 'Auto Field-Maintenance Unit',
|
||||
'resourcesiphon' : 'Hatch Breaker Limpet Controller',
|
||||
|
21
plug.py
21
plug.py
@ -226,6 +226,27 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
return error
|
||||
|
||||
|
||||
def notify_dashboard_entry(cmdr, is_beta, entry):
|
||||
"""
|
||||
Send a status entry to each plugin.
|
||||
:param cmdr: The piloting Cmdr name
|
||||
:param is_beta: whether the player is in a Beta universe.
|
||||
:param entry: The status entry as a dictionary
|
||||
:return: Error message from the first plugin that returns one (if any)
|
||||
"""
|
||||
error = None
|
||||
for plugin in PLUGINS:
|
||||
status = plugin._get_func('dashboard_entry')
|
||||
if status:
|
||||
try:
|
||||
# Pass a copy of the status entry in case the callee modifies it
|
||||
newerror = status(cmdr, is_beta, dict(entry))
|
||||
error = error or newerror
|
||||
except:
|
||||
print_exc()
|
||||
return error
|
||||
|
||||
|
||||
def notify_system_changed(timestamp, system, coordinates):
|
||||
"""
|
||||
Send notification data to each plugin when we arrive at a new system.
|
||||
|
@ -34,11 +34,14 @@ this.session = requests.Session()
|
||||
this.queue = Queue() # Items to be sent to EDSM by worker thread
|
||||
this.discardedEvents = [] # List discarded events from EDSM
|
||||
this.lastship = None # Description of last ship that we sent to EDSM
|
||||
this.lastloadout = None # Description of last ship that we sent to EDSM
|
||||
this.lastlookup = False # whether the last lookup succeeded
|
||||
|
||||
# Game state
|
||||
this.multicrew = False # don't send captain's ship info to EDSM while on a crew
|
||||
this.coordinates = None
|
||||
this.newgame = False # starting up - batch initial burst of events
|
||||
this.newgame_docked = False # starting up while docked
|
||||
|
||||
def plugin_start():
|
||||
# Can't be earlier since can only call PhotoImage after window is created
|
||||
@ -198,6 +201,16 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
elif entry['event'] == 'LoadGame':
|
||||
this.coordinates = None
|
||||
|
||||
if entry['event'] in ['LoadGame', 'Commander', 'NewCommander']:
|
||||
this.newgame = True
|
||||
this.newgame_docked = False
|
||||
elif entry['event'] == 'StartUp':
|
||||
this.newgame = False
|
||||
this.newgame_docked = False
|
||||
elif entry['event'] == 'Location':
|
||||
this.newgame = True
|
||||
this.newgame_docked = entry.get('Docked', False)
|
||||
|
||||
# Send interesting events to EDSM
|
||||
if config.getint('edsm_out') and not is_beta and not this.multicrew and credentials(cmdr) and entry['event'] not in this.discardedEvents:
|
||||
# Introduce transient states into the event
|
||||
@ -210,15 +223,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
entry.update(transient)
|
||||
|
||||
if entry['event'] == 'LoadGame':
|
||||
# Synthesise Cargo and Materials events on LoadGame since we will have missed them because Cmdr was unknown
|
||||
cargo = {
|
||||
'timestamp': entry['timestamp'],
|
||||
'event': 'Cargo',
|
||||
'Inventory': [ { 'Name': k, 'Count': v } for k,v in state['Cargo'].iteritems() ],
|
||||
}
|
||||
cargo.update(transient)
|
||||
this.queue.put((cmdr, cargo))
|
||||
|
||||
# Synthesise Materials events on LoadGame since we will have missed it
|
||||
materials = {
|
||||
'timestamp': entry['timestamp'],
|
||||
'event': 'Materials',
|
||||
@ -231,6 +236,14 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
|
||||
this.queue.put((cmdr, entry))
|
||||
|
||||
if entry['event'] == 'Loadout' and 'EDShipyard' not in this.discardedEvents:
|
||||
url = edshipyard.url(is_beta)
|
||||
if this.lastloadout != url:
|
||||
this.lastloadout = url
|
||||
this.queue.put((cmdr, {
|
||||
'event': 'EDShipyard', 'timestamp': entry['timestamp'], '_shipId': state['ShipID'], 'url': this.lastloadout
|
||||
}))
|
||||
|
||||
|
||||
# Update system data
|
||||
def cmdr_data(data, is_beta):
|
||||
@ -258,10 +271,6 @@ def cmdr_data(data, is_beta):
|
||||
this.queue.put((cmdr, {
|
||||
'event': 'Coriolis', 'timestamp': timestamp, '_shipId': data['ship']['id'], 'url': coriolis.url(data, is_beta)
|
||||
}))
|
||||
if 'EDShipyard' not in this.discardedEvents:
|
||||
this.queue.put((cmdr, {
|
||||
'event': 'EDShipyard', 'timestamp': timestamp, '_shipId': data['ship']['id'], 'url': edshipyard.url(data, is_beta)
|
||||
}))
|
||||
this.lastship = ship
|
||||
|
||||
|
||||
@ -310,14 +319,14 @@ def worker():
|
||||
if msgnum // 100 == 2:
|
||||
print('EDSM\t%s %s\t%s' % (msgnum, msg, json.dumps(pending, separators = (',', ': '))))
|
||||
plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg))
|
||||
elif not closing:
|
||||
# Update main window's system status
|
||||
for i in range(len(pending) - 1, -1, -1):
|
||||
if pending[i]['event'] in ['StartUp', 'Location', 'FSDJump']:
|
||||
this.lastlookup = reply['events'][i]
|
||||
else:
|
||||
for e, r in zip(pending, reply['events']):
|
||||
if not closing and e['event'] in ['StartUp', 'Location', 'FSDJump']:
|
||||
# Update main window's system status
|
||||
this.lastlookup = r
|
||||
this.system.event_generate('<<EDSMStatus>>', when="tail") # calls update_status in main thread
|
||||
break
|
||||
|
||||
elif r['msgnum'] // 100 != 1:
|
||||
print('EDSM\t%s %s\t%s' % (r['msgnum'], r['msg'], json.dumps(e, separators = (',', ': '))))
|
||||
pending = []
|
||||
|
||||
break
|
||||
@ -334,10 +343,16 @@ def worker():
|
||||
# Whether any of the entries should be sent immediately
|
||||
def should_send(entries):
|
||||
for entry in entries:
|
||||
if (entry['event'] not in ['CommunityGoal', # Spammed periodically
|
||||
'Cargo', 'Loadout', 'Materials', 'LoadGame', 'Rank', 'Progress', # Will be followed by 'Docked' or 'Location'
|
||||
'ShipyardBuy', 'ShipyardNew', 'ShipyardSwap'] and # "
|
||||
not (entry['event'] == 'Location' and entry.get('Docked'))): # "
|
||||
if (entry['event'] == 'Cargo' and not this.newgame_docked) or entry['event'] == 'Docked':
|
||||
# Cargo is the last event on startup, unless starting when docked in which case Docked is the last event
|
||||
this.newgame = False
|
||||
this.newgame_docked = False
|
||||
return True
|
||||
elif this.newgame:
|
||||
pass
|
||||
elif entry['event'] not in ['CommunityGoal', # Spammed periodically
|
||||
'ModuleBuy', 'ModuleSell', 'ModuleSwap', # will be shortly followed by "Loadout"
|
||||
'ShipyardBuy', 'ShipyardNew', 'ShipyardSwap']: # "
|
||||
return True
|
||||
return False
|
||||
|
||||
|
210
plugins/inara.py
210
plugins/inara.py
@ -7,6 +7,7 @@ import json
|
||||
import requests
|
||||
import sys
|
||||
import time
|
||||
from operator import itemgetter
|
||||
from Queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
@ -36,11 +37,13 @@ this.cmdr = None
|
||||
this.multicrew = False # don't send captain's ship info to Inara while on a crew
|
||||
this.newuser = False # just entered API Key
|
||||
this.undocked = False # just undocked
|
||||
this.suppress_docked = False # Skip Docked event after Location if started docked
|
||||
this.suppress_docked = False # Skip initial Docked event if started docked
|
||||
this.cargo = None
|
||||
this.materials = None
|
||||
this.lastcredits = 0 # Send credit update soon after Startup / new game
|
||||
this.needfleet = True # Send full fleet update soon after Startup / new game
|
||||
this.storedmodules = None
|
||||
this.loadout = None
|
||||
this.fleet = None
|
||||
this.shipswap = False # just swapped ship
|
||||
|
||||
# URLs
|
||||
@ -154,7 +157,9 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
this.cargo = None
|
||||
this.materials = None
|
||||
this.lastcredits = 0
|
||||
this.needfleet = True
|
||||
this.storedmodules = None
|
||||
this.loadout = None
|
||||
this.fleet = None
|
||||
this.shipswap = False
|
||||
elif entry['event'] in ['Resurrect', 'ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy']:
|
||||
# Events that mean a significant change in credits so we should send credits after next "Update"
|
||||
@ -163,7 +168,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
this.suppress_docked = True
|
||||
|
||||
|
||||
# Send location and status on new game or StartUp. Assumes Location is the last event on a new game (other than Docked).
|
||||
# Send location and status on new game or StartUp. Assumes Cargo is the last event on a new game (other than Docked).
|
||||
# Always send an update on Docked, FSDJump, Undocked+SuperCruise, Promotion and EngineerProgress.
|
||||
# Also send material and cargo (if changed) whenever we send an update.
|
||||
|
||||
@ -171,8 +176,17 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
try:
|
||||
old_events = len(this.events) # Will only send existing events if we add a new event below
|
||||
|
||||
# Send credits to Inara on startup only - otherwise may be out of date
|
||||
if entry['event'] == 'Cargo':
|
||||
add_event('setCommanderCredits', entry['timestamp'],
|
||||
OrderedDict([
|
||||
('commanderCredits', state['Credits']),
|
||||
('commanderLoan', state['Loan']),
|
||||
]))
|
||||
this.lastcredits = state['Credits']
|
||||
|
||||
# Send rank info to Inara on startup or change
|
||||
if (entry['event'] in ['StartUp', 'Location'] or this.newuser) and state['Rank']:
|
||||
if (entry['event'] in ['StartUp', 'Cargo'] or this.newuser):
|
||||
for k,v in state['Rank'].iteritems():
|
||||
if v is not None:
|
||||
add_event('setCommanderRankPilot', entry['timestamp'],
|
||||
@ -181,6 +195,15 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
('rankValue', v[0]),
|
||||
('rankProgress', v[1] / 100.0),
|
||||
]))
|
||||
for k,v in state['Reputation'].iteritems():
|
||||
if v is not None:
|
||||
add_event('setCommanderReputationMajorFaction', entry['timestamp'],
|
||||
OrderedDict([
|
||||
('majorfactionName', k.lower()),
|
||||
('majorfactionReputation', v / 100.0),
|
||||
]))
|
||||
add_event('setCommanderGameStatistics', entry['timestamp'], state['Statistics']) # may be out of date
|
||||
|
||||
elif entry['event'] == 'Promotion':
|
||||
for k,v in state['Rank'].iteritems():
|
||||
if k in entry:
|
||||
@ -227,27 +250,33 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
]))
|
||||
|
||||
# Update ship
|
||||
if (entry['event'] in ['StartUp', 'Location', 'ShipyardNew'] or
|
||||
if (entry['event'] in ['StartUp', 'Cargo'] or
|
||||
(entry['event'] == 'Loadout' and this.shipswap) or
|
||||
this.newuser):
|
||||
if entry['event'] == 'ShipyardNew':
|
||||
add_event('addCommanderShip', entry['timestamp'],
|
||||
OrderedDict([
|
||||
('shipType', state['ShipType']),
|
||||
('shipGameID', state['ShipID']),
|
||||
]))
|
||||
add_event('setCommanderShip', entry['timestamp'],
|
||||
OrderedDict([
|
||||
('shipType', state['ShipType']),
|
||||
('shipGameID', state['ShipID']),
|
||||
('shipName', state['ShipName']), # Can be None
|
||||
('shipIdent', state['ShipIdent']), # Can be None
|
||||
('isCurrentShip', True),
|
||||
]))
|
||||
data = OrderedDict([
|
||||
('shipType', state['ShipType']),
|
||||
('shipGameID', state['ShipID']),
|
||||
('shipName', state['ShipName']), # Can be None
|
||||
('shipIdent', state['ShipIdent']), # Can be None
|
||||
('isCurrentShip', True),
|
||||
])
|
||||
if state['HullValue']:
|
||||
data['shipHullValue'] = state['HullValue']
|
||||
if state['ModulesValue']:
|
||||
data['shipModulesValue'] = state['ModulesValue']
|
||||
data['shipRebuyCost'] = state['Rebuy']
|
||||
add_event('setCommanderShip', entry['timestamp'], data)
|
||||
|
||||
this.loadout = OrderedDict([
|
||||
('shipType', state['ShipType']),
|
||||
('shipGameID', state['ShipID']),
|
||||
('shipLoadout', state['Modules'].values()),
|
||||
])
|
||||
add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
|
||||
this.shipswap = False
|
||||
|
||||
# Update location
|
||||
if (entry['event'] in ['StartUp', 'Location'] or this.newuser) and system:
|
||||
if (entry['event'] in ['StartUp', 'Cargo'] or this.newuser) and system:
|
||||
this.undocked = False
|
||||
add_event('setCommanderTravelLocation', entry['timestamp'],
|
||||
OrderedDict([
|
||||
@ -262,7 +291,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
# Undocked and now docking again. Don't send.
|
||||
this.undocked = False
|
||||
elif this.suppress_docked:
|
||||
# Don't send Docked event on new game - i.e. following 'Location' event
|
||||
# Don't send initial Docked event on new game
|
||||
this.suppress_docked = False
|
||||
else:
|
||||
add_event('addCommanderTravelDock', entry['timestamp'],
|
||||
@ -325,7 +354,15 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
#
|
||||
|
||||
# Selling / swapping ships
|
||||
if entry['event'] in ['ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy', 'ShipyardSwap']:
|
||||
if entry['event'] == 'ShipyardNew':
|
||||
add_event('addCommanderShip', entry['timestamp'],
|
||||
OrderedDict([
|
||||
('shipType', state['ShipType']),
|
||||
('shipGameID', state['ShipID']),
|
||||
]))
|
||||
this.shipswap = True # Want subsequent Loadout event to be sent immediately
|
||||
|
||||
elif entry['event'] in ['ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy', 'ShipyardSwap']:
|
||||
if entry['event'] == 'ShipyardSwap':
|
||||
this.shipswap = True # Don't know new ship name and ident 'til the following Loadout event
|
||||
if 'StoreShipID' in entry:
|
||||
@ -363,6 +400,79 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
('transferTime', entry['TransferTime']),
|
||||
]))
|
||||
|
||||
# Fleet
|
||||
if entry['event'] == 'StoredShips':
|
||||
fleet = sorted(
|
||||
[{
|
||||
'shipType': x['ShipType'],
|
||||
'shipGameID': x['ShipID'],
|
||||
'shipName': x.get('Name'),
|
||||
'isHot': x['Hot'],
|
||||
'starsystemName': entry['StarSystem'],
|
||||
'stationName': entry['StationName'],
|
||||
'marketID': entry['MarketID'],
|
||||
} for x in entry['ShipsHere']] +
|
||||
[{
|
||||
'shipType': x['ShipType'],
|
||||
'shipGameID': x['ShipID'],
|
||||
'shipName': x.get('Name'),
|
||||
'isHot': x['Hot'],
|
||||
'starsystemName': x.get('StarSystem'), # Not present for ships in transit
|
||||
'marketID': x.get('ShipMarketID'), # "
|
||||
} for x in entry['ShipsRemote']],
|
||||
key = itemgetter('shipGameID')
|
||||
)
|
||||
if this.fleet != fleet:
|
||||
this.fleet = fleet
|
||||
this.events = [x for x in this.events if x['eventName'] != 'setCommanderShip'] # Remove any unsent
|
||||
for ship in this.fleet:
|
||||
add_event('setCommanderShip', entry['timestamp'], ship)
|
||||
|
||||
# Loadout
|
||||
if entry['event'] == 'Loadout':
|
||||
loadout = OrderedDict([
|
||||
('shipType', state['ShipType']),
|
||||
('shipGameID', state['ShipID']),
|
||||
('shipLoadout', state['Modules'].values()),
|
||||
])
|
||||
if this.loadout != loadout:
|
||||
this.loadout = loadout
|
||||
this.events = [x for x in this.events if x['eventName'] != 'setCommanderShipLoadout' or x['shipGameID'] != this.loadout['shipGameID']] # Remove any unsent for this ship
|
||||
add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
|
||||
|
||||
# Stored modules
|
||||
if entry['event'] == 'StoredModules':
|
||||
items = dict([(x['StorageSlot'], x) for x in entry['Items']]) # Impose an order
|
||||
modules = []
|
||||
for slot in sorted(items):
|
||||
item = items[slot]
|
||||
module = OrderedDict([
|
||||
('itemName', item['Name']),
|
||||
('itemValue', item['BuyPrice']),
|
||||
('isHot', item['Hot']),
|
||||
])
|
||||
|
||||
# Location can be absent if in transit
|
||||
if 'StarSystem' in item:
|
||||
module['starsystemName'] = item['StarSystem']
|
||||
if 'MarketID' in item:
|
||||
module['marketID'] = item['MarketID']
|
||||
|
||||
if 'EngineerModifications' in item:
|
||||
module['engineering'] = OrderedDict([('blueprintName', item['EngineerModifications'])])
|
||||
if 'Level' in item:
|
||||
module['engineering']['blueprintLevel'] = item['Level']
|
||||
if 'Quality' in item:
|
||||
module['engineering']['blueprintQuality'] = item['Quality']
|
||||
|
||||
modules.append(module)
|
||||
|
||||
if this.storedmodules != modules:
|
||||
# Only send on change
|
||||
this.storedmodules = modules
|
||||
this.events = [x for x in this.events if x['eventName'] != 'setCommanderStorageModules'] # Remove any unsent
|
||||
add_event('setCommanderStorageModules', entry['timestamp'], this.storedmodules)
|
||||
|
||||
# Missions
|
||||
if entry['event'] == 'MissionAccepted':
|
||||
data = OrderedDict([
|
||||
@ -410,14 +520,10 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
data['rewardPermits'] = [{ 'starsystemName': x } for x in entry['PermitsAwarded']]
|
||||
if 'CommodityReward' in entry:
|
||||
data['rewardCommodities'] = [{ 'itemName': x['Name'], 'itemCount': x['Count'] } for x in entry['CommodityReward']]
|
||||
if 'MaterialsReward' in entry:
|
||||
data['rewardMaterials'] = [{ 'itemName': x['Name'], 'itemCount': x['Count'] } for x in entry['MaterialsReward']]
|
||||
add_event('setCommanderMissionCompleted', entry['timestamp'], data)
|
||||
|
||||
# Journal doesn't list rewarded materials directly, just as 'MaterialCollected'
|
||||
elif (entry['event'] == 'MaterialCollected' and this.events and
|
||||
this.events[-1]['eventName'] == 'setCommanderMissionCompleted' and
|
||||
this.events[-1]['eventTimestamp'] == entry['timestamp']):
|
||||
this.events[-1]['eventData']['rewardMaterials'] = [{ 'itemName': entry['Name'], 'itemCount': entry['Count'] }]
|
||||
|
||||
elif entry['event'] == 'MissionFailed':
|
||||
add_event('setCommanderMissionFailed', entry['timestamp'], { 'missionGameID': entry['MissionID'] })
|
||||
|
||||
@ -483,6 +589,9 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
|
||||
data['tierReached'] = int(goal['TierReached'].split()[-1])
|
||||
if 'TopRankSize' in goal:
|
||||
data['topRankSize'] = goal['TopRankSize']
|
||||
if 'TopTier' in goal:
|
||||
data['tierMax'] = int(goal['TopTier']['Name'].split()[-1])
|
||||
data['completionBonus'] = goal['TopTier']['Bonus']
|
||||
add_event('setCommunityGoal', entry['timestamp'], data)
|
||||
|
||||
data = OrderedDict([
|
||||
@ -503,54 +612,15 @@ def cmdr_data(data, is_beta):
|
||||
this.cmdr = data['commander']['name']
|
||||
|
||||
if config.getint('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr):
|
||||
|
||||
timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
||||
assets = data['commander']['credits'] - data['commander'].get('debt', 0)
|
||||
|
||||
for ship in companion.listify(data.get('ships', [])):
|
||||
if ship:
|
||||
assets += ship['value']['total']
|
||||
if this.needfleet:
|
||||
if ship['id'] != data['commander']['currentShipId']:
|
||||
add_event('setCommanderShip', timestamp,
|
||||
OrderedDict([
|
||||
('shipType', ship['name']),
|
||||
('shipGameID', ship['id']),
|
||||
('shipName', ship.get('shipName')), # Can be None
|
||||
('shipIdent', ship.get('shipID')), # Can be None
|
||||
('shipHullValue', ship['value']['hull']),
|
||||
('shipModulesValue', ship['value']['modules']),
|
||||
('starsystemName', ship['starsystem']['name']),
|
||||
('stationName', ship['station']['name']),
|
||||
]))
|
||||
else:
|
||||
add_event('setCommanderShip', timestamp,
|
||||
OrderedDict([
|
||||
('shipType', ship['name']),
|
||||
('shipGameID', ship['id']),
|
||||
('shipName', ship.get('shipName')), # Can be None
|
||||
('shipIdent', ship.get('shipID')), # Can be None
|
||||
('isCurrentShip', True),
|
||||
('shipHullValue', ship['value']['hull']),
|
||||
('shipModulesValue', ship['value']['modules']),
|
||||
]))
|
||||
|
||||
if not (CREDIT_RATIO > this.lastcredits / data['commander']['credits'] > 1/CREDIT_RATIO):
|
||||
this.events = [x for x in this.events if x['eventName'] != 'setCommanderCredits'] # Remove any unsent
|
||||
add_event('setCommanderCredits', timestamp,
|
||||
add_event('setCommanderCredits', time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
||||
OrderedDict([
|
||||
('commanderCredits', data['commander']['credits']),
|
||||
('commanderAssets', assets),
|
||||
('commanderLoan', data['commander'].get('debt', 0)),
|
||||
]))
|
||||
this.lastcredits = float(data['commander']['credits'])
|
||||
|
||||
# *Don't* queue a call to Inara if we're just updating credits - wait for next mandatory event
|
||||
if this.needfleet:
|
||||
call()
|
||||
this.needfleet = False
|
||||
|
||||
|
||||
def add_event(name, timestamp, data):
|
||||
this.events.append(OrderedDict([
|
||||
('eventName', name),
|
||||
@ -603,7 +673,7 @@ def worker():
|
||||
# Log individual errors and warnings
|
||||
for data_event, reply_event in zip(data['events'], reply['events']):
|
||||
if reply_event['eventStatus'] != 200:
|
||||
print 'Inara\t%s %s\t%s' % (reply_event['eventStatus'], reply_event.get('eventStatusText', ''), json.dumps(data_event, separators = (',', ': ')))
|
||||
print 'Inara\t%s %s\t%s' % (reply_event['eventStatus'], reply_event.get('eventStatusText', ''), json.dumps(data_event))
|
||||
if reply_event['eventStatus'] // 100 != 2:
|
||||
plug.show_error(_('Error: Inara {MSG}').format(MSG = '%s, %s' % (data_event['eventName'], reply_event.get('eventStatusText', reply_event['eventStatus']))))
|
||||
if data_event['eventName'] in ['addCommanderTravelDock', 'addCommanderTravelFSDJump', 'setCommanderTravelLocation']:
|
||||
|
128
ships.p
128
ships.p
@ -10,190 +10,196 @@ S'hullMass'
|
||||
p6
|
||||
I35
|
||||
saa(lp7
|
||||
S'Anaconda'
|
||||
S'Alliance Chieftain'
|
||||
p8
|
||||
a(dp9
|
||||
g6
|
||||
I400
|
||||
I420
|
||||
saa(lp10
|
||||
S'Asp Explorer'
|
||||
S'Anaconda'
|
||||
p11
|
||||
a(dp12
|
||||
g6
|
||||
I280
|
||||
I400
|
||||
saa(lp13
|
||||
S'Asp Scout'
|
||||
S'Asp Explorer'
|
||||
p14
|
||||
a(dp15
|
||||
g6
|
||||
I150
|
||||
I280
|
||||
saa(lp16
|
||||
S'Beluga Liner'
|
||||
S'Asp Scout'
|
||||
p17
|
||||
a(dp18
|
||||
g6
|
||||
I950
|
||||
I150
|
||||
saa(lp19
|
||||
S'Cobra MkIII'
|
||||
S'Beluga Liner'
|
||||
p20
|
||||
a(dp21
|
||||
g6
|
||||
I180
|
||||
I950
|
||||
saa(lp22
|
||||
S'Cobra MkIV'
|
||||
S'Cobra MkIII'
|
||||
p23
|
||||
a(dp24
|
||||
g6
|
||||
I210
|
||||
I180
|
||||
saa(lp25
|
||||
S'Diamondback Explorer'
|
||||
S'Cobra MkIV'
|
||||
p26
|
||||
a(dp27
|
||||
g6
|
||||
I260
|
||||
I210
|
||||
saa(lp28
|
||||
S'Diamondback Scout'
|
||||
S'Diamondback Explorer'
|
||||
p29
|
||||
a(dp30
|
||||
g6
|
||||
I170
|
||||
I260
|
||||
saa(lp31
|
||||
S'Dolphin'
|
||||
S'Diamondback Scout'
|
||||
p32
|
||||
a(dp33
|
||||
g6
|
||||
I140
|
||||
I170
|
||||
saa(lp34
|
||||
S'Eagle'
|
||||
S'Dolphin'
|
||||
p35
|
||||
a(dp36
|
||||
g6
|
||||
I50
|
||||
I140
|
||||
saa(lp37
|
||||
S'Federal Assault Ship'
|
||||
S'Eagle'
|
||||
p38
|
||||
a(dp39
|
||||
g6
|
||||
I480
|
||||
I50
|
||||
saa(lp40
|
||||
S'Federal Corvette'
|
||||
S'Federal Assault Ship'
|
||||
p41
|
||||
a(dp42
|
||||
g6
|
||||
I900
|
||||
I480
|
||||
saa(lp43
|
||||
S'Federal Dropship'
|
||||
S'Federal Corvette'
|
||||
p44
|
||||
a(dp45
|
||||
g6
|
||||
I580
|
||||
I900
|
||||
saa(lp46
|
||||
S'Federal Gunship'
|
||||
S'Federal Dropship'
|
||||
p47
|
||||
a(dp48
|
||||
g6
|
||||
I580
|
||||
saa(lp49
|
||||
S'Fer-de-Lance'
|
||||
S'Federal Gunship'
|
||||
p50
|
||||
a(dp51
|
||||
g6
|
||||
I250
|
||||
I580
|
||||
saa(lp52
|
||||
S'Hauler'
|
||||
S'Fer-de-Lance'
|
||||
p53
|
||||
a(dp54
|
||||
g6
|
||||
I14
|
||||
I250
|
||||
saa(lp55
|
||||
S'Imperial Clipper'
|
||||
S'Hauler'
|
||||
p56
|
||||
a(dp57
|
||||
g6
|
||||
I400
|
||||
I14
|
||||
saa(lp58
|
||||
S'Imperial Courier'
|
||||
S'Imperial Clipper'
|
||||
p59
|
||||
a(dp60
|
||||
g6
|
||||
I35
|
||||
I400
|
||||
saa(lp61
|
||||
S'Imperial Cutter'
|
||||
S'Imperial Courier'
|
||||
p62
|
||||
a(dp63
|
||||
g6
|
||||
I1100
|
||||
I35
|
||||
saa(lp64
|
||||
S'Imperial Eagle'
|
||||
S'Imperial Cutter'
|
||||
p65
|
||||
a(dp66
|
||||
g6
|
||||
I50
|
||||
I1100
|
||||
saa(lp67
|
||||
S'Keelback'
|
||||
S'Imperial Eagle'
|
||||
p68
|
||||
a(dp69
|
||||
g6
|
||||
I180
|
||||
I50
|
||||
saa(lp70
|
||||
S'Orca'
|
||||
S'Keelback'
|
||||
p71
|
||||
a(dp72
|
||||
g6
|
||||
I290
|
||||
I180
|
||||
saa(lp73
|
||||
S'Python'
|
||||
S'Orca'
|
||||
p74
|
||||
a(dp75
|
||||
g6
|
||||
I350
|
||||
I290
|
||||
saa(lp76
|
||||
S'Sidewinder'
|
||||
S'Python'
|
||||
p77
|
||||
a(dp78
|
||||
g6
|
||||
I25
|
||||
I350
|
||||
saa(lp79
|
||||
S'Type-10 Defender'
|
||||
S'Sidewinder'
|
||||
p80
|
||||
a(dp81
|
||||
g6
|
||||
I1200
|
||||
I25
|
||||
saa(lp82
|
||||
S'Type-6 Transporter'
|
||||
S'Type-10 Defender'
|
||||
p83
|
||||
a(dp84
|
||||
g6
|
||||
I155
|
||||
I1200
|
||||
saa(lp85
|
||||
S'Type-7 Transporter'
|
||||
S'Type-6 Transporter'
|
||||
p86
|
||||
a(dp87
|
||||
g6
|
||||
I420
|
||||
I155
|
||||
saa(lp88
|
||||
S'Type-9 Heavy'
|
||||
S'Type-7 Transporter'
|
||||
p89
|
||||
a(dp90
|
||||
g6
|
||||
I1000
|
||||
I420
|
||||
saa(lp91
|
||||
S'Viper MkIII'
|
||||
S'Type-9 Heavy'
|
||||
p92
|
||||
a(dp93
|
||||
g6
|
||||
I50
|
||||
I850
|
||||
saa(lp94
|
||||
S'Viper MkIV'
|
||||
S'Viper MkIII'
|
||||
p95
|
||||
a(dp96
|
||||
g6
|
||||
I190
|
||||
I50
|
||||
saa(lp97
|
||||
S'Vulture'
|
||||
S'Viper MkIV'
|
||||
p98
|
||||
a(dp99
|
||||
g6
|
||||
I190
|
||||
saa(lp100
|
||||
S'Vulture'
|
||||
p101
|
||||
a(dp102
|
||||
g6
|
||||
I230
|
||||
saatRp100
|
||||
saatRp103
|
||||
.
|
Loading…
x
Reference in New Issue
Block a user