1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-18 09:57:40 +03:00

Merge branch 'beyond'

This commit is contained in:
Jonathan Harris 2018-02-26 17:57:49 +00:00
commit c79398eb12
14 changed files with 3534 additions and 3078 deletions

View File

@ -57,6 +57,7 @@ import prefs
import plug import plug
from hotkey import hotkeymgr from hotkey import hotkeymgr
from monitor import monitor from monitor import monitor
from dashboard import dashboard
from theme import theme from theme import theme
@ -269,6 +270,7 @@ class AppWindow:
self.w.bind('<KP_Enter>', self.getandsend) self.w.bind('<KP_Enter>', self.getandsend)
self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring
self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal 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('<<PluginError>>', self.plugin_error) # Statusbar
self.w.bind_all('<<Quit>>', self.onexit) # Updater self.w.bind_all('<<Quit>>', self.onexit) # Updater
@ -627,6 +629,11 @@ class AppWindow:
if not config.getint('hotkey_mute'): if not config.getint('hotkey_mute'):
hotkeymgr.play_bad() 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 # Don't send to EDDN while on crew
if monitor.state['Captain']: if monitor.state['Captain']:
return return
@ -678,6 +685,17 @@ class AppWindow:
if not config.getint('hotkey_mute'): if not config.getint('hotkey_mute'):
hotkeymgr.play_bad() 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 # Display asynchronous error from plugin
def plugin_error(self, event=None): def plugin_error(self, event=None):
if plug.last_error.get('msg'): if plug.last_error.get('msg'):
@ -691,6 +709,14 @@ class AppWindow:
if not monitor.cmdr or not monitor.mode: if not monitor.cmdr or not monitor.mode:
return False # In CQC - do nothing 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.status['text'] = _('Fetching data...')
self.w.update_idletasks() self.w.update_idletasks()
try: 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 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: else:
self.status['text'] = '' self.status['text'] = ''
if config.getint('shipyard') == config.SHIPYARD_EDSHIPYARD: return coriolis.url(data, monitor.is_beta)
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
def cooldown(self): def cooldown(self):
if time() < self.holdofftime: if time() < self.holdofftime:
@ -779,6 +799,7 @@ class AppWindow:
config.set('geometry', '+{1}+{2}'.format(*self.w.geometry().split('+'))) 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 self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen
hotkeymgr.unregister() hotkeymgr.unregister()
dashboard.close()
monitor.close() monitor.close()
plug.notify_stop() plug.notify_stop()
self.eddn.close() self.eddn.close()

View File

@ -106,7 +106,7 @@ this.status["text"] = "Happy!"
## Events ## 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 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'])) 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 ### Getting Commander Data
This gets called when EDMC has just fetched fresh Cmdr and station data from Frontier's servers. 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 ## 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. 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.

View File

@ -73,6 +73,7 @@ ship_map = {
'type7' : 'Type-7 Transporter', 'type7' : 'Type-7 Transporter',
'type9' : 'Type-9 Heavy', 'type9' : 'Type-9 Heavy',
'type9_military' : 'Type-10 Defender', 'type9_military' : 'Type-10 Defender',
'typex' : 'Alliance Chieftain',
'viper' : 'Viper MkIII', 'viper' : 'Viper MkIII',
'viper_mkiv' : 'Viper MkIV', 'viper_mkiv' : 'Viper MkIV',
'vulture' : 'Vulture', 'vulture' : 'Vulture',

@ -1 +1 @@
Subproject commit a3ae3e34ad879d1504a7d2fdc3fce4854820051b Subproject commit 44d57f26a8099cfa8d91815f453c7bba303a63ec

View File

@ -169,11 +169,16 @@ if __name__ == "__main__":
else: else:
modules[key] = { 'mass': m.get('mass', 0) } # Some modules don't have mass 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, '1', 'E')] = {'mass': 1.3}
modules[('Decontamination Limpet Controller', None, '3', 'E')] = {'mass': 2} modules[('Decontamination Limpet Controller', None, '3', 'E')] = {'mass': 2}
modules[('Decontamination Limpet Controller', None, '5', 'E')] = {'mass': 20} modules[('Decontamination Limpet Controller', None, '5', 'E')] = {'mass': 20}
modules[('Decontamination Limpet Controller', None, '7', 'E')] = {'mass': 128} 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 modules = OrderedDict([(k,modules[k]) for k in sorted(modules)]) # sort for easier diffing
cPickle.dump(modules, open('modules.p', 'wb')) cPickle.dump(modules, open('modules.p', 'wb'))

131
dashboard.py Normal file
View 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()

View File

@ -13,6 +13,7 @@ import gzip
from config import config from config import config
import companion import companion
import outfitting import outfitting
from monitor import monitor
# Map API ship names to E:D Shipyard ship names # Map API ship names to E:D Shipyard ship names
ship_map = dict(companion.ship_map) ship_map = dict(companion.ship_map)
@ -162,9 +163,11 @@ def export(data, filename=None):
# Return a URL for the current ship # 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() out = StringIO.StringIO()
with gzip.GzipFile(fileobj=out, mode='w') as f: with gzip.GzipFile(fileobj=out, mode='w') as f:

5828
modules.p

File diff suppressed because it is too large Load Diff

View File

@ -113,14 +113,19 @@ class EDLogs(FileSystemEventHandler):
'Raw' : defaultdict(int), 'Raw' : defaultdict(int),
'Manufactured' : defaultdict(int), 'Manufactured' : defaultdict(int),
'Encoded' : defaultdict(int), 'Encoded' : defaultdict(int),
'PaintJob' : None, 'Rank' : {},
'Rank' : { 'Combat': None, 'Trade': None, 'Explore': None, 'Empire': None, 'Federation': None, 'CQC': None }, 'Reputation' : {},
'Statistics' : {},
'Role' : None, # Crew role - None, Idle, FireCon, FighterCon 'Role' : None, # Crew role - None, Idle, FireCon, FighterCon
'Friends' : set(), # Online friends 'Friends' : set(), # Online friends
'ShipID' : None, 'ShipID' : None,
'ShipIdent' : None, 'ShipIdent' : None,
'ShipName' : None, 'ShipName' : None,
'ShipType' : None, 'ShipType' : None,
'HullValue' : None,
'ModulesValue' : None,
'Rebuy' : None,
'Modules' : None,
} }
def start(self, root): def start(self, root):
@ -224,24 +229,18 @@ class EDLogs(FileSystemEventHandler):
if self.live: if self.live:
if self.game_was_running: if self.game_was_running:
# Game is running locally # 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: if self.station:
entry = OrderedDict([ entry['StationName'] = self.station
('timestamp', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())), entry['StationType'] = self.stationtype
('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),
])
self.event_queue.append(json.dumps(entry, separators=(', ', ':'))) self.event_queue.append(json.dumps(entry, separators=(', ', ':')))
else: else:
self.event_queue.append(None) # Generate null event to update the display (with possibly out-of-date info) 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), 'Raw' : defaultdict(int),
'Manufactured' : defaultdict(int), 'Manufactured' : defaultdict(int),
'Encoded' : defaultdict(int), 'Encoded' : defaultdict(int),
'PaintJob' : None, 'Rank' : {},
'Rank' : { 'Combat': None, 'Trade': None, 'Explore': None, 'Empire': None, 'Federation': None, 'CQC': None }, 'Reputation' : {},
'Statistics' : {},
'Role' : None, 'Role' : None,
'Friends' : set(), 'Friends' : set(),
'ShipID' : None, 'ShipID' : None,
'ShipIdent' : None, 'ShipIdent' : None,
'ShipName' : None, 'ShipName' : None,
'ShipType' : 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': elif entry['event'] == 'LoadGame':
self.live = True
self.cmdr = entry['Commander'] self.cmdr = entry['Commander']
self.mode = entry.get('GameMode') # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) self.mode = entry.get('GameMode') # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event)
self.group = entry.get('Group') self.group = entry.get('Group')
@ -350,7 +355,9 @@ class EDLogs(FileSystemEventHandler):
'Captain' : None, 'Captain' : None,
'Credits' : entry['Credits'], 'Credits' : entry['Credits'],
'Loan' : entry['Loan'], 'Loan' : entry['Loan'],
'Rank' : { 'Combat': None, 'Trade': None, 'Explore': None, 'Empire': None, 'Federation': None, 'CQC': None }, 'Rank' : {},
'Reputation' : {},
'Statistics' : {},
'Role' : None, 'Role' : None,
}) })
elif entry['event'] == 'NewCommander': elif entry['event'] == 'NewCommander':
@ -367,25 +374,45 @@ class EDLogs(FileSystemEventHandler):
self.state['ShipIdent'] = None self.state['ShipIdent'] = None
self.state['ShipName'] = None self.state['ShipName'] = None
self.state['ShipType'] = self.canonicalise(entry['ShipType']) 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': elif entry['event'] == 'ShipyardSwap':
self.state['ShipID'] = entry['ShipID'] self.state['ShipID'] = entry['ShipID']
self.state['ShipIdent'] = None self.state['ShipIdent'] = None
self.state['ShipName'] = None self.state['ShipName'] = None
self.state['ShipType'] = self.canonicalise(entry['ShipType']) 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 elif entry['event'] == 'Loadout': # Note: Precedes LoadGame, ShipyardNew, follows ShipyardSwap, ShipyardBuy
self.state['ShipID'] = entry['ShipID'] self.state['ShipID'] = entry['ShipID']
self.state['ShipIdent'] = entry['ShipIdent'] self.state['ShipIdent'] = entry['ShipIdent']
self.state['ShipName'] = entry['ShipName'] self.state['ShipName'] = entry['ShipName']
self.state['ShipType'] = self.canonicalise(entry['Ship']) self.state['ShipType'] = self.canonicalise(entry['Ship'])
# Ignore other Modules since they're missing Engineer modification details self.state['HullValue'] = entry.get('HullValue') # not present on exiting Outfitting
self.state['PaintJob'] = 'paintjob_%s_default_defaultpaintjob' % self.state['ShipType'] self.state['ModulesValue'] = entry.get('ModulesValue') # "
for module in entry['Modules']: self.state['Rebuy'] = entry.get('Rebuy')
if module.get('Slot') == 'PaintJob' and module.get('Item'): self.state['Modules'] = dict([(thing['Slot'], thing) for thing in entry['Modules']])
self.state['PaintJob'] = self.canonicalise(module['Item']) elif entry['event'] == 'ModuleBuy':
elif entry['event'] in ['ModuleBuy', 'ModuleSell'] and entry['Slot'] == 'PaintJob': self.state['Modules'][entry['Slot']] = { 'Slot' : entry['Slot'],
self.state['PaintJob'] = self.canonicalise(entry.get('BuyItem')) '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']: elif entry['event'] in ['Undocked']:
self.station = None self.station = None
self.stationtype = None self.stationtype = None
@ -403,21 +430,30 @@ class EDLogs(FileSystemEventHandler):
entry.get('StationName')) # May be None entry.get('StationName')) # May be None
self.stationtype = entry.get('StationType') # May be None self.stationtype = entry.get('StationType') # May be None
self.stationservices = entry.get('StationServices') # None under E:D < 2.4 self.stationservices = entry.get('StationServices') # None under E:D < 2.4
elif entry['event'] == 'ApproachBody':
self.planet = entry['Body']
elif entry['event'] == 'SupercruiseExit': elif entry['event'] == 'SupercruiseExit':
self.planet = entry.get('Body') if entry.get('BodyType') == 'Planet' else None 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 self.planet = None
elif entry['event'] in ['Rank', 'Promotion']: elif entry['event'] in ['Rank', 'Promotion']:
for k,v in entry.iteritems(): payload = dict(entry)
if k in self.state['Rank']: payload.pop('event')
self.state['Rank'][k] = (v,0) payload.pop('timestamp')
for k,v in payload.iteritems():
self.state['Rank'][k] = (v,0)
elif entry['event'] == 'Progress': elif entry['event'] == 'Progress':
for k,v in entry.iteritems(): 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 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': elif entry['event'] == 'Cargo':
self.live = True # First event in 2.3
self.state['Cargo'] = defaultdict(int) self.state['Cargo'] = defaultdict(int)
self.state['Cargo'].update({ self.canonicalise(x['Name']): x['Count'] for x in entry['Inventory'] }) self.state['Cargo'].update({ self.canonicalise(x['Name']): x['Count'] for x in entry['Inventory'] })
elif entry['event'] in ['CollectCargo', 'MarketBuy', 'BuyDrones', 'MiningRefined']: elif entry['event'] in ['CollectCargo', 'MarketBuy', 'BuyDrones', 'MiningRefined']:
@ -432,6 +468,11 @@ class EDLogs(FileSystemEventHandler):
for reward in entry.get('CommodityReward', []): for reward in entry.get('CommodityReward', []):
commodity = self.canonicalise(reward['Name']) commodity = self.canonicalise(reward['Name'])
self.state['Cargo'][commodity] += reward.get('Count', 1) 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': elif entry['event'] == 'SearchAndRescue':
for item in entry.get('Items', []): for item in entry.get('Items', []):
commodity = self.canonicalise(item['Name']) commodity = self.canonicalise(item['Name'])
@ -451,15 +492,39 @@ class EDLogs(FileSystemEventHandler):
self.state[entry['Category']][material] -= entry['Count'] self.state[entry['Category']][material] -= entry['Count']
if self.state[entry['Category']][material] <= 0: if self.state[entry['Category']][material] <= 0:
self.state[entry['Category']].pop(material) self.state[entry['Category']].pop(material)
elif entry['event'] in ['EngineerCraft', 'Synthesis']: elif entry['event'] == 'Synthesis':
for category in ['Raw', 'Manufactured', 'Encoded']: 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']) material = self.canonicalise(x['Name'])
if material in self.state[category]: if material in self.state[category]:
self.state[category][material] -= x['Count'] self.state[category][material] -= x['Count']
if self.state[category][material] <= 0: if self.state[category][material] <= 0:
self.state[category].pop(material) 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': elif entry['event'] == 'EngineerContribution':
commodity = self.canonicalise(entry.get('Commodity')) commodity = self.canonicalise(entry.get('Commodity'))
if commodity: if commodity:
@ -579,5 +644,24 @@ class EDLogs(FileSystemEventHandler):
return False 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 # singleton
monitor = EDLogs() monitor = EDLogs()

View File

@ -261,6 +261,7 @@ internal_map = {
'passengercabin' : 'Passenger Cabin', 'passengercabin' : 'Passenger Cabin',
'prospector' : 'Prospector Limpet Controller', 'prospector' : 'Prospector Limpet Controller',
'refinery' : 'Refinery', 'refinery' : 'Refinery',
'recon' : 'Recon Limpet Controller',
'repair' : 'Repair Limpet Controller', 'repair' : 'Repair Limpet Controller',
'repairer' : 'Auto Field-Maintenance Unit', 'repairer' : 'Auto Field-Maintenance Unit',
'resourcesiphon' : 'Hatch Breaker Limpet Controller', 'resourcesiphon' : 'Hatch Breaker Limpet Controller',

21
plug.py
View File

@ -226,6 +226,27 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state):
return error 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): def notify_system_changed(timestamp, system, coordinates):
""" """
Send notification data to each plugin when we arrive at a new system. Send notification data to each plugin when we arrive at a new system.

View File

@ -34,11 +34,14 @@ this.session = requests.Session()
this.queue = Queue() # Items to be sent to EDSM by worker thread this.queue = Queue() # Items to be sent to EDSM by worker thread
this.discardedEvents = [] # List discarded events from EDSM this.discardedEvents = [] # List discarded events from EDSM
this.lastship = None # Description of last ship that we sent to 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 this.lastlookup = False # whether the last lookup succeeded
# Game state # Game state
this.multicrew = False # don't send captain's ship info to EDSM while on a crew this.multicrew = False # don't send captain's ship info to EDSM while on a crew
this.coordinates = None this.coordinates = None
this.newgame = False # starting up - batch initial burst of events
this.newgame_docked = False # starting up while docked
def plugin_start(): def plugin_start():
# Can't be earlier since can only call PhotoImage after window is created # 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': elif entry['event'] == 'LoadGame':
this.coordinates = None 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 # 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: 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 # Introduce transient states into the event
@ -210,15 +223,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
entry.update(transient) entry.update(transient)
if entry['event'] == 'LoadGame': if entry['event'] == 'LoadGame':
# Synthesise Cargo and Materials events on LoadGame since we will have missed them because Cmdr was unknown # Synthesise Materials events on LoadGame since we will have missed it
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))
materials = { materials = {
'timestamp': entry['timestamp'], 'timestamp': entry['timestamp'],
'event': 'Materials', 'event': 'Materials',
@ -231,6 +236,14 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
this.queue.put((cmdr, entry)) 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 # Update system data
def cmdr_data(data, is_beta): def cmdr_data(data, is_beta):
@ -258,10 +271,6 @@ def cmdr_data(data, is_beta):
this.queue.put((cmdr, { this.queue.put((cmdr, {
'event': 'Coriolis', 'timestamp': timestamp, '_shipId': data['ship']['id'], 'url': coriolis.url(data, is_beta) '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 this.lastship = ship
@ -310,14 +319,14 @@ def worker():
if msgnum // 100 == 2: if msgnum // 100 == 2:
print('EDSM\t%s %s\t%s' % (msgnum, msg, json.dumps(pending, separators = (',', ': ')))) print('EDSM\t%s %s\t%s' % (msgnum, msg, json.dumps(pending, separators = (',', ': '))))
plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg)) plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg))
elif not closing: else:
# Update main window's system status for e, r in zip(pending, reply['events']):
for i in range(len(pending) - 1, -1, -1): if not closing and e['event'] in ['StartUp', 'Location', 'FSDJump']:
if pending[i]['event'] in ['StartUp', 'Location', 'FSDJump']: # Update main window's system status
this.lastlookup = reply['events'][i] this.lastlookup = r
this.system.event_generate('<<EDSMStatus>>', when="tail") # calls update_status in main thread 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 = [] pending = []
break break
@ -334,10 +343,16 @@ def worker():
# Whether any of the entries should be sent immediately # Whether any of the entries should be sent immediately
def should_send(entries): def should_send(entries):
for entry in entries: for entry in entries:
if (entry['event'] not in ['CommunityGoal', # Spammed periodically if (entry['event'] == 'Cargo' and not this.newgame_docked) or entry['event'] == 'Docked':
'Cargo', 'Loadout', 'Materials', 'LoadGame', 'Rank', 'Progress', # Will be followed by 'Docked' or 'Location' # Cargo is the last event on startup, unless starting when docked in which case Docked is the last event
'ShipyardBuy', 'ShipyardNew', 'ShipyardSwap'] and # " this.newgame = False
not (entry['event'] == 'Location' and entry.get('Docked'))): # " 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 True
return False return False

View File

@ -7,6 +7,7 @@ import json
import requests import requests
import sys import sys
import time import time
from operator import itemgetter
from Queue import Queue from Queue import Queue
from threading import Thread 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.multicrew = False # don't send captain's ship info to Inara while on a crew
this.newuser = False # just entered API Key this.newuser = False # just entered API Key
this.undocked = False # just undocked 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.cargo = None
this.materials = None this.materials = None
this.lastcredits = 0 # Send credit update soon after Startup / new game 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 this.shipswap = False # just swapped ship
# URLs # URLs
@ -154,7 +157,9 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
this.cargo = None this.cargo = None
this.materials = None this.materials = None
this.lastcredits = 0 this.lastcredits = 0
this.needfleet = True this.storedmodules = None
this.loadout = None
this.fleet = None
this.shipswap = False this.shipswap = False
elif entry['event'] in ['Resurrect', 'ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy']: elif entry['event'] in ['Resurrect', 'ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy']:
# Events that mean a significant change in credits so we should send credits after next "Update" # 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 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. # Always send an update on Docked, FSDJump, Undocked+SuperCruise, Promotion and EngineerProgress.
# Also send material and cargo (if changed) whenever we send an update. # 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: try:
old_events = len(this.events) # Will only send existing events if we add a new event below 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 # 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(): for k,v in state['Rank'].iteritems():
if v is not None: if v is not None:
add_event('setCommanderRankPilot', entry['timestamp'], add_event('setCommanderRankPilot', entry['timestamp'],
@ -181,6 +195,15 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
('rankValue', v[0]), ('rankValue', v[0]),
('rankProgress', v[1] / 100.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': elif entry['event'] == 'Promotion':
for k,v in state['Rank'].iteritems(): for k,v in state['Rank'].iteritems():
if k in entry: if k in entry:
@ -227,27 +250,33 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
])) ]))
# Update ship # Update ship
if (entry['event'] in ['StartUp', 'Location', 'ShipyardNew'] or if (entry['event'] in ['StartUp', 'Cargo'] or
(entry['event'] == 'Loadout' and this.shipswap) or (entry['event'] == 'Loadout' and this.shipswap) or
this.newuser): this.newuser):
if entry['event'] == 'ShipyardNew': data = OrderedDict([
add_event('addCommanderShip', entry['timestamp'], ('shipType', state['ShipType']),
OrderedDict([ ('shipGameID', state['ShipID']),
('shipType', state['ShipType']), ('shipName', state['ShipName']), # Can be None
('shipGameID', state['ShipID']), ('shipIdent', state['ShipIdent']), # Can be None
])) ('isCurrentShip', True),
add_event('setCommanderShip', entry['timestamp'], ])
OrderedDict([ if state['HullValue']:
('shipType', state['ShipType']), data['shipHullValue'] = state['HullValue']
('shipGameID', state['ShipID']), if state['ModulesValue']:
('shipName', state['ShipName']), # Can be None data['shipModulesValue'] = state['ModulesValue']
('shipIdent', state['ShipIdent']), # Can be None data['shipRebuyCost'] = state['Rebuy']
('isCurrentShip', True), 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 this.shipswap = False
# Update location # 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 this.undocked = False
add_event('setCommanderTravelLocation', entry['timestamp'], add_event('setCommanderTravelLocation', entry['timestamp'],
OrderedDict([ OrderedDict([
@ -262,7 +291,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
# Undocked and now docking again. Don't send. # Undocked and now docking again. Don't send.
this.undocked = False this.undocked = False
elif this.suppress_docked: 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 this.suppress_docked = False
else: else:
add_event('addCommanderTravelDock', entry['timestamp'], add_event('addCommanderTravelDock', entry['timestamp'],
@ -325,7 +354,15 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
# #
# Selling / swapping ships # 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': if entry['event'] == 'ShipyardSwap':
this.shipswap = True # Don't know new ship name and ident 'til the following Loadout event this.shipswap = True # Don't know new ship name and ident 'til the following Loadout event
if 'StoreShipID' in entry: if 'StoreShipID' in entry:
@ -363,6 +400,79 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
('transferTime', entry['TransferTime']), ('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 # Missions
if entry['event'] == 'MissionAccepted': if entry['event'] == 'MissionAccepted':
data = OrderedDict([ 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']] data['rewardPermits'] = [{ 'starsystemName': x } for x in entry['PermitsAwarded']]
if 'CommodityReward' in entry: if 'CommodityReward' in entry:
data['rewardCommodities'] = [{ 'itemName': x['Name'], 'itemCount': x['Count'] } for x in entry['CommodityReward']] 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) 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': elif entry['event'] == 'MissionFailed':
add_event('setCommanderMissionFailed', entry['timestamp'], { 'missionGameID': entry['MissionID'] }) 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]) data['tierReached'] = int(goal['TierReached'].split()[-1])
if 'TopRankSize' in goal: if 'TopRankSize' in goal:
data['topRankSize'] = goal['TopRankSize'] 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) add_event('setCommunityGoal', entry['timestamp'], data)
data = OrderedDict([ data = OrderedDict([
@ -503,54 +612,15 @@ def cmdr_data(data, is_beta):
this.cmdr = data['commander']['name'] this.cmdr = data['commander']['name']
if config.getint('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr): 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): 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 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([ OrderedDict([
('commanderCredits', data['commander']['credits']), ('commanderCredits', data['commander']['credits']),
('commanderAssets', assets),
('commanderLoan', data['commander'].get('debt', 0)), ('commanderLoan', data['commander'].get('debt', 0)),
])) ]))
this.lastcredits = float(data['commander']['credits']) 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): def add_event(name, timestamp, data):
this.events.append(OrderedDict([ this.events.append(OrderedDict([
('eventName', name), ('eventName', name),
@ -603,7 +673,7 @@ def worker():
# Log individual errors and warnings # Log individual errors and warnings
for data_event, reply_event in zip(data['events'], reply['events']): for data_event, reply_event in zip(data['events'], reply['events']):
if reply_event['eventStatus'] != 200: 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: 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'])))) 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']: if data_event['eventName'] in ['addCommanderTravelDock', 'addCommanderTravelFSDJump', 'setCommanderTravelLocation']:

128
ships.p
View File

@ -10,190 +10,196 @@ S'hullMass'
p6 p6
I35 I35
saa(lp7 saa(lp7
S'Anaconda' S'Alliance Chieftain'
p8 p8
a(dp9 a(dp9
g6 g6
I400 I420
saa(lp10 saa(lp10
S'Asp Explorer' S'Anaconda'
p11 p11
a(dp12 a(dp12
g6 g6
I280 I400
saa(lp13 saa(lp13
S'Asp Scout' S'Asp Explorer'
p14 p14
a(dp15 a(dp15
g6 g6
I150 I280
saa(lp16 saa(lp16
S'Beluga Liner' S'Asp Scout'
p17 p17
a(dp18 a(dp18
g6 g6
I950 I150
saa(lp19 saa(lp19
S'Cobra MkIII' S'Beluga Liner'
p20 p20
a(dp21 a(dp21
g6 g6
I180 I950
saa(lp22 saa(lp22
S'Cobra MkIV' S'Cobra MkIII'
p23 p23
a(dp24 a(dp24
g6 g6
I210 I180
saa(lp25 saa(lp25
S'Diamondback Explorer' S'Cobra MkIV'
p26 p26
a(dp27 a(dp27
g6 g6
I260 I210
saa(lp28 saa(lp28
S'Diamondback Scout' S'Diamondback Explorer'
p29 p29
a(dp30 a(dp30
g6 g6
I170 I260
saa(lp31 saa(lp31
S'Dolphin' S'Diamondback Scout'
p32 p32
a(dp33 a(dp33
g6 g6
I140 I170
saa(lp34 saa(lp34
S'Eagle' S'Dolphin'
p35 p35
a(dp36 a(dp36
g6 g6
I50 I140
saa(lp37 saa(lp37
S'Federal Assault Ship' S'Eagle'
p38 p38
a(dp39 a(dp39
g6 g6
I480 I50
saa(lp40 saa(lp40
S'Federal Corvette' S'Federal Assault Ship'
p41 p41
a(dp42 a(dp42
g6 g6
I900 I480
saa(lp43 saa(lp43
S'Federal Dropship' S'Federal Corvette'
p44 p44
a(dp45 a(dp45
g6 g6
I580 I900
saa(lp46 saa(lp46
S'Federal Gunship' S'Federal Dropship'
p47 p47
a(dp48 a(dp48
g6 g6
I580 I580
saa(lp49 saa(lp49
S'Fer-de-Lance' S'Federal Gunship'
p50 p50
a(dp51 a(dp51
g6 g6
I250 I580
saa(lp52 saa(lp52
S'Hauler' S'Fer-de-Lance'
p53 p53
a(dp54 a(dp54
g6 g6
I14 I250
saa(lp55 saa(lp55
S'Imperial Clipper' S'Hauler'
p56 p56
a(dp57 a(dp57
g6 g6
I400 I14
saa(lp58 saa(lp58
S'Imperial Courier' S'Imperial Clipper'
p59 p59
a(dp60 a(dp60
g6 g6
I35 I400
saa(lp61 saa(lp61
S'Imperial Cutter' S'Imperial Courier'
p62 p62
a(dp63 a(dp63
g6 g6
I1100 I35
saa(lp64 saa(lp64
S'Imperial Eagle' S'Imperial Cutter'
p65 p65
a(dp66 a(dp66
g6 g6
I50 I1100
saa(lp67 saa(lp67
S'Keelback' S'Imperial Eagle'
p68 p68
a(dp69 a(dp69
g6 g6
I180 I50
saa(lp70 saa(lp70
S'Orca' S'Keelback'
p71 p71
a(dp72 a(dp72
g6 g6
I290 I180
saa(lp73 saa(lp73
S'Python' S'Orca'
p74 p74
a(dp75 a(dp75
g6 g6
I350 I290
saa(lp76 saa(lp76
S'Sidewinder' S'Python'
p77 p77
a(dp78 a(dp78
g6 g6
I25 I350
saa(lp79 saa(lp79
S'Type-10 Defender' S'Sidewinder'
p80 p80
a(dp81 a(dp81
g6 g6
I1200 I25
saa(lp82 saa(lp82
S'Type-6 Transporter' S'Type-10 Defender'
p83 p83
a(dp84 a(dp84
g6 g6
I155 I1200
saa(lp85 saa(lp85
S'Type-7 Transporter' S'Type-6 Transporter'
p86 p86
a(dp87 a(dp87
g6 g6
I420 I155
saa(lp88 saa(lp88
S'Type-9 Heavy' S'Type-7 Transporter'
p89 p89
a(dp90 a(dp90
g6 g6
I1000 I420
saa(lp91 saa(lp91
S'Viper MkIII' S'Type-9 Heavy'
p92 p92
a(dp93 a(dp93
g6 g6
I50 I850
saa(lp94 saa(lp94
S'Viper MkIV' S'Viper MkIII'
p95 p95
a(dp96 a(dp96
g6 g6
I190 I50
saa(lp97 saa(lp97
S'Vulture' S'Viper MkIV'
p98 p98
a(dp99 a(dp99
g6 g6
I190
saa(lp100
S'Vulture'
p101
a(dp102
g6
I230 I230
saatRp100 saatRp103
. .