From b9815400b3698857f75442fce66efece7dd91912 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sun, 28 Jan 2018 00:45:36 +0000
Subject: [PATCH 01/15] Add Alliance Chieftain and Recon Limpet Controller

---
 companion.py  |    1 +
 coriolis-data |    2 +-
 coriolis.py   |    7 +-
 modules.p     | 5828 +++++++++++++++++++++++++------------------------
 outfitting.py |    1 +
 ships.p       |  128 +-
 6 files changed, 3034 insertions(+), 2933 deletions(-)

diff --git a/companion.py b/companion.py
index 9221e482..858f9180 100644
--- a/companion.py
+++ b/companion.py
@@ -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',
diff --git a/coriolis-data b/coriolis-data
index a3ae3e34..44d57f26 160000
--- a/coriolis-data
+++ b/coriolis-data
@@ -1 +1 @@
-Subproject commit a3ae3e34ad879d1504a7d2fdc3fce4854820051b
+Subproject commit 44d57f26a8099cfa8d91815f453c7bba303a63ec
diff --git a/coriolis.py b/coriolis.py
index 4e947fb4..433aa390 100755
--- a/coriolis.py
+++ b/coriolis.py
@@ -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'))
diff --git a/modules.p b/modules.p
index 2d1a1cb5..9e6e42ba 100644
--- a/modules.p
+++ b/modules.p
@@ -3139,7 +3139,7 @@ g7
 I0
 saa(lp1102
 (g1098
-S'Anaconda'
+S'Alliance Chieftain'
 p1103
 S'1'
 S'I'
@@ -3149,7 +3149,7 @@ g7
 I0
 saa(lp1106
 (g1098
-S'Asp Explorer'
+S'Anaconda'
 p1107
 S'1'
 S'I'
@@ -3159,7 +3159,7 @@ g7
 I0
 saa(lp1110
 (g1098
-S'Asp Scout'
+S'Asp Explorer'
 p1111
 S'1'
 S'I'
@@ -3169,7 +3169,7 @@ g7
 I0
 saa(lp1114
 (g1098
-S'Beluga Liner'
+S'Asp Scout'
 p1115
 S'1'
 S'I'
@@ -3179,7 +3179,7 @@ g7
 I0
 saa(lp1118
 (g1098
-S'Cobra MkIII'
+S'Beluga Liner'
 p1119
 S'1'
 S'I'
@@ -3189,7 +3189,7 @@ g7
 I0
 saa(lp1122
 (g1098
-S'Cobra MkIV'
+S'Cobra MkIII'
 p1123
 S'1'
 S'I'
@@ -3199,7 +3199,7 @@ g7
 I0
 saa(lp1126
 (g1098
-S'Diamondback Explorer'
+S'Cobra MkIV'
 p1127
 S'1'
 S'I'
@@ -3209,7 +3209,7 @@ g7
 I0
 saa(lp1130
 (g1098
-S'Diamondback Scout'
+S'Diamondback Explorer'
 p1131
 S'1'
 S'I'
@@ -3219,7 +3219,7 @@ g7
 I0
 saa(lp1134
 (g1098
-S'Dolphin'
+S'Diamondback Scout'
 p1135
 S'1'
 S'I'
@@ -3229,7 +3229,7 @@ g7
 I0
 saa(lp1138
 (g1098
-S'Eagle'
+S'Dolphin'
 p1139
 S'1'
 S'I'
@@ -3239,7 +3239,7 @@ g7
 I0
 saa(lp1142
 (g1098
-S'Federal Assault Ship'
+S'Eagle'
 p1143
 S'1'
 S'I'
@@ -3249,7 +3249,7 @@ g7
 I0
 saa(lp1146
 (g1098
-S'Federal Corvette'
+S'Federal Assault Ship'
 p1147
 S'1'
 S'I'
@@ -3259,7 +3259,7 @@ g7
 I0
 saa(lp1150
 (g1098
-S'Federal Dropship'
+S'Federal Corvette'
 p1151
 S'1'
 S'I'
@@ -3269,7 +3269,7 @@ g7
 I0
 saa(lp1154
 (g1098
-S'Federal Gunship'
+S'Federal Dropship'
 p1155
 S'1'
 S'I'
@@ -3279,7 +3279,7 @@ g7
 I0
 saa(lp1158
 (g1098
-S'Fer-de-Lance'
+S'Federal Gunship'
 p1159
 S'1'
 S'I'
@@ -3289,7 +3289,7 @@ g7
 I0
 saa(lp1162
 (g1098
-S'Hauler'
+S'Fer-de-Lance'
 p1163
 S'1'
 S'I'
@@ -3299,7 +3299,7 @@ g7
 I0
 saa(lp1166
 (g1098
-S'Imperial Clipper'
+S'Hauler'
 p1167
 S'1'
 S'I'
@@ -3309,7 +3309,7 @@ g7
 I0
 saa(lp1170
 (g1098
-S'Imperial Courier'
+S'Imperial Clipper'
 p1171
 S'1'
 S'I'
@@ -3319,7 +3319,7 @@ g7
 I0
 saa(lp1174
 (g1098
-S'Imperial Cutter'
+S'Imperial Courier'
 p1175
 S'1'
 S'I'
@@ -3329,7 +3329,7 @@ g7
 I0
 saa(lp1178
 (g1098
-S'Imperial Eagle'
+S'Imperial Cutter'
 p1179
 S'1'
 S'I'
@@ -3339,7 +3339,7 @@ g7
 I0
 saa(lp1182
 (g1098
-S'Keelback'
+S'Imperial Eagle'
 p1183
 S'1'
 S'I'
@@ -3349,7 +3349,7 @@ g7
 I0
 saa(lp1186
 (g1098
-S'Orca'
+S'Keelback'
 p1187
 S'1'
 S'I'
@@ -3359,7 +3359,7 @@ g7
 I0
 saa(lp1190
 (g1098
-S'Python'
+S'Orca'
 p1191
 S'1'
 S'I'
@@ -3369,7 +3369,7 @@ g7
 I0
 saa(lp1194
 (g1098
-S'Sidewinder'
+S'Python'
 p1195
 S'1'
 S'I'
@@ -3379,7 +3379,7 @@ g7
 I0
 saa(lp1198
 (g1098
-S'Type-10 Defender'
+S'Sidewinder'
 p1199
 S'1'
 S'I'
@@ -3389,7 +3389,7 @@ g7
 I0
 saa(lp1202
 (g1098
-S'Type-6 Transporter'
+S'Type-10 Defender'
 p1203
 S'1'
 S'I'
@@ -3399,7 +3399,7 @@ g7
 I0
 saa(lp1206
 (g1098
-S'Type-7 Transporter'
+S'Type-6 Transporter'
 p1207
 S'1'
 S'I'
@@ -3409,7 +3409,7 @@ g7
 I0
 saa(lp1210
 (g1098
-S'Type-9 Heavy'
+S'Type-7 Transporter'
 p1211
 S'1'
 S'I'
@@ -3419,7 +3419,7 @@ g7
 I0
 saa(lp1214
 (g1098
-S'Viper MkIII'
+S'Type-9 Heavy'
 p1215
 S'1'
 S'I'
@@ -3429,7 +3429,7 @@ g7
 I0
 saa(lp1218
 (g1098
-S'Viper MkIV'
+S'Viper MkIII'
 p1219
 S'1'
 S'I'
@@ -3439,7 +3439,7 @@ g7
 I0
 saa(lp1222
 (g1098
-S'Vulture'
+S'Viper MkIV'
 p1223
 S'1'
 S'I'
@@ -3448,1756 +3448,1759 @@ a(dp1225
 g7
 I0
 saa(lp1226
-(S'Luxury Class Passenger Cabin'
+(g1098
+S'Vulture'
 p1227
-NS'5'
-S'B'
+S'1'
+S'I'
 tp1228
 a(dp1229
 g7
-I20
+I0
 saa(lp1230
-(g1227
+(S'Luxury Class Passenger Cabin'
+p1231
+NS'5'
+S'B'
+tp1232
+a(dp1233
+g7
+I20
+saa(lp1234
+(g1231
 NS'6'
 S'B'
-tp1231
-a(dp1232
-g7
-I40
-saa(lp1233
-(S'Military Grade Composite'
-p1234
-g1099
-S'1'
-S'I'
 tp1235
 a(dp1236
 g7
-I5
+I40
 saa(lp1237
-(g1234
+(S'Military Grade Composite'
+p1238
+g1099
+S'1'
+S'I'
+tp1239
+a(dp1240
+g7
+I5
+saa(lp1241
+(g1238
 g1103
 S'1'
 S'I'
-tp1238
-a(dp1239
+tp1242
+a(dp1243
 g7
-I60
-saa(lp1240
-(g1234
+I150
+saa(lp1244
+(g1238
 g1107
 S'1'
 S'I'
-tp1241
-a(dp1242
+tp1245
+a(dp1246
 g7
-I42
-saa(lp1243
-(g1234
+I60
+saa(lp1247
+(g1238
 g1111
 S'1'
 S'I'
-tp1244
-a(dp1245
+tp1248
+a(dp1249
 g7
 I42
-saa(lp1246
-(g1234
+saa(lp1250
+(g1238
 g1115
 S'1'
 S'I'
-tp1247
-a(dp1248
+tp1251
+a(dp1252
 g7
-I165
-saa(lp1249
-(g1234
+I42
+saa(lp1253
+(g1238
 g1119
 S'1'
 S'I'
-tp1250
-a(dp1251
+tp1254
+a(dp1255
 g7
-I27
-saa(lp1252
-(g1234
+I165
+saa(lp1256
+(g1238
 g1123
 S'1'
 S'I'
-tp1253
-a(dp1254
+tp1257
+a(dp1258
 g7
 I27
-saa(lp1255
-(g1234
+saa(lp1259
+(g1238
 g1127
 S'1'
 S'I'
-tp1256
-a(dp1257
+tp1260
+a(dp1261
 g7
-I47
-saa(lp1258
-(g1234
+I27
+saa(lp1262
+(g1238
 g1131
 S'1'
 S'I'
-tp1259
-a(dp1260
+tp1263
+a(dp1264
 g7
-I26
-saa(lp1261
-(g1234
+I47
+saa(lp1265
+(g1238
 g1135
 S'1'
 S'I'
-tp1262
-a(dp1263
+tp1266
+a(dp1267
 g7
-I63
-saa(lp1264
-(g1234
+I26
+saa(lp1268
+(g1238
 g1139
 S'1'
 S'I'
-tp1265
-a(dp1266
+tp1269
+a(dp1270
 g7
-I8
-saa(lp1267
-(g1234
+I63
+saa(lp1271
+(g1238
 g1143
 S'1'
 S'I'
-tp1268
-a(dp1269
+tp1272
+a(dp1273
 g7
-I87
-saa(lp1270
-(g1234
+I8
+saa(lp1274
+(g1238
 g1147
 S'1'
 S'I'
-tp1271
-a(dp1272
+tp1275
+a(dp1276
 g7
-I60
-saa(lp1273
-(g1234
+I87
+saa(lp1277
+(g1238
 g1151
 S'1'
 S'I'
-tp1274
-a(dp1275
+tp1278
+a(dp1279
 g7
-I87
-saa(lp1276
-(g1234
+I60
+saa(lp1280
+(g1238
 g1155
 S'1'
 S'I'
-tp1277
-a(dp1278
+tp1281
+a(dp1282
 g7
 I87
-saa(lp1279
-(g1234
+saa(lp1283
+(g1238
 g1159
 S'1'
 S'I'
-tp1280
-a(dp1281
+tp1284
+a(dp1285
 g7
-I38
-saa(lp1282
-(g1234
+I87
+saa(lp1286
+(g1238
 g1163
 S'1'
 S'I'
-tp1283
-a(dp1284
+tp1287
+a(dp1288
 g7
-I2
-saa(lp1285
-(g1234
+I38
+saa(lp1289
+(g1238
 g1167
 S'1'
 S'I'
-tp1286
-a(dp1287
+tp1290
+a(dp1291
 g7
-I60
-saa(lp1288
-(g1234
+I2
+saa(lp1292
+(g1238
 g1171
 S'1'
 S'I'
-tp1289
-a(dp1290
+tp1293
+a(dp1294
 g7
-I8
-saa(lp1291
-(g1234
+I60
+saa(lp1295
+(g1238
 g1175
 S'1'
 S'I'
-tp1292
-a(dp1293
+tp1296
+a(dp1297
 g7
-I60
-saa(lp1294
-(g1234
+I8
+saa(lp1298
+(g1238
 g1179
 S'1'
 S'I'
-tp1295
-a(dp1296
+tp1299
+a(dp1300
 g7
-I8
-saa(lp1297
-(g1234
+I60
+saa(lp1301
+(g1238
 g1183
 S'1'
 S'I'
-tp1298
-a(dp1299
+tp1302
+a(dp1303
 g7
-I23
-saa(lp1300
-(g1234
+I8
+saa(lp1304
+(g1238
 g1187
 S'1'
 S'I'
-tp1301
-a(dp1302
+tp1305
+a(dp1306
 g7
-I87
-saa(lp1303
-(g1234
+I23
+saa(lp1307
+(g1238
 g1191
 S'1'
 S'I'
-tp1304
-a(dp1305
+tp1308
+a(dp1309
 g7
-I53
-saa(lp1306
-(g1234
+I87
+saa(lp1310
+(g1238
 g1195
 S'1'
 S'I'
-tp1307
-a(dp1308
+tp1311
+a(dp1312
 g7
-I4
-saa(lp1309
-(g1234
+I53
+saa(lp1313
+(g1238
 g1199
 S'1'
 S'I'
-tp1310
-a(dp1311
+tp1314
+a(dp1315
 g7
-I150
-saa(lp1312
-(g1234
+I4
+saa(lp1316
+(g1238
 g1203
 S'1'
 S'I'
-tp1313
-a(dp1314
+tp1317
+a(dp1318
 g7
-I23
-saa(lp1315
-(g1234
+I150
+saa(lp1319
+(g1238
 g1207
 S'1'
 S'I'
-tp1316
-a(dp1317
+tp1320
+a(dp1321
 g7
-I63
-saa(lp1318
-(g1234
+I23
+saa(lp1322
+(g1238
 g1211
 S'1'
 S'I'
-tp1319
-a(dp1320
+tp1323
+a(dp1324
 g7
-I150
-saa(lp1321
-(g1234
+I63
+saa(lp1325
+(g1238
 g1215
 S'1'
 S'I'
-tp1322
-a(dp1323
+tp1326
+a(dp1327
 g7
-I9
-saa(lp1324
-(g1234
+I150
+saa(lp1328
+(g1238
 g1219
 S'1'
 S'I'
-tp1325
-a(dp1326
+tp1329
+a(dp1330
 g7
 I9
-saa(lp1327
-(g1234
+saa(lp1331
+(g1238
 g1223
 S'1'
 S'I'
-tp1328
-a(dp1329
-g7
-I35
-saa(lp1330
-(S'Mine Launcher'
-p1331
-NS'1'
-S'I'
 tp1332
 a(dp1333
 g7
-I2
+I9
 saa(lp1334
-(g1331
-NS'2'
+(g1238
+g1227
+S'1'
 S'I'
 tp1335
 a(dp1336
 g7
-I4
+I35
 saa(lp1337
-(S'Mining Lance Beam Laser'
+(S'Mine Launcher'
 p1338
 NS'1'
-S'D'
+S'I'
 tp1339
 a(dp1340
 g7
 I2
 saa(lp1341
-(S'Mining Laser'
-p1342
-NS'1'
-S'D'
-tp1343
-a(dp1344
-g7
-I2
-saa(lp1345
-(g1342
+(g1338
 NS'2'
+S'I'
+tp1342
+a(dp1343
+g7
+I4
+saa(lp1344
+(S'Mining Lance Beam Laser'
+p1345
+NS'1'
 S'D'
 tp1346
 a(dp1347
 g7
 I2
 saa(lp1348
-(S'Mirrored Surface Composite'
+(S'Mining Laser'
 p1349
-g1099
-S'1'
-S'I'
+NS'1'
+S'D'
 tp1350
 a(dp1351
 g7
-I5
+I2
 saa(lp1352
 (g1349
-g1103
-S'1'
-S'I'
+NS'2'
+S'D'
 tp1353
 a(dp1354
 g7
-I60
+I2
 saa(lp1355
-(g1349
+(S'Mirrored Surface Composite'
+p1356
+g1099
+S'1'
+S'I'
+tp1357
+a(dp1358
+g7
+I5
+saa(lp1359
+(g1356
+g1103
+S'1'
+S'I'
+tp1360
+a(dp1361
+g7
+I150
+saa(lp1362
+(g1356
 g1107
 S'1'
 S'I'
-tp1356
-a(dp1357
+tp1363
+a(dp1364
 g7
-I42
-saa(lp1358
-(g1349
+I60
+saa(lp1365
+(g1356
 g1111
 S'1'
 S'I'
-tp1359
-a(dp1360
+tp1366
+a(dp1367
 g7
 I42
-saa(lp1361
-(g1349
+saa(lp1368
+(g1356
 g1115
 S'1'
 S'I'
-tp1362
-a(dp1363
+tp1369
+a(dp1370
 g7
-I165
-saa(lp1364
-(g1349
+I42
+saa(lp1371
+(g1356
 g1119
 S'1'
 S'I'
-tp1365
-a(dp1366
+tp1372
+a(dp1373
 g7
-I27
-saa(lp1367
-(g1349
+I165
+saa(lp1374
+(g1356
 g1123
 S'1'
 S'I'
-tp1368
-a(dp1369
+tp1375
+a(dp1376
 g7
 I27
-saa(lp1370
-(g1349
+saa(lp1377
+(g1356
 g1127
 S'1'
 S'I'
-tp1371
-a(dp1372
+tp1378
+a(dp1379
 g7
-I26
-saa(lp1373
-(g1349
+I27
+saa(lp1380
+(g1356
 g1131
 S'1'
 S'I'
-tp1374
-a(dp1375
+tp1381
+a(dp1382
 g7
 I26
-saa(lp1376
-(g1349
+saa(lp1383
+(g1356
 g1135
 S'1'
 S'I'
-tp1377
-a(dp1378
+tp1384
+a(dp1385
 g7
-I63
-saa(lp1379
-(g1349
+I26
+saa(lp1386
+(g1356
 g1139
 S'1'
 S'I'
-tp1380
-a(dp1381
+tp1387
+a(dp1388
 g7
-I8
-saa(lp1382
-(g1349
+I63
+saa(lp1389
+(g1356
 g1143
 S'1'
 S'I'
-tp1383
-a(dp1384
+tp1390
+a(dp1391
 g7
-I87
-saa(lp1385
-(g1349
+I8
+saa(lp1392
+(g1356
 g1147
 S'1'
 S'I'
-tp1386
-a(dp1387
+tp1393
+a(dp1394
 g7
-I60
-saa(lp1388
-(g1349
+I87
+saa(lp1395
+(g1356
 g1151
 S'1'
 S'I'
-tp1389
-a(dp1390
+tp1396
+a(dp1397
 g7
-I87
-saa(lp1391
-(g1349
+I60
+saa(lp1398
+(g1356
 g1155
 S'1'
 S'I'
-tp1392
-a(dp1393
+tp1399
+a(dp1400
 g7
 I87
-saa(lp1394
-(g1349
+saa(lp1401
+(g1356
 g1159
 S'1'
 S'I'
-tp1395
-a(dp1396
+tp1402
+a(dp1403
 g7
-I38
-saa(lp1397
-(g1349
+I87
+saa(lp1404
+(g1356
 g1163
 S'1'
 S'I'
-tp1398
-a(dp1399
+tp1405
+a(dp1406
 g7
-I2
-saa(lp1400
-(g1349
+I38
+saa(lp1407
+(g1356
 g1167
 S'1'
 S'I'
-tp1401
-a(dp1402
+tp1408
+a(dp1409
 g7
-I60
-saa(lp1403
-(g1349
+I2
+saa(lp1410
+(g1356
 g1171
 S'1'
 S'I'
-tp1404
-a(dp1405
+tp1411
+a(dp1412
 g7
-I8
-saa(lp1406
-(g1349
+I60
+saa(lp1413
+(g1356
 g1175
 S'1'
 S'I'
-tp1407
-a(dp1408
+tp1414
+a(dp1415
 g7
-I60
-saa(lp1409
-(g1349
+I8
+saa(lp1416
+(g1356
 g1179
 S'1'
 S'I'
-tp1410
-a(dp1411
+tp1417
+a(dp1418
 g7
-I8
-saa(lp1412
-(g1349
+I60
+saa(lp1419
+(g1356
 g1183
 S'1'
 S'I'
-tp1413
-a(dp1414
+tp1420
+a(dp1421
 g7
-I23
-saa(lp1415
-(g1349
+I8
+saa(lp1422
+(g1356
 g1187
 S'1'
 S'I'
-tp1416
-a(dp1417
+tp1423
+a(dp1424
 g7
-I87
-saa(lp1418
-(g1349
+I23
+saa(lp1425
+(g1356
 g1191
 S'1'
 S'I'
-tp1419
-a(dp1420
+tp1426
+a(dp1427
 g7
-I53
-saa(lp1421
-(g1349
+I87
+saa(lp1428
+(g1356
 g1195
 S'1'
 S'I'
-tp1422
-a(dp1423
+tp1429
+a(dp1430
 g7
-I4
-saa(lp1424
-(g1349
+I53
+saa(lp1431
+(g1356
 g1199
 S'1'
 S'I'
-tp1425
-a(dp1426
+tp1432
+a(dp1433
 g7
-I150
-saa(lp1427
-(g1349
+I4
+saa(lp1434
+(g1356
 g1203
 S'1'
 S'I'
-tp1428
-a(dp1429
+tp1435
+a(dp1436
 g7
-I23
-saa(lp1430
-(g1349
+I150
+saa(lp1437
+(g1356
 g1207
 S'1'
 S'I'
-tp1431
-a(dp1432
+tp1438
+a(dp1439
 g7
-I63
-saa(lp1433
-(g1349
+I23
+saa(lp1440
+(g1356
 g1211
 S'1'
 S'I'
-tp1434
-a(dp1435
+tp1441
+a(dp1442
 g7
-I150
-saa(lp1436
-(g1349
+I63
+saa(lp1443
+(g1356
 g1215
 S'1'
 S'I'
-tp1437
-a(dp1438
+tp1444
+a(dp1445
 g7
-I9
-saa(lp1439
-(g1349
+I150
+saa(lp1446
+(g1356
 g1219
 S'1'
 S'I'
-tp1440
-a(dp1441
-g7
-I9
-saa(lp1442
-(g1349
-g1223
-S'1'
-S'I'
-tp1443
-a(dp1444
-g7
-I35
-saa(lp1445
-(S'Missile Rack'
-p1446
-NS'1'
-S'B'
 tp1447
 a(dp1448
 g7
-I2
+I9
 saa(lp1449
-(g1446
-NS'2'
-S'B'
+(g1356
+g1223
+S'1'
+S'I'
 tp1450
 a(dp1451
 g7
-I4
+I9
 saa(lp1452
-(S'Module Reinforcement Package'
-p1453
-NS'1'
-S'D'
-tp1454
-a(dp1455
+(g1356
+g1227
+S'1'
+S'I'
+tp1453
+a(dp1454
 g7
-I1
-saa(lp1456
-(g1453
+I35
+saa(lp1455
+(S'Missile Rack'
+p1456
 NS'1'
-S'E'
+S'B'
 tp1457
 a(dp1458
 g7
 I2
 saa(lp1459
-(g1453
+(g1456
 NS'2'
-S'D'
+S'B'
 tp1460
 a(dp1461
 g7
-I2
+I4
 saa(lp1462
-(g1453
+(S'Module Reinforcement Package'
+p1463
+NS'1'
+S'D'
+tp1464
+a(dp1465
+g7
+I1
+saa(lp1466
+(g1463
+NS'1'
+S'E'
+tp1467
+a(dp1468
+g7
+I2
+saa(lp1469
+(g1463
+NS'2'
+S'D'
+tp1470
+a(dp1471
+g7
+I2
+saa(lp1472
+(g1463
 NS'2'
 S'E'
-tp1463
-a(dp1464
+tp1473
+a(dp1474
 g7
 I4
-saa(lp1465
-(g1453
+saa(lp1475
+(g1463
 NS'3'
 S'D'
-tp1466
-a(dp1467
+tp1476
+a(dp1477
 g7
 I4
-saa(lp1468
-(g1453
+saa(lp1478
+(g1463
 NS'3'
 S'E'
-tp1469
-a(dp1470
+tp1479
+a(dp1480
 g7
 I8
-saa(lp1471
-(g1453
+saa(lp1481
+(g1463
 NS'4'
 S'D'
-tp1472
-a(dp1473
+tp1482
+a(dp1483
 g7
 I8
-saa(lp1474
-(g1453
+saa(lp1484
+(g1463
 NS'4'
 S'E'
-tp1475
-a(dp1476
-g7
-I16
-saa(lp1477
-(g1453
-NS'5'
-S'D'
-tp1478
-a(dp1479
-g7
-I16
-saa(lp1480
-(g1453
-NS'5'
-S'E'
-tp1481
-a(dp1482
-g7
-I32
-saa(lp1483
-(S'Multi-Cannon'
-p1484
-NS'1'
-S'F'
 tp1485
 a(dp1486
 g7
-I2
+I16
 saa(lp1487
-(g1484
-NS'1'
-S'G'
+(g1463
+NS'5'
+S'D'
 tp1488
 a(dp1489
 g7
-I2
+I16
 saa(lp1490
-(g1484
-NS'2'
+(g1463
+NS'5'
 S'E'
 tp1491
 a(dp1492
 g7
-I4
+I32
 saa(lp1493
-(g1484
+(S'Multi-Cannon'
+p1494
+NS'1'
+S'F'
+tp1495
+a(dp1496
+g7
+I2
+saa(lp1497
+(g1494
+NS'1'
+S'G'
+tp1498
+a(dp1499
+g7
+I2
+saa(lp1500
+(g1494
+NS'2'
+S'E'
+tp1501
+a(dp1502
+g7
+I4
+saa(lp1503
+(g1494
 NS'2'
 S'F'
-tp1494
-a(dp1495
-g7
-I4
-saa(lp1496
-(g1484
-NS'3'
-S'C'
-tp1497
-a(dp1498
-g7
-I8
-saa(lp1499
-(g1484
-NS'4'
-S'A'
-tp1500
-a(dp1501
-g7
-I16
-saa(lp1502
-(S'Pacifier Frag-Cannon'
-p1503
-NS'3'
-S'C'
 tp1504
 a(dp1505
 g7
-I8
+I4
 saa(lp1506
+(g1494
+NS'3'
+S'C'
+tp1507
+a(dp1508
+g7
+I8
+saa(lp1509
+(g1494
+NS'4'
+S'A'
+tp1510
+a(dp1511
+g7
+I16
+saa(lp1512
+(S'Pacifier Frag-Cannon'
+p1513
+NS'3'
+S'C'
+tp1514
+a(dp1515
+g7
+I8
+saa(lp1516
 (S'Pack-Hound Missile Rack'
-p1507
+p1517
 NS'2'
 S'B'
-tp1508
-a(dp1509
+tp1518
+a(dp1519
 g7
 I4
-saa(lp1510
+saa(lp1520
 (S'Planetary Approach Suite'
-p1511
+p1521
 NS'1'
 S'I'
-tp1512
-a(dp1513
-g7
-I0
-saa(lp1514
-(S'Planetary Vehicle Hangar'
-p1515
-NS'2'
-S'G'
-tp1516
-a(dp1517
-g7
-I6
-saa(lp1518
-(g1515
-NS'2'
-S'H'
-tp1519
-a(dp1520
-g7
-I12
-saa(lp1521
-(g1515
-NS'4'
-S'G'
 tp1522
 a(dp1523
 g7
-I10
+I0
 saa(lp1524
-(g1515
+(S'Planetary Vehicle Hangar'
+p1525
+NS'2'
+S'G'
+tp1526
+a(dp1527
+g7
+I6
+saa(lp1528
+(g1525
+NS'2'
+S'H'
+tp1529
+a(dp1530
+g7
+I12
+saa(lp1531
+(g1525
+NS'4'
+S'G'
+tp1532
+a(dp1533
+g7
+I10
+saa(lp1534
+(g1525
 NS'4'
 S'H'
-tp1525
-a(dp1526
-g7
-I20
-saa(lp1527
-(g1515
-NS'6'
-S'G'
-tp1528
-a(dp1529
-g7
-I17
-saa(lp1530
-(g1515
-NS'6'
-S'H'
-tp1531
-a(dp1532
-g7
-I34
-saa(lp1533
-(S'Plasma Accelerator'
-p1534
-NS'2'
-S'C'
 tp1535
 a(dp1536
 g7
-I4
+I20
 saa(lp1537
-(g1534
-NS'3'
-S'B'
+(g1525
+NS'6'
+S'G'
 tp1538
 a(dp1539
 g7
-I8
+I17
 saa(lp1540
-(g1534
-NS'4'
-S'A'
+(g1525
+NS'6'
+S'H'
 tp1541
 a(dp1542
 g7
-I16
+I34
 saa(lp1543
-(S'Point Defence'
+(S'Plasma Accelerator'
 p1544
-NS'0'
-S'I'
+NS'2'
+S'C'
 tp1545
 a(dp1546
 g7
-F0.5
+I4
 saa(lp1547
-(S'Power Distributor'
-p1548
-NS'1'
-S'A'
-tp1549
-a(dp1550
-g7
-F1.3
-saa(lp1551
-(g1548
-NS'1'
+(g1544
+NS'3'
 S'B'
-tp1552
-a(dp1553
+tp1548
+a(dp1549
 g7
-I2
-saa(lp1554
-(g1548
-NS'1'
-S'C'
+I8
+saa(lp1550
+(g1544
+NS'4'
+S'A'
+tp1551
+a(dp1552
+g7
+I16
+saa(lp1553
+(S'Point Defence'
+p1554
+NS'0'
+S'I'
 tp1555
 a(dp1556
 g7
-F1.3
-saa(lp1557
-(g1548
-NS'1'
-S'D'
-tp1558
-a(dp1559
-g7
 F0.5
-saa(lp1560
-(g1548
+saa(lp1557
+(S'Power Distributor'
+p1558
 NS'1'
-S'E'
-tp1561
-a(dp1562
+S'A'
+tp1559
+a(dp1560
 g7
 F1.3
-saa(lp1563
-(g1548
-NS'2'
-S'A'
-tp1564
-a(dp1565
-g7
-F2.5
-saa(lp1566
-(g1548
-NS'2'
+saa(lp1561
+(g1558
+NS'1'
 S'B'
-tp1567
-a(dp1568
-g7
-I4
-saa(lp1569
-(g1548
-NS'2'
-S'C'
-tp1570
-a(dp1571
-g7
-F2.5
-saa(lp1572
-(g1548
-NS'2'
-S'D'
-tp1573
-a(dp1574
-g7
-I1
-saa(lp1575
-(g1548
-NS'2'
-S'E'
-tp1576
-a(dp1577
-g7
-F2.5
-saa(lp1578
-(g1548
-NS'3'
-S'A'
-tp1579
-a(dp1580
-g7
-I5
-saa(lp1581
-(g1548
-NS'3'
-S'B'
-tp1582
-a(dp1583
-g7
-I8
-saa(lp1584
-(g1548
-NS'3'
-S'C'
-tp1585
-a(dp1586
-g7
-I5
-saa(lp1587
-(g1548
-NS'3'
-S'D'
-tp1588
-a(dp1589
+tp1562
+a(dp1563
 g7
 I2
-saa(lp1590
-(g1548
-NS'3'
-S'E'
-tp1591
-a(dp1592
-g7
-I5
-saa(lp1593
-(g1548
-NS'4'
-S'A'
-tp1594
-a(dp1595
-g7
-I10
-saa(lp1596
-(g1548
-NS'4'
-S'B'
-tp1597
-a(dp1598
-g7
-I16
-saa(lp1599
-(g1548
-NS'4'
+saa(lp1564
+(g1558
+NS'1'
 S'C'
-tp1600
-a(dp1601
+tp1565
+a(dp1566
 g7
-I10
-saa(lp1602
-(g1548
-NS'4'
+F1.3
+saa(lp1567
+(g1558
+NS'1'
 S'D'
-tp1603
-a(dp1604
+tp1568
+a(dp1569
 g7
-I4
-saa(lp1605
-(g1548
-NS'4'
+F0.5
+saa(lp1570
+(g1558
+NS'1'
 S'E'
-tp1606
-a(dp1607
+tp1571
+a(dp1572
 g7
-I10
-saa(lp1608
-(g1548
-NS'5'
-S'A'
-tp1609
-a(dp1610
-g7
-I20
-saa(lp1611
-(g1548
-NS'5'
-S'B'
-tp1612
-a(dp1613
-g7
-I32
-saa(lp1614
-(g1548
-NS'5'
-S'C'
-tp1615
-a(dp1616
-g7
-I20
-saa(lp1617
-(g1548
-NS'5'
-S'D'
-tp1618
-a(dp1619
-g7
-I8
-saa(lp1620
-(g1548
-NS'5'
-S'E'
-tp1621
-a(dp1622
-g7
-I20
-saa(lp1623
-(g1548
-NS'6'
-S'A'
-tp1624
-a(dp1625
-g7
-I40
-saa(lp1626
-(g1548
-NS'6'
-S'B'
-tp1627
-a(dp1628
-g7
-I64
-saa(lp1629
-(g1548
-NS'6'
-S'C'
-tp1630
-a(dp1631
-g7
-I40
-saa(lp1632
-(g1548
-NS'6'
-S'D'
-tp1633
-a(dp1634
-g7
-I16
-saa(lp1635
-(g1548
-NS'6'
-S'E'
-tp1636
-a(dp1637
-g7
-I40
-saa(lp1638
-(g1548
-NS'7'
-S'A'
-tp1639
-a(dp1640
-g7
-I80
-saa(lp1641
-(g1548
-NS'7'
-S'B'
-tp1642
-a(dp1643
-g7
-I128
-saa(lp1644
-(g1548
-NS'7'
-S'C'
-tp1645
-a(dp1646
-g7
-I80
-saa(lp1647
-(g1548
-NS'7'
-S'D'
-tp1648
-a(dp1649
-g7
-I32
-saa(lp1650
-(g1548
-NS'7'
-S'E'
-tp1651
-a(dp1652
-g7
-I80
-saa(lp1653
-(g1548
-NS'8'
-S'A'
-tp1654
-a(dp1655
-g7
-I160
-saa(lp1656
-(g1548
-NS'8'
-S'B'
-tp1657
-a(dp1658
-g7
-I256
-saa(lp1659
-(g1548
-NS'8'
-S'C'
-tp1660
-a(dp1661
-g7
-I160
-saa(lp1662
-(g1548
-NS'8'
-S'D'
-tp1663
-a(dp1664
-g7
-I64
-saa(lp1665
-(g1548
-NS'8'
-S'E'
-tp1666
-a(dp1667
-g7
-I160
-saa(lp1668
-(S'Power Plant'
-p1669
+F1.3
+saa(lp1573
+(g1558
 NS'2'
 S'A'
+tp1574
+a(dp1575
+g7
+F2.5
+saa(lp1576
+(g1558
+NS'2'
+S'B'
+tp1577
+a(dp1578
+g7
+I4
+saa(lp1579
+(g1558
+NS'2'
+S'C'
+tp1580
+a(dp1581
+g7
+F2.5
+saa(lp1582
+(g1558
+NS'2'
+S'D'
+tp1583
+a(dp1584
+g7
+I1
+saa(lp1585
+(g1558
+NS'2'
+S'E'
+tp1586
+a(dp1587
+g7
+F2.5
+saa(lp1588
+(g1558
+NS'3'
+S'A'
+tp1589
+a(dp1590
+g7
+I5
+saa(lp1591
+(g1558
+NS'3'
+S'B'
+tp1592
+a(dp1593
+g7
+I8
+saa(lp1594
+(g1558
+NS'3'
+S'C'
+tp1595
+a(dp1596
+g7
+I5
+saa(lp1597
+(g1558
+NS'3'
+S'D'
+tp1598
+a(dp1599
+g7
+I2
+saa(lp1600
+(g1558
+NS'3'
+S'E'
+tp1601
+a(dp1602
+g7
+I5
+saa(lp1603
+(g1558
+NS'4'
+S'A'
+tp1604
+a(dp1605
+g7
+I10
+saa(lp1606
+(g1558
+NS'4'
+S'B'
+tp1607
+a(dp1608
+g7
+I16
+saa(lp1609
+(g1558
+NS'4'
+S'C'
+tp1610
+a(dp1611
+g7
+I10
+saa(lp1612
+(g1558
+NS'4'
+S'D'
+tp1613
+a(dp1614
+g7
+I4
+saa(lp1615
+(g1558
+NS'4'
+S'E'
+tp1616
+a(dp1617
+g7
+I10
+saa(lp1618
+(g1558
+NS'5'
+S'A'
+tp1619
+a(dp1620
+g7
+I20
+saa(lp1621
+(g1558
+NS'5'
+S'B'
+tp1622
+a(dp1623
+g7
+I32
+saa(lp1624
+(g1558
+NS'5'
+S'C'
+tp1625
+a(dp1626
+g7
+I20
+saa(lp1627
+(g1558
+NS'5'
+S'D'
+tp1628
+a(dp1629
+g7
+I8
+saa(lp1630
+(g1558
+NS'5'
+S'E'
+tp1631
+a(dp1632
+g7
+I20
+saa(lp1633
+(g1558
+NS'6'
+S'A'
+tp1634
+a(dp1635
+g7
+I40
+saa(lp1636
+(g1558
+NS'6'
+S'B'
+tp1637
+a(dp1638
+g7
+I64
+saa(lp1639
+(g1558
+NS'6'
+S'C'
+tp1640
+a(dp1641
+g7
+I40
+saa(lp1642
+(g1558
+NS'6'
+S'D'
+tp1643
+a(dp1644
+g7
+I16
+saa(lp1645
+(g1558
+NS'6'
+S'E'
+tp1646
+a(dp1647
+g7
+I40
+saa(lp1648
+(g1558
+NS'7'
+S'A'
+tp1649
+a(dp1650
+g7
+I80
+saa(lp1651
+(g1558
+NS'7'
+S'B'
+tp1652
+a(dp1653
+g7
+I128
+saa(lp1654
+(g1558
+NS'7'
+S'C'
+tp1655
+a(dp1656
+g7
+I80
+saa(lp1657
+(g1558
+NS'7'
+S'D'
+tp1658
+a(dp1659
+g7
+I32
+saa(lp1660
+(g1558
+NS'7'
+S'E'
+tp1661
+a(dp1662
+g7
+I80
+saa(lp1663
+(g1558
+NS'8'
+S'A'
+tp1664
+a(dp1665
+g7
+I160
+saa(lp1666
+(g1558
+NS'8'
+S'B'
+tp1667
+a(dp1668
+g7
+I256
+saa(lp1669
+(g1558
+NS'8'
+S'C'
 tp1670
 a(dp1671
 g7
-F1.3
+I160
 saa(lp1672
-(g1669
-NS'2'
-S'B'
+(g1558
+NS'8'
+S'D'
 tp1673
 a(dp1674
 g7
-I2
+I64
 saa(lp1675
-(g1669
-NS'2'
-S'C'
+(g1558
+NS'8'
+S'E'
 tp1676
 a(dp1677
 g7
-F1.3
+I160
 saa(lp1678
-(g1669
+(S'Power Plant'
+p1679
 NS'2'
-S'D'
-tp1679
-a(dp1680
-g7
-I1
-saa(lp1681
-(g1669
-NS'2'
-S'E'
-tp1682
-a(dp1683
-g7
-F2.5
-saa(lp1684
-(g1669
-NS'3'
 S'A'
-tp1685
-a(dp1686
+tp1680
+a(dp1681
 g7
-F2.5
-saa(lp1687
-(g1669
-NS'3'
+F1.3
+saa(lp1682
+(g1679
+NS'2'
 S'B'
-tp1688
-a(dp1689
-g7
-I4
-saa(lp1690
-(g1669
-NS'3'
-S'C'
-tp1691
-a(dp1692
-g7
-F2.5
-saa(lp1693
-(g1669
-NS'3'
-S'D'
-tp1694
-a(dp1695
+tp1683
+a(dp1684
 g7
 I2
-saa(lp1696
-(g1669
-NS'3'
-S'E'
-tp1697
-a(dp1698
-g7
-I5
-saa(lp1699
-(g1669
-NS'4'
-S'A'
-tp1700
-a(dp1701
-g7
-I5
-saa(lp1702
-(g1669
-NS'4'
-S'B'
-tp1703
-a(dp1704
-g7
-I8
-saa(lp1705
-(g1669
-NS'4'
+saa(lp1685
+(g1679
+NS'2'
 S'C'
-tp1706
-a(dp1707
+tp1686
+a(dp1687
 g7
-I5
-saa(lp1708
-(g1669
-NS'4'
+F1.3
+saa(lp1688
+(g1679
+NS'2'
 S'D'
-tp1709
-a(dp1710
+tp1689
+a(dp1690
+g7
+I1
+saa(lp1691
+(g1679
+NS'2'
+S'E'
+tp1692
+a(dp1693
+g7
+F2.5
+saa(lp1694
+(g1679
+NS'3'
+S'A'
+tp1695
+a(dp1696
+g7
+F2.5
+saa(lp1697
+(g1679
+NS'3'
+S'B'
+tp1698
+a(dp1699
 g7
 I4
-saa(lp1711
-(g1669
-NS'4'
-S'E'
-tp1712
-a(dp1713
-g7
-I10
-saa(lp1714
-(g1669
-NS'5'
-S'A'
-tp1715
-a(dp1716
-g7
-I10
-saa(lp1717
-(g1669
-NS'5'
-S'B'
-tp1718
-a(dp1719
-g7
-I16
-saa(lp1720
-(g1669
-NS'5'
+saa(lp1700
+(g1679
+NS'3'
 S'C'
-tp1721
-a(dp1722
+tp1701
+a(dp1702
 g7
-I10
-saa(lp1723
-(g1669
-NS'5'
+F2.5
+saa(lp1703
+(g1679
+NS'3'
 S'D'
-tp1724
-a(dp1725
+tp1704
+a(dp1705
+g7
+I2
+saa(lp1706
+(g1679
+NS'3'
+S'E'
+tp1707
+a(dp1708
+g7
+I5
+saa(lp1709
+(g1679
+NS'4'
+S'A'
+tp1710
+a(dp1711
+g7
+I5
+saa(lp1712
+(g1679
+NS'4'
+S'B'
+tp1713
+a(dp1714
 g7
 I8
-saa(lp1726
-(g1669
-NS'5'
-S'E'
-tp1727
-a(dp1728
-g7
-I20
-saa(lp1729
-(g1669
-NS'6'
-S'A'
-tp1730
-a(dp1731
-g7
-I20
-saa(lp1732
-(g1669
-NS'6'
-S'B'
-tp1733
-a(dp1734
-g7
-I32
-saa(lp1735
-(g1669
-NS'6'
+saa(lp1715
+(g1679
+NS'4'
 S'C'
-tp1736
-a(dp1737
+tp1716
+a(dp1717
 g7
-I20
-saa(lp1738
-(g1669
-NS'6'
+I5
+saa(lp1718
+(g1679
+NS'4'
 S'D'
-tp1739
-a(dp1740
+tp1719
+a(dp1720
+g7
+I4
+saa(lp1721
+(g1679
+NS'4'
+S'E'
+tp1722
+a(dp1723
+g7
+I10
+saa(lp1724
+(g1679
+NS'5'
+S'A'
+tp1725
+a(dp1726
+g7
+I10
+saa(lp1727
+(g1679
+NS'5'
+S'B'
+tp1728
+a(dp1729
 g7
 I16
-saa(lp1741
-(g1669
-NS'6'
-S'E'
-tp1742
-a(dp1743
-g7
-I40
-saa(lp1744
-(g1669
-NS'7'
-S'A'
-tp1745
-a(dp1746
-g7
-I40
-saa(lp1747
-(g1669
-NS'7'
-S'B'
-tp1748
-a(dp1749
-g7
-I64
-saa(lp1750
-(g1669
-NS'7'
+saa(lp1730
+(g1679
+NS'5'
 S'C'
-tp1751
-a(dp1752
+tp1731
+a(dp1732
 g7
-I40
-saa(lp1753
-(g1669
-NS'7'
+I10
+saa(lp1733
+(g1679
+NS'5'
 S'D'
-tp1754
-a(dp1755
+tp1734
+a(dp1735
+g7
+I8
+saa(lp1736
+(g1679
+NS'5'
+S'E'
+tp1737
+a(dp1738
+g7
+I20
+saa(lp1739
+(g1679
+NS'6'
+S'A'
+tp1740
+a(dp1741
+g7
+I20
+saa(lp1742
+(g1679
+NS'6'
+S'B'
+tp1743
+a(dp1744
 g7
 I32
-saa(lp1756
-(g1669
-NS'7'
-S'E'
-tp1757
-a(dp1758
-g7
-I80
-saa(lp1759
-(g1669
-NS'8'
-S'A'
-tp1760
-a(dp1761
-g7
-I80
-saa(lp1762
-(g1669
-NS'8'
-S'B'
-tp1763
-a(dp1764
-g7
-I128
-saa(lp1765
-(g1669
-NS'8'
+saa(lp1745
+(g1679
+NS'6'
 S'C'
-tp1766
-a(dp1767
+tp1746
+a(dp1747
 g7
-I80
-saa(lp1768
-(g1669
-NS'8'
+I20
+saa(lp1748
+(g1679
+NS'6'
 S'D'
-tp1769
-a(dp1770
+tp1749
+a(dp1750
+g7
+I16
+saa(lp1751
+(g1679
+NS'6'
+S'E'
+tp1752
+a(dp1753
+g7
+I40
+saa(lp1754
+(g1679
+NS'7'
+S'A'
+tp1755
+a(dp1756
+g7
+I40
+saa(lp1757
+(g1679
+NS'7'
+S'B'
+tp1758
+a(dp1759
 g7
 I64
-saa(lp1771
-(g1669
-NS'8'
-S'E'
-tp1772
-a(dp1773
+saa(lp1760
+(g1679
+NS'7'
+S'C'
+tp1761
+a(dp1762
 g7
-I160
-saa(lp1774
-(S'Prismatic Shield Generator'
-p1775
-NS'1'
+I40
+saa(lp1763
+(g1679
+NS'7'
+S'D'
+tp1764
+a(dp1765
+g7
+I32
+saa(lp1766
+(g1679
+NS'7'
+S'E'
+tp1767
+a(dp1768
+g7
+I80
+saa(lp1769
+(g1679
+NS'8'
 S'A'
+tp1770
+a(dp1771
+g7
+I80
+saa(lp1772
+(g1679
+NS'8'
+S'B'
+tp1773
+a(dp1774
+g7
+I128
+saa(lp1775
+(g1679
+NS'8'
+S'C'
 tp1776
 a(dp1777
 g7
-F2.5
+I80
 saa(lp1778
-(g1775
-NS'2'
-S'A'
+(g1679
+NS'8'
+S'D'
 tp1779
 a(dp1780
 g7
-I5
+I64
 saa(lp1781
-(g1775
-NS'3'
-S'A'
+(g1679
+NS'8'
+S'E'
 tp1782
 a(dp1783
 g7
-I10
+I160
 saa(lp1784
-(g1775
+(S'Prismatic Shield Generator'
+p1785
+NS'1'
+S'A'
+tp1786
+a(dp1787
+g7
+F2.5
+saa(lp1788
+(g1785
+NS'2'
+S'A'
+tp1789
+a(dp1790
+g7
+I5
+saa(lp1791
+(g1785
+NS'3'
+S'A'
+tp1792
+a(dp1793
+g7
+I10
+saa(lp1794
+(g1785
 NS'4'
 S'A'
-tp1785
-a(dp1786
+tp1795
+a(dp1796
 g7
 I20
-saa(lp1787
-(g1775
+saa(lp1797
+(g1785
 NS'5'
 S'A'
-tp1788
-a(dp1789
+tp1798
+a(dp1799
 g7
 I40
-saa(lp1790
-(g1775
+saa(lp1800
+(g1785
 NS'6'
 S'A'
-tp1791
-a(dp1792
-g7
-I80
-saa(lp1793
-(g1775
-NS'7'
-S'A'
-tp1794
-a(dp1795
-g7
-I160
-saa(lp1796
-(g1775
-NS'8'
-S'A'
-tp1797
-a(dp1798
-g7
-I320
-saa(lp1799
-(S'Prospector Limpet Controller'
-p1800
-NS'1'
-S'A'
 tp1801
 a(dp1802
 g7
-F1.3
+I80
 saa(lp1803
-(g1800
-NS'1'
-S'B'
+(g1785
+NS'7'
+S'A'
 tp1804
 a(dp1805
 g7
-I2
+I160
 saa(lp1806
-(g1800
-NS'1'
-S'C'
+(g1785
+NS'8'
+S'A'
 tp1807
 a(dp1808
 g7
-F1.3
+I320
 saa(lp1809
-(g1800
+(S'Prospector Limpet Controller'
+p1810
 NS'1'
-S'D'
-tp1810
-a(dp1811
-g7
-F0.5
-saa(lp1812
-(g1800
-NS'1'
-S'E'
-tp1813
-a(dp1814
+S'A'
+tp1811
+a(dp1812
 g7
 F1.3
-saa(lp1815
-(g1800
-NS'3'
-S'A'
-tp1816
-a(dp1817
-g7
-I5
-saa(lp1818
-(g1800
-NS'3'
+saa(lp1813
+(g1810
+NS'1'
 S'B'
-tp1819
-a(dp1820
-g7
-I8
-saa(lp1821
-(g1800
-NS'3'
-S'C'
-tp1822
-a(dp1823
-g7
-I5
-saa(lp1824
-(g1800
-NS'3'
-S'D'
-tp1825
-a(dp1826
+tp1814
+a(dp1815
 g7
 I2
-saa(lp1827
-(g1800
-NS'3'
+saa(lp1816
+(g1810
+NS'1'
+S'C'
+tp1817
+a(dp1818
+g7
+F1.3
+saa(lp1819
+(g1810
+NS'1'
+S'D'
+tp1820
+a(dp1821
+g7
+F0.5
+saa(lp1822
+(g1810
+NS'1'
 S'E'
-tp1828
-a(dp1829
+tp1823
+a(dp1824
+g7
+F1.3
+saa(lp1825
+(g1810
+NS'3'
+S'A'
+tp1826
+a(dp1827
 g7
 I5
-saa(lp1830
-(g1800
-NS'5'
-S'A'
-tp1831
-a(dp1832
-g7
-I20
-saa(lp1833
-(g1800
-NS'5'
+saa(lp1828
+(g1810
+NS'3'
 S'B'
-tp1834
-a(dp1835
-g7
-I32
-saa(lp1836
-(g1800
-NS'5'
-S'C'
-tp1837
-a(dp1838
-g7
-I20
-saa(lp1839
-(g1800
-NS'5'
-S'D'
-tp1840
-a(dp1841
+tp1829
+a(dp1830
 g7
 I8
-saa(lp1842
-(g1800
-NS'5'
+saa(lp1831
+(g1810
+NS'3'
+S'C'
+tp1832
+a(dp1833
+g7
+I5
+saa(lp1834
+(g1810
+NS'3'
+S'D'
+tp1835
+a(dp1836
+g7
+I2
+saa(lp1837
+(g1810
+NS'3'
 S'E'
-tp1843
-a(dp1844
+tp1838
+a(dp1839
+g7
+I5
+saa(lp1840
+(g1810
+NS'5'
+S'A'
+tp1841
+a(dp1842
 g7
 I20
-saa(lp1845
-(g1800
-NS'7'
-S'A'
-tp1846
-a(dp1847
-g7
-I80
-saa(lp1848
-(g1800
-NS'7'
+saa(lp1843
+(g1810
+NS'5'
 S'B'
-tp1849
-a(dp1850
-g7
-I128
-saa(lp1851
-(g1800
-NS'7'
-S'C'
-tp1852
-a(dp1853
-g7
-I80
-saa(lp1854
-(g1800
-NS'7'
-S'D'
-tp1855
-a(dp1856
+tp1844
+a(dp1845
 g7
 I32
-saa(lp1857
-(g1800
-NS'7'
+saa(lp1846
+(g1810
+NS'5'
+S'C'
+tp1847
+a(dp1848
+g7
+I20
+saa(lp1849
+(g1810
+NS'5'
+S'D'
+tp1850
+a(dp1851
+g7
+I8
+saa(lp1852
+(g1810
+NS'5'
 S'E'
-tp1858
-a(dp1859
+tp1853
+a(dp1854
+g7
+I20
+saa(lp1855
+(g1810
+NS'7'
+S'A'
+tp1856
+a(dp1857
 g7
 I80
-saa(lp1860
-(S'Pulse Disruptor Laser'
-p1861
-NS'2'
-S'E'
+saa(lp1858
+(g1810
+NS'7'
+S'B'
+tp1859
+a(dp1860
+g7
+I128
+saa(lp1861
+(g1810
+NS'7'
+S'C'
 tp1862
 a(dp1863
 g7
-I4
+I80
 saa(lp1864
-(S'Pulse Laser'
-p1865
-NS'1'
-S'F'
-tp1866
-a(dp1867
+(g1810
+NS'7'
+S'D'
+tp1865
+a(dp1866
 g7
-I2
-saa(lp1868
-(g1865
-NS'1'
-S'G'
-tp1869
-a(dp1870
+I32
+saa(lp1867
+(g1810
+NS'7'
+S'E'
+tp1868
+a(dp1869
 g7
-I2
-saa(lp1871
-(g1865
+I80
+saa(lp1870
+(S'Pulse Disruptor Laser'
+p1871
 NS'2'
 S'E'
 tp1872
@@ -5205,2294 +5208,2379 @@ a(dp1873
 g7
 I4
 saa(lp1874
-(g1865
-NS'2'
+(S'Pulse Laser'
+p1875
+NS'1'
 S'F'
-tp1875
-a(dp1876
+tp1876
+a(dp1877
+g7
+I2
+saa(lp1878
+(g1875
+NS'1'
+S'G'
+tp1879
+a(dp1880
+g7
+I2
+saa(lp1881
+(g1875
+NS'2'
+S'E'
+tp1882
+a(dp1883
 g7
 I4
-saa(lp1877
-(g1865
+saa(lp1884
+(g1875
+NS'2'
+S'F'
+tp1885
+a(dp1886
+g7
+I4
+saa(lp1887
+(g1875
 NS'3'
 S'D'
-tp1878
-a(dp1879
+tp1888
+a(dp1889
 g7
 I8
-saa(lp1880
-(g1865
+saa(lp1890
+(g1875
 NS'3'
 S'E'
-tp1881
-a(dp1882
-g7
-I8
-saa(lp1883
-(g1865
-NS'3'
-S'F'
-tp1884
-a(dp1885
-g7
-I8
-saa(lp1886
-(g1865
-NS'4'
-S'A'
-tp1887
-a(dp1888
-g7
-I16
-saa(lp1889
-(S'Rail Gun'
-p1890
-NS'1'
-S'D'
 tp1891
 a(dp1892
 g7
-I2
+I8
 saa(lp1893
-(g1890
-NS'2'
-S'B'
+(g1875
+NS'3'
+S'F'
 tp1894
 a(dp1895
 g7
-I4
+I8
 saa(lp1896
-(S'Reactive Surface Composite'
-p1897
-g1099
-S'1'
-S'I'
-tp1898
-a(dp1899
+(g1875
+NS'4'
+S'A'
+tp1897
+a(dp1898
 g7
-I5
-saa(lp1900
-(g1897
-g1103
-S'1'
-S'I'
+I16
+saa(lp1899
+(S'Rail Gun'
+p1900
+NS'1'
+S'D'
 tp1901
 a(dp1902
 g7
-I60
+I2
 saa(lp1903
-(g1897
-g1107
-S'1'
-S'I'
+(g1900
+NS'2'
+S'B'
 tp1904
 a(dp1905
 g7
-I42
-saa(lp1906
-(g1897
-g1111
-S'1'
-S'I'
-tp1907
-a(dp1908
-g7
-I42
-saa(lp1909
-(g1897
-g1115
-S'1'
-S'I'
-tp1910
-a(dp1911
-g7
-I165
-saa(lp1912
-(g1897
-g1119
-S'1'
-S'I'
-tp1913
-a(dp1914
-g7
-I27
-saa(lp1915
-(g1897
-g1123
-S'1'
-S'I'
-tp1916
-a(dp1917
-g7
-I27
-saa(lp1918
-(g1897
-g1127
-S'1'
-S'I'
-tp1919
-a(dp1920
-g7
-I47
-saa(lp1921
-(g1897
-g1131
-S'1'
-S'I'
-tp1922
-a(dp1923
-g7
-I26
-saa(lp1924
-(g1897
-g1135
-S'1'
-S'I'
-tp1925
-a(dp1926
-g7
-I63
-saa(lp1927
-(g1897
-g1139
-S'1'
-S'I'
-tp1928
-a(dp1929
-g7
-I8
-saa(lp1930
-(g1897
-g1143
-S'1'
-S'I'
-tp1931
-a(dp1932
-g7
-I87
-saa(lp1933
-(g1897
-g1147
-S'1'
-S'I'
-tp1934
-a(dp1935
-g7
-I60
-saa(lp1936
-(g1897
-g1151
-S'1'
-S'I'
-tp1937
-a(dp1938
-g7
-I87
-saa(lp1939
-(g1897
-g1155
-S'1'
-S'I'
-tp1940
-a(dp1941
-g7
-I87
-saa(lp1942
-(g1897
-g1159
-S'1'
-S'I'
-tp1943
-a(dp1944
-g7
-I38
-saa(lp1945
-(g1897
-g1163
-S'1'
-S'I'
-tp1946
-a(dp1947
-g7
-I2
-saa(lp1948
-(g1897
-g1167
-S'1'
-S'I'
-tp1949
-a(dp1950
-g7
-I60
-saa(lp1951
-(g1897
-g1171
-S'1'
-S'I'
-tp1952
-a(dp1953
-g7
-I8
-saa(lp1954
-(g1897
-g1175
-S'1'
-S'I'
-tp1955
-a(dp1956
-g7
-I60
-saa(lp1957
-(g1897
-g1179
-S'1'
-S'I'
-tp1958
-a(dp1959
-g7
-I8
-saa(lp1960
-(g1897
-g1183
-S'1'
-S'I'
-tp1961
-a(dp1962
-g7
-I23
-saa(lp1963
-(g1897
-g1187
-S'1'
-S'I'
-tp1964
-a(dp1965
-g7
-I87
-saa(lp1966
-(g1897
-g1191
-S'1'
-S'I'
-tp1967
-a(dp1968
-g7
-I53
-saa(lp1969
-(g1897
-g1195
-S'1'
-S'I'
-tp1970
-a(dp1971
-g7
 I4
-saa(lp1972
-(g1897
-g1199
-S'1'
-S'I'
-tp1973
-a(dp1974
-g7
-I150
-saa(lp1975
-(g1897
-g1203
-S'1'
-S'I'
-tp1976
-a(dp1977
-g7
-I23
-saa(lp1978
-(g1897
-g1207
-S'1'
-S'I'
-tp1979
-a(dp1980
-g7
-I63
-saa(lp1981
-(g1897
-g1211
-S'1'
-S'I'
-tp1982
-a(dp1983
-g7
-I150
-saa(lp1984
-(g1897
-g1215
-S'1'
-S'I'
-tp1985
-a(dp1986
-g7
-I9
-saa(lp1987
-(g1897
-g1219
-S'1'
-S'I'
-tp1988
-a(dp1989
-g7
-I9
-saa(lp1990
-(g1897
-g1223
-S'1'
-S'I'
-tp1991
-a(dp1992
-g7
-I35
-saa(lp1993
-(S'Refinery'
-p1994
-NS'1'
-S'A'
-tp1995
-a(dp1996
-g7
-I0
-saa(lp1997
-(g1994
-NS'1'
-S'B'
-tp1998
-a(dp1999
-g7
-I0
-saa(lp2000
-(g1994
-NS'1'
-S'C'
-tp2001
-a(dp2002
-g7
-I0
-saa(lp2003
-(g1994
-NS'1'
-S'D'
-tp2004
-a(dp2005
-g7
-I0
-saa(lp2006
-(g1994
-NS'1'
-S'E'
-tp2007
-a(dp2008
-g7
-I0
-saa(lp2009
-(g1994
-NS'2'
-S'A'
-tp2010
-a(dp2011
-g7
-I0
-saa(lp2012
-(g1994
-NS'2'
-S'B'
-tp2013
-a(dp2014
-g7
-I0
-saa(lp2015
-(g1994
-NS'2'
-S'C'
-tp2016
-a(dp2017
-g7
-I0
-saa(lp2018
-(g1994
-NS'2'
-S'D'
-tp2019
-a(dp2020
-g7
-I0
-saa(lp2021
-(g1994
-NS'2'
-S'E'
-tp2022
-a(dp2023
-g7
-I0
-saa(lp2024
-(g1994
-NS'3'
-S'A'
-tp2025
-a(dp2026
-g7
-I0
-saa(lp2027
-(g1994
-NS'3'
-S'B'
-tp2028
-a(dp2029
-g7
-I0
-saa(lp2030
-(g1994
-NS'3'
-S'C'
-tp2031
-a(dp2032
-g7
-I0
-saa(lp2033
-(g1994
-NS'3'
-S'D'
-tp2034
-a(dp2035
-g7
-I0
-saa(lp2036
-(g1994
-NS'3'
-S'E'
-tp2037
-a(dp2038
-g7
-I0
-saa(lp2039
-(g1994
-NS'4'
-S'A'
-tp2040
-a(dp2041
-g7
-I0
-saa(lp2042
-(g1994
-NS'4'
-S'B'
-tp2043
-a(dp2044
-g7
-I0
-saa(lp2045
-(g1994
-NS'4'
-S'C'
-tp2046
-a(dp2047
-g7
-I0
-saa(lp2048
-(g1994
-NS'4'
-S'D'
-tp2049
-a(dp2050
-g7
-I0
-saa(lp2051
-(g1994
-NS'4'
-S'E'
-tp2052
-a(dp2053
-g7
-I0
-saa(lp2054
-(S'Reinforced Alloy'
-p2055
+saa(lp1906
+(S'Reactive Surface Composite'
+p1907
 g1099
 S'1'
 S'I'
-tp2056
-a(dp2057
+tp1908
+a(dp1909
 g7
-I3
-saa(lp2058
-(g2055
+I5
+saa(lp1910
+(g1907
 g1103
 S'1'
 S'I'
-tp2059
-a(dp2060
+tp1911
+a(dp1912
 g7
-I30
-saa(lp2061
-(g2055
+I150
+saa(lp1913
+(g1907
 g1107
 S'1'
 S'I'
-tp2062
-a(dp2063
+tp1914
+a(dp1915
 g7
-I21
-saa(lp2064
-(g2055
+I60
+saa(lp1916
+(g1907
 g1111
 S'1'
 S'I'
-tp2065
-a(dp2066
+tp1917
+a(dp1918
 g7
-I21
-saa(lp2067
-(g2055
+I42
+saa(lp1919
+(g1907
 g1115
 S'1'
 S'I'
-tp2068
-a(dp2069
+tp1920
+a(dp1921
 g7
-I83
-saa(lp2070
-(g2055
+I42
+saa(lp1922
+(g1907
 g1119
 S'1'
 S'I'
-tp2071
-a(dp2072
+tp1923
+a(dp1924
 g7
-I14
-saa(lp2073
-(g2055
+I165
+saa(lp1925
+(g1907
 g1123
 S'1'
 S'I'
-tp2074
-a(dp2075
+tp1926
+a(dp1927
 g7
-I14
-saa(lp2076
-(g2055
+I27
+saa(lp1928
+(g1907
 g1127
 S'1'
 S'I'
-tp2077
-a(dp2078
+tp1929
+a(dp1930
 g7
-I23
-saa(lp2079
-(g2055
+I27
+saa(lp1931
+(g1907
 g1131
 S'1'
 S'I'
-tp2080
-a(dp2081
+tp1932
+a(dp1933
 g7
-I13
-saa(lp2082
-(g2055
+I47
+saa(lp1934
+(g1907
 g1135
 S'1'
 S'I'
-tp2083
-a(dp2084
+tp1935
+a(dp1936
 g7
-I32
-saa(lp2085
-(g2055
+I26
+saa(lp1937
+(g1907
 g1139
 S'1'
 S'I'
-tp2086
-a(dp2087
+tp1938
+a(dp1939
 g7
-I4
-saa(lp2088
-(g2055
+I63
+saa(lp1940
+(g1907
 g1143
 S'1'
 S'I'
-tp2089
-a(dp2090
+tp1941
+a(dp1942
 g7
-I44
-saa(lp2091
-(g2055
+I8
+saa(lp1943
+(g1907
 g1147
 S'1'
 S'I'
-tp2092
-a(dp2093
+tp1944
+a(dp1945
 g7
-I30
-saa(lp2094
-(g2055
+I87
+saa(lp1946
+(g1907
 g1151
 S'1'
 S'I'
-tp2095
-a(dp2096
+tp1947
+a(dp1948
 g7
-I44
-saa(lp2097
-(g2055
+I60
+saa(lp1949
+(g1907
 g1155
 S'1'
 S'I'
-tp2098
-a(dp2099
+tp1950
+a(dp1951
 g7
-I44
-saa(lp2100
-(g2055
+I87
+saa(lp1952
+(g1907
 g1159
 S'1'
 S'I'
-tp2101
-a(dp2102
+tp1953
+a(dp1954
 g7
-I19
-saa(lp2103
-(g2055
+I87
+saa(lp1955
+(g1907
 g1163
 S'1'
 S'I'
-tp2104
-a(dp2105
+tp1956
+a(dp1957
 g7
-I1
-saa(lp2106
-(g2055
+I38
+saa(lp1958
+(g1907
 g1167
 S'1'
 S'I'
-tp2107
-a(dp2108
+tp1959
+a(dp1960
 g7
-I30
-saa(lp2109
-(g2055
+I2
+saa(lp1961
+(g1907
 g1171
 S'1'
 S'I'
-tp2110
-a(dp2111
+tp1962
+a(dp1963
 g7
-I4
-saa(lp2112
-(g2055
+I60
+saa(lp1964
+(g1907
 g1175
 S'1'
 S'I'
-tp2113
-a(dp2114
+tp1965
+a(dp1966
 g7
-I30
-saa(lp2115
-(g2055
+I8
+saa(lp1967
+(g1907
 g1179
 S'1'
 S'I'
-tp2116
-a(dp2117
+tp1968
+a(dp1969
 g7
-I4
-saa(lp2118
-(g2055
+I60
+saa(lp1970
+(g1907
 g1183
 S'1'
 S'I'
-tp2119
-a(dp2120
+tp1971
+a(dp1972
 g7
-I12
-saa(lp2121
-(g2055
+I8
+saa(lp1973
+(g1907
 g1187
 S'1'
 S'I'
-tp2122
-a(dp2123
+tp1974
+a(dp1975
 g7
-I21
-saa(lp2124
-(g2055
+I23
+saa(lp1976
+(g1907
 g1191
 S'1'
 S'I'
-tp2125
-a(dp2126
+tp1977
+a(dp1978
 g7
-I26
-saa(lp2127
-(g2055
+I87
+saa(lp1979
+(g1907
 g1195
 S'1'
 S'I'
-tp2128
-a(dp2129
+tp1980
+a(dp1981
 g7
-I2
-saa(lp2130
-(g2055
+I53
+saa(lp1982
+(g1907
 g1199
 S'1'
 S'I'
-tp2131
-a(dp2132
+tp1983
+a(dp1984
 g7
-I75
-saa(lp2133
-(g2055
+I4
+saa(lp1985
+(g1907
 g1203
 S'1'
 S'I'
-tp2134
-a(dp2135
+tp1986
+a(dp1987
 g7
-I12
-saa(lp2136
-(g2055
+I150
+saa(lp1988
+(g1907
 g1207
 S'1'
 S'I'
-tp2137
-a(dp2138
+tp1989
+a(dp1990
 g7
-I32
-saa(lp2139
-(g2055
+I23
+saa(lp1991
+(g1907
 g1211
 S'1'
 S'I'
-tp2140
-a(dp2141
+tp1992
+a(dp1993
 g7
-I75
-saa(lp2142
-(g2055
+I63
+saa(lp1994
+(g1907
 g1215
 S'1'
 S'I'
-tp2143
-a(dp2144
+tp1995
+a(dp1996
 g7
-I5
-saa(lp2145
-(g2055
+I150
+saa(lp1997
+(g1907
 g1219
 S'1'
 S'I'
-tp2146
-a(dp2147
+tp1998
+a(dp1999
 g7
-I5
-saa(lp2148
-(g2055
+I9
+saa(lp2000
+(g1907
 g1223
 S'1'
 S'I'
-tp2149
-a(dp2150
+tp2001
+a(dp2002
 g7
-I17
-saa(lp2151
-(S'Remote Release Flak Launcher'
-p2152
-NS'2'
-S'B'
-tp2153
-a(dp2154
+I9
+saa(lp2003
+(g1907
+g1227
+S'1'
+S'I'
+tp2004
+a(dp2005
 g7
-I4
-saa(lp2155
-(S'Repair Limpet Controller'
-p2156
+I35
+saa(lp2006
+(S'Recon Limpet Controller'
+p2007
+NS'1'
+S'E'
+tp2008
+a(dp2009
+g7
+F1.3
+saa(lp2010
+(g2007
+NS'3'
+S'E'
+tp2011
+a(dp2012
+g7
+I2
+saa(lp2013
+(g2007
+NS'5'
+S'E'
+tp2014
+a(dp2015
+g7
+I20
+saa(lp2016
+(g2007
+NS'7'
+S'E'
+tp2017
+a(dp2018
+g7
+I128
+saa(lp2019
+(S'Refinery'
+p2020
 NS'1'
 S'A'
+tp2021
+a(dp2022
+g7
+I0
+saa(lp2023
+(g2020
+NS'1'
+S'B'
+tp2024
+a(dp2025
+g7
+I0
+saa(lp2026
+(g2020
+NS'1'
+S'C'
+tp2027
+a(dp2028
+g7
+I0
+saa(lp2029
+(g2020
+NS'1'
+S'D'
+tp2030
+a(dp2031
+g7
+I0
+saa(lp2032
+(g2020
+NS'1'
+S'E'
+tp2033
+a(dp2034
+g7
+I0
+saa(lp2035
+(g2020
+NS'2'
+S'A'
+tp2036
+a(dp2037
+g7
+I0
+saa(lp2038
+(g2020
+NS'2'
+S'B'
+tp2039
+a(dp2040
+g7
+I0
+saa(lp2041
+(g2020
+NS'2'
+S'C'
+tp2042
+a(dp2043
+g7
+I0
+saa(lp2044
+(g2020
+NS'2'
+S'D'
+tp2045
+a(dp2046
+g7
+I0
+saa(lp2047
+(g2020
+NS'2'
+S'E'
+tp2048
+a(dp2049
+g7
+I0
+saa(lp2050
+(g2020
+NS'3'
+S'A'
+tp2051
+a(dp2052
+g7
+I0
+saa(lp2053
+(g2020
+NS'3'
+S'B'
+tp2054
+a(dp2055
+g7
+I0
+saa(lp2056
+(g2020
+NS'3'
+S'C'
+tp2057
+a(dp2058
+g7
+I0
+saa(lp2059
+(g2020
+NS'3'
+S'D'
+tp2060
+a(dp2061
+g7
+I0
+saa(lp2062
+(g2020
+NS'3'
+S'E'
+tp2063
+a(dp2064
+g7
+I0
+saa(lp2065
+(g2020
+NS'4'
+S'A'
+tp2066
+a(dp2067
+g7
+I0
+saa(lp2068
+(g2020
+NS'4'
+S'B'
+tp2069
+a(dp2070
+g7
+I0
+saa(lp2071
+(g2020
+NS'4'
+S'C'
+tp2072
+a(dp2073
+g7
+I0
+saa(lp2074
+(g2020
+NS'4'
+S'D'
+tp2075
+a(dp2076
+g7
+I0
+saa(lp2077
+(g2020
+NS'4'
+S'E'
+tp2078
+a(dp2079
+g7
+I0
+saa(lp2080
+(S'Reinforced Alloy'
+p2081
+g1099
+S'1'
+S'I'
+tp2082
+a(dp2083
+g7
+I3
+saa(lp2084
+(g2081
+g1103
+S'1'
+S'I'
+tp2085
+a(dp2086
+g7
+I75
+saa(lp2087
+(g2081
+g1107
+S'1'
+S'I'
+tp2088
+a(dp2089
+g7
+I30
+saa(lp2090
+(g2081
+g1111
+S'1'
+S'I'
+tp2091
+a(dp2092
+g7
+I21
+saa(lp2093
+(g2081
+g1115
+S'1'
+S'I'
+tp2094
+a(dp2095
+g7
+I21
+saa(lp2096
+(g2081
+g1119
+S'1'
+S'I'
+tp2097
+a(dp2098
+g7
+I83
+saa(lp2099
+(g2081
+g1123
+S'1'
+S'I'
+tp2100
+a(dp2101
+g7
+I14
+saa(lp2102
+(g2081
+g1127
+S'1'
+S'I'
+tp2103
+a(dp2104
+g7
+I14
+saa(lp2105
+(g2081
+g1131
+S'1'
+S'I'
+tp2106
+a(dp2107
+g7
+I23
+saa(lp2108
+(g2081
+g1135
+S'1'
+S'I'
+tp2109
+a(dp2110
+g7
+I13
+saa(lp2111
+(g2081
+g1139
+S'1'
+S'I'
+tp2112
+a(dp2113
+g7
+I32
+saa(lp2114
+(g2081
+g1143
+S'1'
+S'I'
+tp2115
+a(dp2116
+g7
+I4
+saa(lp2117
+(g2081
+g1147
+S'1'
+S'I'
+tp2118
+a(dp2119
+g7
+I44
+saa(lp2120
+(g2081
+g1151
+S'1'
+S'I'
+tp2121
+a(dp2122
+g7
+I30
+saa(lp2123
+(g2081
+g1155
+S'1'
+S'I'
+tp2124
+a(dp2125
+g7
+I44
+saa(lp2126
+(g2081
+g1159
+S'1'
+S'I'
+tp2127
+a(dp2128
+g7
+I44
+saa(lp2129
+(g2081
+g1163
+S'1'
+S'I'
+tp2130
+a(dp2131
+g7
+I19
+saa(lp2132
+(g2081
+g1167
+S'1'
+S'I'
+tp2133
+a(dp2134
+g7
+I1
+saa(lp2135
+(g2081
+g1171
+S'1'
+S'I'
+tp2136
+a(dp2137
+g7
+I30
+saa(lp2138
+(g2081
+g1175
+S'1'
+S'I'
+tp2139
+a(dp2140
+g7
+I4
+saa(lp2141
+(g2081
+g1179
+S'1'
+S'I'
+tp2142
+a(dp2143
+g7
+I30
+saa(lp2144
+(g2081
+g1183
+S'1'
+S'I'
+tp2145
+a(dp2146
+g7
+I4
+saa(lp2147
+(g2081
+g1187
+S'1'
+S'I'
+tp2148
+a(dp2149
+g7
+I12
+saa(lp2150
+(g2081
+g1191
+S'1'
+S'I'
+tp2151
+a(dp2152
+g7
+I21
+saa(lp2153
+(g2081
+g1195
+S'1'
+S'I'
+tp2154
+a(dp2155
+g7
+I26
+saa(lp2156
+(g2081
+g1199
+S'1'
+S'I'
 tp2157
 a(dp2158
 g7
-F1.3
+I2
 saa(lp2159
-(g2156
-NS'1'
-S'B'
+(g2081
+g1203
+S'1'
+S'I'
 tp2160
 a(dp2161
 g7
-I2
+I75
 saa(lp2162
-(g2156
-NS'1'
-S'C'
+(g2081
+g1207
+S'1'
+S'I'
 tp2163
 a(dp2164
 g7
-F1.3
+I12
 saa(lp2165
-(g2156
-NS'1'
-S'D'
+(g2081
+g1211
+S'1'
+S'I'
 tp2166
 a(dp2167
 g7
-F0.5
+I32
 saa(lp2168
-(g2156
-NS'1'
-S'E'
+(g2081
+g1215
+S'1'
+S'I'
 tp2169
 a(dp2170
 g7
-F1.3
+I75
 saa(lp2171
-(g2156
-NS'3'
-S'A'
+(g2081
+g1219
+S'1'
+S'I'
 tp2172
 a(dp2173
 g7
 I5
 saa(lp2174
-(g2156
-NS'3'
-S'B'
+(g2081
+g1223
+S'1'
+S'I'
 tp2175
 a(dp2176
 g7
-I8
+I5
 saa(lp2177
-(g2156
-NS'3'
-S'C'
+(g2081
+g1227
+S'1'
+S'I'
 tp2178
 a(dp2179
 g7
-I5
+I17
 saa(lp2180
-(g2156
-NS'3'
-S'D'
-tp2181
-a(dp2182
-g7
-I2
-saa(lp2183
-(g2156
-NS'3'
-S'E'
-tp2184
-a(dp2185
-g7
-I5
-saa(lp2186
-(g2156
-NS'5'
-S'A'
-tp2187
-a(dp2188
-g7
-I20
-saa(lp2189
-(g2156
-NS'5'
-S'B'
-tp2190
-a(dp2191
-g7
-I32
-saa(lp2192
-(g2156
-NS'5'
-S'C'
-tp2193
-a(dp2194
-g7
-I20
-saa(lp2195
-(g2156
-NS'5'
-S'D'
-tp2196
-a(dp2197
-g7
-I8
-saa(lp2198
-(g2156
-NS'5'
-S'E'
-tp2199
-a(dp2200
-g7
-I20
-saa(lp2201
-(g2156
-NS'7'
-S'A'
-tp2202
-a(dp2203
-g7
-I80
-saa(lp2204
-(g2156
-NS'7'
-S'B'
-tp2205
-a(dp2206
-g7
-I128
-saa(lp2207
-(g2156
-NS'7'
-S'C'
-tp2208
-a(dp2209
-g7
-I80
-saa(lp2210
-(g2156
-NS'7'
-S'D'
-tp2211
-a(dp2212
-g7
-I32
-saa(lp2213
-(g2156
-NS'7'
-S'E'
-tp2214
-a(dp2215
-g7
-I80
-saa(lp2216
-(S'Retributor Beam Laser'
-p2217
-NS'1'
-S'E'
-tp2218
-a(dp2219
-g7
-I2
-saa(lp2220
-(S'Rocket Propelled FSD Disruptor'
-p2221
+(S'Remote Release Flak Launcher'
+p2181
 NS'2'
 S'B'
+tp2182
+a(dp2183
+g7
+I4
+saa(lp2184
+(S'Repair Limpet Controller'
+p2185
+NS'1'
+S'A'
+tp2186
+a(dp2187
+g7
+F1.3
+saa(lp2188
+(g2185
+NS'1'
+S'B'
+tp2189
+a(dp2190
+g7
+I2
+saa(lp2191
+(g2185
+NS'1'
+S'C'
+tp2192
+a(dp2193
+g7
+F1.3
+saa(lp2194
+(g2185
+NS'1'
+S'D'
+tp2195
+a(dp2196
+g7
+F0.5
+saa(lp2197
+(g2185
+NS'1'
+S'E'
+tp2198
+a(dp2199
+g7
+F1.3
+saa(lp2200
+(g2185
+NS'3'
+S'A'
+tp2201
+a(dp2202
+g7
+I5
+saa(lp2203
+(g2185
+NS'3'
+S'B'
+tp2204
+a(dp2205
+g7
+I8
+saa(lp2206
+(g2185
+NS'3'
+S'C'
+tp2207
+a(dp2208
+g7
+I5
+saa(lp2209
+(g2185
+NS'3'
+S'D'
+tp2210
+a(dp2211
+g7
+I2
+saa(lp2212
+(g2185
+NS'3'
+S'E'
+tp2213
+a(dp2214
+g7
+I5
+saa(lp2215
+(g2185
+NS'5'
+S'A'
+tp2216
+a(dp2217
+g7
+I20
+saa(lp2218
+(g2185
+NS'5'
+S'B'
+tp2219
+a(dp2220
+g7
+I32
+saa(lp2221
+(g2185
+NS'5'
+S'C'
 tp2222
 a(dp2223
 g7
-I4
+I20
 saa(lp2224
-(S'Sensors'
-p2225
-NS'1'
-S'A'
-tp2226
-a(dp2227
-g7
-F1.3
-saa(lp2228
-(g2225
-NS'1'
-S'B'
-tp2229
-a(dp2230
-g7
-I2
-saa(lp2231
-(g2225
-NS'1'
-S'C'
-tp2232
-a(dp2233
-g7
-F1.3
-saa(lp2234
-(g2225
-NS'1'
+(g2185
+NS'5'
 S'D'
-tp2235
-a(dp2236
+tp2225
+a(dp2226
 g7
-F0.5
-saa(lp2237
-(g2225
+I8
+saa(lp2227
+(g2185
+NS'5'
+S'E'
+tp2228
+a(dp2229
+g7
+I20
+saa(lp2230
+(g2185
+NS'7'
+S'A'
+tp2231
+a(dp2232
+g7
+I80
+saa(lp2233
+(g2185
+NS'7'
+S'B'
+tp2234
+a(dp2235
+g7
+I128
+saa(lp2236
+(g2185
+NS'7'
+S'C'
+tp2237
+a(dp2238
+g7
+I80
+saa(lp2239
+(g2185
+NS'7'
+S'D'
+tp2240
+a(dp2241
+g7
+I32
+saa(lp2242
+(g2185
+NS'7'
+S'E'
+tp2243
+a(dp2244
+g7
+I80
+saa(lp2245
+(S'Research Limpet Controller'
+p2246
 NS'1'
 S'E'
-tp2238
-a(dp2239
-g7
-F1.3
-saa(lp2240
-(g2225
-NS'2'
-S'A'
-tp2241
-a(dp2242
-g7
-F2.5
-saa(lp2243
-(g2225
-NS'2'
-S'B'
-tp2244
-a(dp2245
-g7
-I4
-saa(lp2246
-(g2225
-NS'2'
-S'C'
 tp2247
 a(dp2248
 g7
-F2.5
+F1.3
 saa(lp2249
-(g2225
-NS'2'
-S'D'
-tp2250
-a(dp2251
-g7
-I1
-saa(lp2252
-(g2225
-NS'2'
+(S'Retributor Beam Laser'
+p2250
+NS'1'
 S'E'
-tp2253
-a(dp2254
+tp2251
+a(dp2252
 g7
-F2.5
-saa(lp2255
-(g2225
-NS'3'
-S'A'
-tp2256
-a(dp2257
-g7
-I5
-saa(lp2258
-(g2225
-NS'3'
+I2
+saa(lp2253
+(S'Rocket Propelled FSD Disruptor'
+p2254
+NS'2'
 S'B'
+tp2255
+a(dp2256
+g7
+I4
+saa(lp2257
+(S'Sensors'
+p2258
+NS'1'
+S'A'
 tp2259
 a(dp2260
 g7
-I8
+F1.3
 saa(lp2261
-(g2225
-NS'3'
-S'C'
+(g2258
+NS'1'
+S'B'
 tp2262
 a(dp2263
 g7
-I5
+I2
 saa(lp2264
-(g2225
-NS'3'
-S'D'
+(g2258
+NS'1'
+S'C'
 tp2265
 a(dp2266
 g7
-I2
+F1.3
 saa(lp2267
-(g2225
-NS'3'
-S'E'
+(g2258
+NS'1'
+S'D'
 tp2268
 a(dp2269
 g7
-I5
+F0.5
 saa(lp2270
-(g2225
-NS'4'
-S'A'
+(g2258
+NS'1'
+S'E'
 tp2271
 a(dp2272
 g7
-I10
+F1.3
 saa(lp2273
-(g2225
-NS'4'
-S'B'
+(g2258
+NS'2'
+S'A'
 tp2274
 a(dp2275
 g7
-I16
+F2.5
 saa(lp2276
-(g2225
-NS'4'
-S'C'
+(g2258
+NS'2'
+S'B'
 tp2277
 a(dp2278
 g7
-I10
+I4
 saa(lp2279
-(g2225
-NS'4'
-S'D'
+(g2258
+NS'2'
+S'C'
 tp2280
 a(dp2281
 g7
-I4
+F2.5
 saa(lp2282
-(g2225
-NS'4'
-S'E'
+(g2258
+NS'2'
+S'D'
 tp2283
 a(dp2284
 g7
-I10
+I1
 saa(lp2285
-(g2225
-NS'5'
-S'A'
+(g2258
+NS'2'
+S'E'
 tp2286
 a(dp2287
 g7
-I20
+F2.5
 saa(lp2288
-(g2225
-NS'5'
-S'B'
+(g2258
+NS'3'
+S'A'
 tp2289
 a(dp2290
 g7
-I32
+I5
 saa(lp2291
-(g2225
-NS'5'
-S'C'
+(g2258
+NS'3'
+S'B'
 tp2292
 a(dp2293
 g7
-I20
+I8
 saa(lp2294
-(g2225
-NS'5'
-S'D'
+(g2258
+NS'3'
+S'C'
 tp2295
 a(dp2296
 g7
-I8
+I5
 saa(lp2297
-(g2225
-NS'5'
-S'E'
+(g2258
+NS'3'
+S'D'
 tp2298
 a(dp2299
 g7
-I20
+I2
 saa(lp2300
-(g2225
-NS'6'
-S'A'
+(g2258
+NS'3'
+S'E'
 tp2301
 a(dp2302
 g7
-I40
+I5
 saa(lp2303
-(g2225
-NS'6'
-S'B'
+(g2258
+NS'4'
+S'A'
 tp2304
 a(dp2305
 g7
-I64
+I10
 saa(lp2306
-(g2225
-NS'6'
-S'C'
+(g2258
+NS'4'
+S'B'
 tp2307
 a(dp2308
 g7
-I40
+I16
 saa(lp2309
-(g2225
-NS'6'
-S'D'
+(g2258
+NS'4'
+S'C'
 tp2310
 a(dp2311
 g7
-I16
+I10
 saa(lp2312
-(g2225
-NS'6'
-S'E'
+(g2258
+NS'4'
+S'D'
 tp2313
 a(dp2314
 g7
-I40
+I4
 saa(lp2315
-(g2225
-NS'7'
-S'A'
+(g2258
+NS'4'
+S'E'
 tp2316
 a(dp2317
 g7
-I80
+I10
 saa(lp2318
-(g2225
-NS'7'
-S'B'
+(g2258
+NS'5'
+S'A'
 tp2319
 a(dp2320
 g7
-I128
+I20
 saa(lp2321
-(g2225
-NS'7'
-S'C'
+(g2258
+NS'5'
+S'B'
 tp2322
 a(dp2323
 g7
-I80
+I32
 saa(lp2324
-(g2225
-NS'7'
-S'D'
+(g2258
+NS'5'
+S'C'
 tp2325
 a(dp2326
 g7
-I32
+I20
 saa(lp2327
-(g2225
-NS'7'
-S'E'
+(g2258
+NS'5'
+S'D'
 tp2328
 a(dp2329
 g7
-I80
+I8
 saa(lp2330
-(g2225
-NS'8'
-S'A'
+(g2258
+NS'5'
+S'E'
 tp2331
 a(dp2332
 g7
-I160
+I20
 saa(lp2333
-(g2225
-NS'8'
-S'B'
+(g2258
+NS'6'
+S'A'
 tp2334
 a(dp2335
 g7
-I256
+I40
 saa(lp2336
-(g2225
-NS'8'
-S'C'
+(g2258
+NS'6'
+S'B'
 tp2337
 a(dp2338
 g7
-I160
+I64
 saa(lp2339
-(g2225
-NS'8'
-S'D'
+(g2258
+NS'6'
+S'C'
 tp2340
 a(dp2341
 g7
-I64
+I40
 saa(lp2342
-(g2225
-NS'8'
-S'E'
+(g2258
+NS'6'
+S'D'
 tp2343
 a(dp2344
 g7
-I160
+I16
 saa(lp2345
+(g2258
+NS'6'
+S'E'
+tp2346
+a(dp2347
+g7
+I40
+saa(lp2348
+(g2258
+NS'7'
+S'A'
+tp2349
+a(dp2350
+g7
+I80
+saa(lp2351
+(g2258
+NS'7'
+S'B'
+tp2352
+a(dp2353
+g7
+I128
+saa(lp2354
+(g2258
+NS'7'
+S'C'
+tp2355
+a(dp2356
+g7
+I80
+saa(lp2357
+(g2258
+NS'7'
+S'D'
+tp2358
+a(dp2359
+g7
+I32
+saa(lp2360
+(g2258
+NS'7'
+S'E'
+tp2361
+a(dp2362
+g7
+I80
+saa(lp2363
+(g2258
+NS'8'
+S'A'
+tp2364
+a(dp2365
+g7
+I160
+saa(lp2366
+(g2258
+NS'8'
+S'B'
+tp2367
+a(dp2368
+g7
+I256
+saa(lp2369
+(g2258
+NS'8'
+S'C'
+tp2370
+a(dp2371
+g7
+I160
+saa(lp2372
+(g2258
+NS'8'
+S'D'
+tp2373
+a(dp2374
+g7
+I64
+saa(lp2375
+(g2258
+NS'8'
+S'E'
+tp2376
+a(dp2377
+g7
+I160
+saa(lp2378
 (S'Shield Booster'
-p2346
+p2379
 NS'0'
 S'A'
-tp2347
-a(dp2348
+tp2380
+a(dp2381
 g7
 F3.5
-saa(lp2349
-(g2346
+saa(lp2382
+(g2379
 NS'0'
 S'B'
-tp2350
-a(dp2351
+tp2383
+a(dp2384
 g7
 I3
-saa(lp2352
-(g2346
+saa(lp2385
+(g2379
 NS'0'
 S'C'
-tp2353
-a(dp2354
+tp2386
+a(dp2387
 g7
 I2
-saa(lp2355
-(g2346
+saa(lp2388
+(g2379
 NS'0'
 S'D'
-tp2356
-a(dp2357
+tp2389
+a(dp2390
 g7
 I1
-saa(lp2358
-(g2346
+saa(lp2391
+(g2379
 NS'0'
 S'E'
-tp2359
-a(dp2360
+tp2392
+a(dp2393
 g7
 F0.5
-saa(lp2361
+saa(lp2394
 (S'Shield Cell Bank'
-p2362
+p2395
 NS'1'
 S'A'
-tp2363
-a(dp2364
-g7
-F1.3
-saa(lp2365
-(g2362
-NS'1'
-S'B'
-tp2366
-a(dp2367
-g7
-I2
-saa(lp2368
-(g2362
-NS'1'
-S'C'
-tp2369
-a(dp2370
-g7
-F1.3
-saa(lp2371
-(g2362
-NS'1'
-S'D'
-tp2372
-a(dp2373
-g7
-F0.5
-saa(lp2374
-(g2362
-NS'1'
-S'E'
-tp2375
-a(dp2376
-g7
-F1.3
-saa(lp2377
-(g2362
-NS'2'
-S'A'
-tp2378
-a(dp2379
-g7
-F2.5
-saa(lp2380
-(g2362
-NS'2'
-S'B'
-tp2381
-a(dp2382
-g7
-I4
-saa(lp2383
-(g2362
-NS'2'
-S'C'
-tp2384
-a(dp2385
-g7
-F2.5
-saa(lp2386
-(g2362
-NS'2'
-S'D'
-tp2387
-a(dp2388
-g7
-I1
-saa(lp2389
-(g2362
-NS'2'
-S'E'
-tp2390
-a(dp2391
-g7
-F2.5
-saa(lp2392
-(g2362
-NS'3'
-S'A'
-tp2393
-a(dp2394
-g7
-I5
-saa(lp2395
-(g2362
-NS'3'
-S'B'
 tp2396
 a(dp2397
 g7
-I8
+F1.3
 saa(lp2398
-(g2362
-NS'3'
-S'C'
+(g2395
+NS'1'
+S'B'
 tp2399
 a(dp2400
 g7
-I5
+I2
 saa(lp2401
-(g2362
-NS'3'
-S'D'
+(g2395
+NS'1'
+S'C'
 tp2402
 a(dp2403
 g7
-I2
+F1.3
 saa(lp2404
-(g2362
-NS'3'
-S'E'
+(g2395
+NS'1'
+S'D'
 tp2405
 a(dp2406
 g7
-I5
+F0.5
 saa(lp2407
-(g2362
-NS'4'
-S'A'
+(g2395
+NS'1'
+S'E'
 tp2408
 a(dp2409
 g7
-I10
+F1.3
 saa(lp2410
-(g2362
-NS'4'
-S'B'
+(g2395
+NS'2'
+S'A'
 tp2411
 a(dp2412
 g7
-I16
+F2.5
 saa(lp2413
-(g2362
-NS'4'
-S'C'
+(g2395
+NS'2'
+S'B'
 tp2414
 a(dp2415
 g7
-I10
+I4
 saa(lp2416
-(g2362
-NS'4'
-S'D'
+(g2395
+NS'2'
+S'C'
 tp2417
 a(dp2418
 g7
-I4
+F2.5
 saa(lp2419
-(g2362
-NS'4'
-S'E'
+(g2395
+NS'2'
+S'D'
 tp2420
 a(dp2421
 g7
-I10
+I1
 saa(lp2422
-(g2362
-NS'5'
-S'A'
+(g2395
+NS'2'
+S'E'
 tp2423
 a(dp2424
 g7
-I20
+F2.5
 saa(lp2425
-(g2362
-NS'5'
-S'B'
+(g2395
+NS'3'
+S'A'
 tp2426
 a(dp2427
 g7
-I32
+I5
 saa(lp2428
-(g2362
-NS'5'
-S'C'
+(g2395
+NS'3'
+S'B'
 tp2429
 a(dp2430
 g7
-I20
+I8
 saa(lp2431
-(g2362
-NS'5'
-S'D'
+(g2395
+NS'3'
+S'C'
 tp2432
 a(dp2433
 g7
-I8
+I5
 saa(lp2434
-(g2362
-NS'5'
-S'E'
+(g2395
+NS'3'
+S'D'
 tp2435
 a(dp2436
 g7
-I20
+I2
 saa(lp2437
-(g2362
-NS'6'
-S'A'
+(g2395
+NS'3'
+S'E'
 tp2438
 a(dp2439
 g7
-I40
+I5
 saa(lp2440
-(g2362
-NS'6'
-S'B'
+(g2395
+NS'4'
+S'A'
 tp2441
 a(dp2442
 g7
-I64
+I10
 saa(lp2443
-(g2362
-NS'6'
-S'C'
+(g2395
+NS'4'
+S'B'
 tp2444
 a(dp2445
 g7
-I40
+I16
 saa(lp2446
-(g2362
-NS'6'
-S'D'
+(g2395
+NS'4'
+S'C'
 tp2447
 a(dp2448
 g7
-I16
+I10
 saa(lp2449
-(g2362
-NS'6'
-S'E'
+(g2395
+NS'4'
+S'D'
 tp2450
 a(dp2451
 g7
-I40
+I4
 saa(lp2452
-(g2362
-NS'7'
-S'A'
+(g2395
+NS'4'
+S'E'
 tp2453
 a(dp2454
 g7
-I80
+I10
 saa(lp2455
-(g2362
-NS'7'
-S'B'
+(g2395
+NS'5'
+S'A'
 tp2456
 a(dp2457
 g7
-I128
+I20
 saa(lp2458
-(g2362
-NS'7'
-S'C'
+(g2395
+NS'5'
+S'B'
 tp2459
 a(dp2460
 g7
-I80
+I32
 saa(lp2461
-(g2362
-NS'7'
-S'D'
+(g2395
+NS'5'
+S'C'
 tp2462
 a(dp2463
 g7
-I32
+I20
 saa(lp2464
-(g2362
-NS'7'
-S'E'
+(g2395
+NS'5'
+S'D'
 tp2465
 a(dp2466
 g7
-I80
+I8
 saa(lp2467
-(g2362
-NS'8'
-S'A'
+(g2395
+NS'5'
+S'E'
 tp2468
 a(dp2469
 g7
-I160
+I20
 saa(lp2470
-(g2362
-NS'8'
-S'B'
+(g2395
+NS'6'
+S'A'
 tp2471
 a(dp2472
 g7
-I256
+I40
 saa(lp2473
-(g2362
-NS'8'
-S'C'
+(g2395
+NS'6'
+S'B'
 tp2474
 a(dp2475
 g7
-I160
+I64
 saa(lp2476
-(g2362
-NS'8'
-S'D'
+(g2395
+NS'6'
+S'C'
 tp2477
 a(dp2478
 g7
-I64
+I40
 saa(lp2479
-(g2362
-NS'8'
-S'E'
+(g2395
+NS'6'
+S'D'
 tp2480
 a(dp2481
 g7
-I160
+I16
 saa(lp2482
+(g2395
+NS'6'
+S'E'
+tp2483
+a(dp2484
+g7
+I40
+saa(lp2485
+(g2395
+NS'7'
+S'A'
+tp2486
+a(dp2487
+g7
+I80
+saa(lp2488
+(g2395
+NS'7'
+S'B'
+tp2489
+a(dp2490
+g7
+I128
+saa(lp2491
+(g2395
+NS'7'
+S'C'
+tp2492
+a(dp2493
+g7
+I80
+saa(lp2494
+(g2395
+NS'7'
+S'D'
+tp2495
+a(dp2496
+g7
+I32
+saa(lp2497
+(g2395
+NS'7'
+S'E'
+tp2498
+a(dp2499
+g7
+I80
+saa(lp2500
+(g2395
+NS'8'
+S'A'
+tp2501
+a(dp2502
+g7
+I160
+saa(lp2503
+(g2395
+NS'8'
+S'B'
+tp2504
+a(dp2505
+g7
+I256
+saa(lp2506
+(g2395
+NS'8'
+S'C'
+tp2507
+a(dp2508
+g7
+I160
+saa(lp2509
+(g2395
+NS'8'
+S'D'
+tp2510
+a(dp2511
+g7
+I64
+saa(lp2512
+(g2395
+NS'8'
+S'E'
+tp2513
+a(dp2514
+g7
+I160
+saa(lp2515
 (S'Shield Generator'
-p2483
+p2516
 NS'1'
 S'A'
-tp2484
-a(dp2485
-g7
-F1.3
-saa(lp2486
-(g2483
-NS'2'
-S'A'
-tp2487
-a(dp2488
-g7
-F2.5
-saa(lp2489
-(g2483
-NS'2'
-S'B'
-tp2490
-a(dp2491
-g7
-I4
-saa(lp2492
-(g2483
-NS'2'
-S'C'
-tp2493
-a(dp2494
-g7
-F2.5
-saa(lp2495
-(g2483
-NS'2'
-S'D'
-tp2496
-a(dp2497
-g7
-I1
-saa(lp2498
-(g2483
-NS'2'
-S'E'
-tp2499
-a(dp2500
-g7
-F2.5
-saa(lp2501
-(g2483
-NS'3'
-S'A'
-tp2502
-a(dp2503
-g7
-I5
-saa(lp2504
-(g2483
-NS'3'
-S'B'
-tp2505
-a(dp2506
-g7
-I8
-saa(lp2507
-(g2483
-NS'3'
-S'C'
-tp2508
-a(dp2509
-g7
-I5
-saa(lp2510
-(g2483
-NS'3'
-S'D'
-tp2511
-a(dp2512
-g7
-I2
-saa(lp2513
-(g2483
-NS'3'
-S'E'
-tp2514
-a(dp2515
-g7
-I5
-saa(lp2516
-(g2483
-NS'4'
-S'A'
 tp2517
 a(dp2518
 g7
-I10
+F1.3
 saa(lp2519
-(g2483
-NS'4'
-S'B'
+(g2516
+NS'2'
+S'A'
 tp2520
 a(dp2521
 g7
-I16
+F2.5
 saa(lp2522
-(g2483
-NS'4'
-S'C'
+(g2516
+NS'2'
+S'B'
 tp2523
 a(dp2524
 g7
-I10
+I4
 saa(lp2525
-(g2483
-NS'4'
-S'D'
+(g2516
+NS'2'
+S'C'
 tp2526
 a(dp2527
 g7
-I4
+F2.5
 saa(lp2528
-(g2483
-NS'4'
-S'E'
+(g2516
+NS'2'
+S'D'
 tp2529
 a(dp2530
 g7
-I10
+I1
 saa(lp2531
-(g2483
-NS'5'
-S'A'
+(g2516
+NS'2'
+S'E'
 tp2532
 a(dp2533
 g7
-I20
+F2.5
 saa(lp2534
-(g2483
-NS'5'
-S'B'
+(g2516
+NS'3'
+S'A'
 tp2535
 a(dp2536
 g7
-I32
+I5
 saa(lp2537
-(g2483
-NS'5'
-S'C'
+(g2516
+NS'3'
+S'B'
 tp2538
 a(dp2539
 g7
-I20
+I8
 saa(lp2540
-(g2483
-NS'5'
-S'D'
+(g2516
+NS'3'
+S'C'
 tp2541
 a(dp2542
 g7
-I8
+I5
 saa(lp2543
-(g2483
-NS'5'
-S'E'
+(g2516
+NS'3'
+S'D'
 tp2544
 a(dp2545
 g7
-I20
+I2
 saa(lp2546
-(g2483
-NS'6'
-S'A'
+(g2516
+NS'3'
+S'E'
 tp2547
 a(dp2548
 g7
-I40
+I5
 saa(lp2549
-(g2483
-NS'6'
-S'B'
+(g2516
+NS'4'
+S'A'
 tp2550
 a(dp2551
 g7
-I64
+I10
 saa(lp2552
-(g2483
-NS'6'
-S'C'
+(g2516
+NS'4'
+S'B'
 tp2553
 a(dp2554
 g7
-I40
+I16
 saa(lp2555
-(g2483
-NS'6'
-S'D'
+(g2516
+NS'4'
+S'C'
 tp2556
 a(dp2557
 g7
-I16
+I10
 saa(lp2558
-(g2483
-NS'6'
-S'E'
+(g2516
+NS'4'
+S'D'
 tp2559
 a(dp2560
 g7
-I40
+I4
 saa(lp2561
-(g2483
-NS'7'
-S'A'
+(g2516
+NS'4'
+S'E'
 tp2562
 a(dp2563
 g7
-I80
+I10
 saa(lp2564
-(g2483
-NS'7'
-S'B'
+(g2516
+NS'5'
+S'A'
 tp2565
 a(dp2566
 g7
-I128
+I20
 saa(lp2567
-(g2483
-NS'7'
-S'C'
+(g2516
+NS'5'
+S'B'
 tp2568
 a(dp2569
 g7
-I80
+I32
 saa(lp2570
-(g2483
-NS'7'
-S'D'
+(g2516
+NS'5'
+S'C'
 tp2571
 a(dp2572
 g7
-I32
+I20
 saa(lp2573
-(g2483
-NS'7'
-S'E'
+(g2516
+NS'5'
+S'D'
 tp2574
 a(dp2575
 g7
-I80
+I8
 saa(lp2576
-(g2483
-NS'8'
-S'A'
+(g2516
+NS'5'
+S'E'
 tp2577
 a(dp2578
 g7
-I160
+I20
 saa(lp2579
-(g2483
-NS'8'
-S'B'
+(g2516
+NS'6'
+S'A'
 tp2580
 a(dp2581
 g7
-I256
+I40
 saa(lp2582
-(g2483
-NS'8'
-S'C'
+(g2516
+NS'6'
+S'B'
 tp2583
 a(dp2584
 g7
-I160
+I64
 saa(lp2585
-(g2483
-NS'8'
-S'D'
+(g2516
+NS'6'
+S'C'
 tp2586
 a(dp2587
 g7
-I64
+I40
 saa(lp2588
-(g2483
-NS'8'
-S'E'
+(g2516
+NS'6'
+S'D'
 tp2589
 a(dp2590
 g7
-I160
+I16
 saa(lp2591
-(S'Shock Mine Launcher'
-p2592
-NS'1'
-S'I'
-tp2593
-a(dp2594
-g7
-I2
-saa(lp2595
-(S'Shutdown Field Neutraliser'
-p2596
-NS'0'
-S'F'
-tp2597
-a(dp2598
-g7
-F1.3
-saa(lp2599
-(S'Standard Docking Computer'
-p2600
-NS'1'
+(g2516
+NS'6'
 S'E'
+tp2592
+a(dp2593
+g7
+I40
+saa(lp2594
+(g2516
+NS'7'
+S'A'
+tp2595
+a(dp2596
+g7
+I80
+saa(lp2597
+(g2516
+NS'7'
+S'B'
+tp2598
+a(dp2599
+g7
+I128
+saa(lp2600
+(g2516
+NS'7'
+S'C'
 tp2601
 a(dp2602
 g7
-I0
+I80
 saa(lp2603
-(S'Thrusters'
-p2604
-NS'2'
-S'A'
-tp2605
-a(dp2606
-g7
-F2.5
-saa(lp2607
-(g2604
-NS'2'
-S'B'
-tp2608
-a(dp2609
-g7
-I4
-saa(lp2610
-(g2604
-NS'2'
-S'C'
-tp2611
-a(dp2612
-g7
-F2.5
-saa(lp2613
-(g2604
-NS'2'
+(g2516
+NS'7'
 S'D'
-tp2614
-a(dp2615
+tp2604
+a(dp2605
 g7
-I1
-saa(lp2616
-(g2604
-NS'2'
+I32
+saa(lp2606
+(g2516
+NS'7'
 S'E'
-tp2617
-a(dp2618
+tp2607
+a(dp2608
 g7
-F2.5
-saa(lp2619
-(g2604
-NS'3'
+I80
+saa(lp2609
+(g2516
+NS'8'
 S'A'
-tp2620
-a(dp2621
+tp2610
+a(dp2611
 g7
-I5
-saa(lp2622
-(g2604
-NS'3'
+I160
+saa(lp2612
+(g2516
+NS'8'
 S'B'
-tp2623
-a(dp2624
+tp2613
+a(dp2614
 g7
-I8
-saa(lp2625
-(g2604
-NS'3'
+I256
+saa(lp2615
+(g2516
+NS'8'
 S'C'
+tp2616
+a(dp2617
+g7
+I160
+saa(lp2618
+(g2516
+NS'8'
+S'D'
+tp2619
+a(dp2620
+g7
+I64
+saa(lp2621
+(g2516
+NS'8'
+S'E'
+tp2622
+a(dp2623
+g7
+I160
+saa(lp2624
+(S'Shock Mine Launcher'
+p2625
+NS'1'
+S'I'
 tp2626
 a(dp2627
 g7
-I5
-saa(lp2628
-(g2604
-NS'3'
-S'D'
-tp2629
-a(dp2630
-g7
 I2
-saa(lp2631
-(g2604
-NS'3'
+saa(lp2628
+(S'Shutdown Field Neutraliser'
+p2629
+NS'0'
+S'F'
+tp2630
+a(dp2631
+g7
+F1.3
+saa(lp2632
+(S'Standard Docking Computer'
+p2633
+NS'1'
 S'E'
-tp2632
-a(dp2633
+tp2634
+a(dp2635
 g7
-I5
-saa(lp2634
-(g2604
-NS'4'
+I0
+saa(lp2636
+(S'Thrusters'
+p2637
+NS'2'
 S'A'
-tp2635
-a(dp2636
-g7
-I10
-saa(lp2637
-(g2604
-NS'4'
-S'B'
 tp2638
 a(dp2639
 g7
-I16
+F2.5
 saa(lp2640
-(g2604
-NS'4'
-S'C'
+(g2637
+NS'2'
+S'B'
 tp2641
 a(dp2642
 g7
-I10
+I4
 saa(lp2643
-(g2604
-NS'4'
-S'D'
+(g2637
+NS'2'
+S'C'
 tp2644
 a(dp2645
 g7
-I4
+F2.5
 saa(lp2646
-(g2604
-NS'4'
-S'E'
+(g2637
+NS'2'
+S'D'
 tp2647
 a(dp2648
 g7
-I10
+I1
 saa(lp2649
-(g2604
-NS'5'
-S'A'
+(g2637
+NS'2'
+S'E'
 tp2650
 a(dp2651
 g7
-I20
+F2.5
 saa(lp2652
-(g2604
-NS'5'
-S'B'
+(g2637
+NS'3'
+S'A'
 tp2653
 a(dp2654
 g7
-I32
+I5
 saa(lp2655
-(g2604
-NS'5'
-S'C'
+(g2637
+NS'3'
+S'B'
 tp2656
 a(dp2657
 g7
-I20
+I8
 saa(lp2658
-(g2604
-NS'5'
-S'D'
+(g2637
+NS'3'
+S'C'
 tp2659
 a(dp2660
 g7
-I8
+I5
 saa(lp2661
-(g2604
-NS'5'
-S'E'
+(g2637
+NS'3'
+S'D'
 tp2662
 a(dp2663
 g7
-I20
+I2
 saa(lp2664
-(g2604
-NS'6'
-S'A'
+(g2637
+NS'3'
+S'E'
 tp2665
 a(dp2666
 g7
-I40
+I5
 saa(lp2667
-(g2604
-NS'6'
-S'B'
+(g2637
+NS'4'
+S'A'
 tp2668
 a(dp2669
 g7
-I64
+I10
 saa(lp2670
-(g2604
-NS'6'
-S'C'
+(g2637
+NS'4'
+S'B'
 tp2671
 a(dp2672
 g7
-I40
+I16
 saa(lp2673
-(g2604
-NS'6'
-S'D'
+(g2637
+NS'4'
+S'C'
 tp2674
 a(dp2675
 g7
-I16
+I10
 saa(lp2676
-(g2604
-NS'6'
-S'E'
+(g2637
+NS'4'
+S'D'
 tp2677
 a(dp2678
 g7
-I40
+I4
 saa(lp2679
-(g2604
-NS'7'
-S'A'
+(g2637
+NS'4'
+S'E'
 tp2680
 a(dp2681
 g7
-I80
+I10
 saa(lp2682
-(g2604
-NS'7'
-S'B'
+(g2637
+NS'5'
+S'A'
 tp2683
 a(dp2684
 g7
-I128
+I20
 saa(lp2685
-(g2604
-NS'7'
-S'C'
+(g2637
+NS'5'
+S'B'
 tp2686
 a(dp2687
 g7
-I80
+I32
 saa(lp2688
-(g2604
-NS'7'
-S'D'
+(g2637
+NS'5'
+S'C'
 tp2689
 a(dp2690
 g7
-I32
+I20
 saa(lp2691
-(g2604
-NS'7'
-S'E'
+(g2637
+NS'5'
+S'D'
 tp2692
 a(dp2693
 g7
-I80
+I8
 saa(lp2694
-(g2604
-NS'8'
-S'A'
+(g2637
+NS'5'
+S'E'
 tp2695
 a(dp2696
 g7
-I160
+I20
 saa(lp2697
-(g2604
-NS'8'
-S'B'
+(g2637
+NS'6'
+S'A'
 tp2698
 a(dp2699
 g7
-I256
+I40
 saa(lp2700
-(g2604
-NS'8'
-S'C'
+(g2637
+NS'6'
+S'B'
 tp2701
 a(dp2702
 g7
-I160
+I64
 saa(lp2703
-(g2604
-NS'8'
-S'D'
+(g2637
+NS'6'
+S'C'
 tp2704
 a(dp2705
 g7
-I64
+I40
 saa(lp2706
-(g2604
-NS'8'
-S'E'
+(g2637
+NS'6'
+S'D'
 tp2707
 a(dp2708
 g7
-I160
+I16
 saa(lp2709
+(g2637
+NS'6'
+S'E'
+tp2710
+a(dp2711
+g7
+I40
+saa(lp2712
+(g2637
+NS'7'
+S'A'
+tp2713
+a(dp2714
+g7
+I80
+saa(lp2715
+(g2637
+NS'7'
+S'B'
+tp2716
+a(dp2717
+g7
+I128
+saa(lp2718
+(g2637
+NS'7'
+S'C'
+tp2719
+a(dp2720
+g7
+I80
+saa(lp2721
+(g2637
+NS'7'
+S'D'
+tp2722
+a(dp2723
+g7
+I32
+saa(lp2724
+(g2637
+NS'7'
+S'E'
+tp2725
+a(dp2726
+g7
+I80
+saa(lp2727
+(g2637
+NS'8'
+S'A'
+tp2728
+a(dp2729
+g7
+I160
+saa(lp2730
+(g2637
+NS'8'
+S'B'
+tp2731
+a(dp2732
+g7
+I256
+saa(lp2733
+(g2637
+NS'8'
+S'C'
+tp2734
+a(dp2735
+g7
+I160
+saa(lp2736
+(g2637
+NS'8'
+S'D'
+tp2737
+a(dp2738
+g7
+I64
+saa(lp2739
+(g2637
+NS'8'
+S'E'
+tp2740
+a(dp2741
+g7
+I160
+saa(lp2742
 (S'Torpedo Pylon'
-p2710
+p2743
 NS'1'
 S'I'
-tp2711
-a(dp2712
+tp2744
+a(dp2745
 g7
 I2
-saa(lp2713
-(g2710
+saa(lp2746
+(g2743
 NS'2'
 S'I'
-tp2714
-a(dp2715
+tp2747
+a(dp2748
 g7
 I4
-saa(lp2716
+saa(lp2749
 (S'Xeno Scanner'
-p2717
+p2750
 NS'0'
 S'E'
-tp2718
-a(dp2719
+tp2751
+a(dp2752
 g7
 F1.3
-saatRp2720
+saatRp2753
 .
\ No newline at end of file
diff --git a/outfitting.py b/outfitting.py
index a122a313..83088b86 100644
--- a/outfitting.py
+++ b/outfitting.py
@@ -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',
diff --git a/ships.p b/ships.p
index ae865753..b2af170c 100644
--- a/ships.p
+++ b/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
 .
\ No newline at end of file

From a081b6b637ab86057660e89f4efe9475933da26d Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Mon, 29 Jan 2018 01:34:30 +0000
Subject: [PATCH 02/15] Monitor player status, and call new "status" plugin
 callback

---
 EDMarketConnector.py |  19 +++++++
 PLUGINS.md           |  14 ++++-
 plug.py              |  21 +++++++
 status.py            | 131 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 183 insertions(+), 2 deletions(-)
 create mode 100644 status.py

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index 672ab03c..1a1fd7e7 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -57,6 +57,7 @@ import prefs
 import plug
 from hotkey import hotkeymgr
 from monitor import monitor
+from status import status
 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('<<StatusEvent>>', self.status_event)	# Cmdr Status 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 status monitoring
+                if not status.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 status_event(self, event):
+        entry = status.status
+        if entry:
+            # Currently we don't do anything with these events
+            err = plug.notify_status(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'):
@@ -779,6 +797,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()
+        status.close()
         monitor.close()
         plug.notify_stop()
         self.eddn.close()
diff --git a/PLUGINS.md b/PLUGINS.md
index f1c5ef8f..c85a5fe5 100644
--- a/PLUGINS.md
+++ b/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 Status
+
+This gets called periodically - typically about once a second - whith the players live status
+
+```python
+def status(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()` 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.
 
diff --git a/plug.py b/plug.py
index 9ffc29a6..045a59f3 100644
--- a/plug.py
+++ b/plug.py
@@ -226,6 +226,27 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state):
     return error
 
 
+def notify_status(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('status')
+        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.
diff --git a/status.py b/status.py
new file mode 100644
index 00000000..466b30fe
--- /dev/null
+++ b/status.py
@@ -0,0 +1,131 @@
+import json
+from calendar import timegm
+from operator import itemgetter
+from os import listdir, stat
+from os.path import getmtime, isdir, 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 Status(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('statusdir')) 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 status "%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 Status'
+        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 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('<<StatusEvent>>', when="tail")
+        except:
+            if __debug__: print_exc()
+
+# singleton
+status = Status()

From 75edff5fc3c45ba2bce7b9f17b96c12ddc49a871 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Mon, 29 Jan 2018 23:39:00 +0000
Subject: [PATCH 03/15] Rename plugin callback to dashboard_entry

---
 EDMarketConnector.py      | 16 ++++++++--------
 PLUGINS.md                |  8 ++++----
 status.py => dashboard.py | 14 +++++++-------
 plug.py                   |  4 ++--
 4 files changed, 21 insertions(+), 21 deletions(-)
 rename status.py => dashboard.py (91%)

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index 1a1fd7e7..aed8b007 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -57,7 +57,7 @@ import prefs
 import plug
 from hotkey import hotkeymgr
 from monitor import monitor
-from status import status
+from dashboard import dashboard
 from theme import theme
 
 
@@ -270,7 +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('<<StatusEvent>>', self.status_event)	# Cmdr Status 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
 
@@ -630,8 +630,8 @@ class AppWindow:
                     hotkeymgr.play_bad()
 
             if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started:
-                # Can start status monitoring
-                if not status.start(self.w, 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
@@ -686,11 +686,11 @@ class AppWindow:
                     hotkeymgr.play_bad()
 
     # Handle Status event
-    def status_event(self, event):
-        entry = status.status
+    def dashboard_event(self, event):
+        entry = dashboard.status
         if entry:
             # Currently we don't do anything with these events
-            err = plug.notify_status(monitor.cmdr, monitor.is_beta, entry)
+            err = plug.notify_dashboard_entry(monitor.cmdr, monitor.is_beta, entry)
             if err:
                 self.status['text'] = err
                 if not config.getint('hotkey_mute'):
@@ -797,7 +797,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()
-        status.close()
+        dashboard.close()
         monitor.close()
         plug.notify_stop()
         self.eddn.close()
diff --git a/PLUGINS.md b/PLUGINS.md
index c85a5fe5..663d0140 100644
--- a/PLUGINS.md
+++ b/PLUGINS.md
@@ -128,12 +128,12 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
             sys.stderr.write("Arrived at {}\n".format(entry['StarSystem']))
 ```
 
-### Player Status
+### Player Dashboard
 
-This gets called periodically - typically about once a second - whith the players live status
+This gets called when something on the player's cockpit display changes - typically about once a second when in orbital flight
 
 ```python
-def status(cmdr, is_beta, entry):
+def dashboard_entry(cmdr, is_beta, entry):
     deployed = entry['Flags'] & 1<<6
     sys.stderr.write("Hardpoints {}\n", deployed and "deployed" or "stowed")
 ```
@@ -154,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()`, `status()` 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.
 
diff --git a/status.py b/dashboard.py
similarity index 91%
rename from status.py
rename to dashboard.py
index 466b30fe..ce78f02d 100644
--- a/status.py
+++ b/dashboard.py
@@ -2,7 +2,7 @@ import json
 from calendar import timegm
 from operator import itemgetter
 from os import listdir, stat
-from os.path import getmtime, isdir, join
+from os.path import isdir, join
 from sys import platform
 import time
 
@@ -26,7 +26,7 @@ else:
 
 
 # Status.json handler
-class Status(FileSystemEventHandler):
+class Dashboard(FileSystemEventHandler):
 
     _POLL = 1		# Fallback polling interval
 
@@ -55,7 +55,7 @@ class Status(FileSystemEventHandler):
         # 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('statusdir')) and platform != 'win32'
+        polling = bool(config.get('journaldir')) and platform != 'win32'
         if not polling and not self.observer:
             self.observer = Observer()
             self.observer.daemon = True
@@ -68,7 +68,7 @@ class Status(FileSystemEventHandler):
             self.observed = self.observer.schedule(self, self.currentdir)
 
         if __debug__:
-            print '%s status "%s"' % (polling and 'Polling' or 'Monitoring', self.currentdir)
+            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
@@ -79,7 +79,7 @@ class Status(FileSystemEventHandler):
 
     def stop(self):
         if __debug__:
-            print 'Stopping monitoring Status'
+            print 'Stopping monitoring Dashboard'
         self.currentdir = None
         if self.observed:
             self.observed = None
@@ -123,9 +123,9 @@ class Status(FileSystemEventHandler):
             # 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('<<StatusEvent>>', when="tail")
+                self.root.event_generate('<<DashboardEvent>>', when="tail")
         except:
             if __debug__: print_exc()
 
 # singleton
-status = Status()
+dashboard = Dashboard()
diff --git a/plug.py b/plug.py
index 045a59f3..8ee5c29c 100644
--- a/plug.py
+++ b/plug.py
@@ -226,7 +226,7 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state):
     return error
 
 
-def notify_status(cmdr, is_beta, entry):
+def notify_dashboard_entry(cmdr, is_beta, entry):
     """
     Send a status entry to each plugin.
     :param cmdr: The piloting Cmdr name
@@ -236,7 +236,7 @@ def notify_status(cmdr, is_beta, entry):
     """
     error = None
     for plugin in PLUGINS:
-        status = plugin._get_func('status')
+        status = plugin._get_func('dashboard_entry')
         if status:
             try:
                 # Pass a copy of the status entry in case the callee modifies it

From 1636148fe07c7b33dde82b7030f1eaeaa1be485c Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Tue, 30 Jan 2018 22:56:47 +0000
Subject: [PATCH 04/15] Include planetary body in StartUp event

---
 monitor.py | 32 ++++++++++++++------------------
 1 file changed, 14 insertions(+), 18 deletions(-)

diff --git a/monitor.py b/monitor.py
index f1358791..392be811 100644
--- a/monitor.py
+++ b/monitor.py
@@ -224,24 +224,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.body:
+                    entry['Body'] = self.body
+                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)
@@ -403,9 +397,11 @@ 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():

From 7b8afa4f4a67de358d5db4d1096b942f1a845a77 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Tue, 30 Jan 2018 23:07:14 +0000
Subject: [PATCH 05/15] Fix StartUp event

Bug introduced in 1636148
---
 monitor.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/monitor.py b/monitor.py
index 392be811..9b626681 100644
--- a/monitor.py
+++ b/monitor.py
@@ -230,8 +230,8 @@ class EDLogs(FileSystemEventHandler):
                     ('StarSystem', self.system),
                     ('StarPos', self.coordinates),
                 ])
-                if self.body:
-                    entry['Body'] = self.body
+                if self.planet:
+                    entry['Body'] = self.planet
                 entry['Docked'] = bool(self.station)
                 if self.station:
                     entry['StationName'] = self.station

From 1a2b16a7b39e37f0d60b39df03d4b1df46676b0f Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sat, 10 Feb 2018 16:43:08 +0000
Subject: [PATCH 06/15] Handle transitory files

---
 dashboard.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/dashboard.py b/dashboard.py
index ce78f02d..35a3f7ef 100644
--- a/dashboard.py
+++ b/dashboard.py
@@ -2,7 +2,7 @@ import json
 from calendar import timegm
 from operator import itemgetter
 from os import listdir, stat
-from os.path import isdir, join
+from os.path import isdir, isfile, join
 from sys import platform
 import time
 
@@ -111,7 +111,7 @@ class Dashboard(FileSystemEventHandler):
 
     def on_modified(self, event):
         # watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows
-        if event.is_directory or stat(event.src_path).st_size:	# Can get on_modified events when the file is emptied
+        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.

From 4e674f85d58e961ca432d7da40253a85e5847380 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sat, 10 Feb 2018 17:09:32 +0000
Subject: [PATCH 07/15] Log individual event errors

---
 plugins/edsm.py | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/plugins/edsm.py b/plugins/edsm.py
index 870c9407..d4ce4d05 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -310,14 +310,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):
+                            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

From d8dd7af2e22db2e426a91ebff04d6562dd204439 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sat, 10 Feb 2018 17:10:03 +0000
Subject: [PATCH 08/15] Adjust batching for new startup event order

---
 monitor.py       |  4 ++--
 plugins/edsm.py  | 38 ++++++++++++++++++++++++--------------
 plugins/inara.py | 14 +++++++-------
 3 files changed, 33 insertions(+), 23 deletions(-)

diff --git a/monitor.py b/monitor.py
index 9b626681..b60eca34 100644
--- a/monitor.py
+++ b/monitor.py
@@ -328,8 +328,9 @@ class EDLogs(FileSystemEventHandler):
                     'ShipName'     : None,
                     'ShipType'     : 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')
@@ -413,7 +414,6 @@ class EDLogs(FileSystemEventHandler):
                         self.state['Rank'][k] = (self.state['Rank'][k][0], min(v, 100))	# perhaps not taken promotion mission yet
 
             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']:
diff --git a/plugins/edsm.py b/plugins/edsm.py
index d4ce4d05..5cb0b824 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -39,6 +39,8 @@ 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 +200,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 +222,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',
@@ -311,7 +315,7 @@ def worker():
                         print('EDSM\t%s %s\t%s' % (msgnum, msg, json.dumps(pending, separators = (',', ': '))))
                         plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg))
                     else:
-                        for e, r in zip(pending, reply):
+                        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
@@ -337,10 +341,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
 
diff --git a/plugins/inara.py b/plugins/inara.py
index d0d731f2..e5392f6b 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -36,7 +36,7 @@ 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
@@ -163,7 +163,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.
 
@@ -172,7 +172,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
             old_events = len(this.events)	# Will only send existing events if we add a new event below
 
             # 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'],
@@ -227,7 +227,7 @@ 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':
@@ -247,7 +247,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
                 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 +262,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'],
@@ -603,7 +603,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']:

From 2aadc17a9d87ddadd0272ce09842050063078da2 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sat, 10 Feb 2018 19:19:37 +0000
Subject: [PATCH 09/15] Track ship loadout

---
 monitor.py | 76 ++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 63 insertions(+), 13 deletions(-)

diff --git a/monitor.py b/monitor.py
index b60eca34..6fbc2115 100644
--- a/monitor.py
+++ b/monitor.py
@@ -113,7 +113,6 @@ 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 },
             'Role'         : None,	# Crew role - None, Idle, FireCon, FighterCon
             'Friends'      : set(),	# Online friends
@@ -121,6 +120,10 @@ class EDLogs(FileSystemEventHandler):
             'ShipIdent'    : None,
             'ShipName'     : None,
             'ShipType'     : None,
+            'HullValue'    : None,
+            'ModulesValue' : None,
+            'Rebuy'        : None,
+            'Modules'      : None,
         }
 
     def start(self, root):
@@ -319,7 +322,6 @@ 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 },
                     'Role'         : None,
                     'Friends'      : set(),
@@ -327,6 +329,10 @@ class EDLogs(FileSystemEventHandler):
                     '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
@@ -362,25 +368,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
@@ -447,15 +473,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:

From 88f323d36e1757cbd20d0ca6b877fac01f021ace Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sat, 10 Feb 2018 19:21:55 +0000
Subject: [PATCH 10/15] Switch EDShipyard import to Loadout event

---
 EDMarketConnector.py | 16 +++++++++-------
 edshipyard.py        |  7 +++++--
 monitor.py           | 19 +++++++++++++++++++
 plugins/edsm.py      | 13 +++++++++----
 4 files changed, 42 insertions(+), 13 deletions(-)

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index aed8b007..b3498fb8 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -709,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:
@@ -734,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:
diff --git a/edshipyard.py b/edshipyard.py
index 8274ab51..31a1b5b9 100644
--- a/edshipyard.py
+++ b/edshipyard.py
@@ -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:
diff --git a/monitor.py b/monitor.py
index 6fbc2115..179d07cc 100644
--- a/monitor.py
+++ b/monitor.py
@@ -625,5 +625,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()
diff --git a/plugins/edsm.py b/plugins/edsm.py
index 5cb0b824..05cfb004 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -34,6 +34,7 @@ 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
@@ -235,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):
@@ -262,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
 
 

From 2552c46d090bb9c3c92cb20104c835c1a4cb2b69 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sun, 18 Feb 2018 02:35:37 +0000
Subject: [PATCH 11/15] Track Reputation and Statistics

---
 monitor.py       | 28 +++++++++++++++++++++-------
 plugins/inara.py |  9 +++++++++
 2 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/monitor.py b/monitor.py
index 179d07cc..de435069 100644
--- a/monitor.py
+++ b/monitor.py
@@ -113,7 +113,9 @@ class EDLogs(FileSystemEventHandler):
             'Raw'          : defaultdict(int),
             'Manufactured' : defaultdict(int),
             'Encoded'      : defaultdict(int),
-            '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,
@@ -322,7 +324,9 @@ class EDLogs(FileSystemEventHandler):
                     'Raw'          : defaultdict(int),
                     'Manufactured' : defaultdict(int),
                     'Encoded'      : defaultdict(int),
-                    'Rank'         : { 'Combat': None, 'Trade': None, 'Explore': None, 'Empire': None, 'Federation': None, 'CQC': None },
+                    'Rank'         : {},
+                    'Reputation'   : {},
+                    'Statistics'   : {},
                     'Role'         : None,
                     'Friends'      : set(),
                     'ShipID'       : None,
@@ -351,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':
@@ -430,14 +436,22 @@ class EDLogs(FileSystemEventHandler):
                 self.planet = entry.get('Body') if entry.get('BodyType') == 'Planet' else None
             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.state['Cargo'] = defaultdict(int)
diff --git a/plugins/inara.py b/plugins/inara.py
index e5392f6b..8873254e 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -181,6 +181,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:

From 1de3edc4525948e1703ed02b458384a10c9f3fe0 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sun, 18 Feb 2018 02:36:32 +0000
Subject: [PATCH 12/15] MissionCompleted has MaterialsReward property

---
 monitor.py       | 5 +++++
 plugins/inara.py | 8 ++------
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/monitor.py b/monitor.py
index de435069..05fd338b 100644
--- a/monitor.py
+++ b/monitor.py
@@ -468,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'])
diff --git a/plugins/inara.py b/plugins/inara.py
index 8873254e..e1eda1f7 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -419,14 +419,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'] })
 

From 4aa80c8426ee0da2a024f8cee8047754e7f9e709 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sun, 18 Feb 2018 02:41:10 +0000
Subject: [PATCH 13/15] Get fleet and loadout from Journal

---
 plugins/inara.py | 167 +++++++++++++++++++++++++++++++----------------
 1 file changed, 110 insertions(+), 57 deletions(-)

diff --git a/plugins/inara.py b/plugins/inara.py
index e1eda1f7..03a47d30 100644
--- a/plugins/inara.py
+++ b/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
 
@@ -40,7 +41,9 @@ 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"
@@ -239,20 +244,26 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
             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
@@ -334,7 +345,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:
@@ -372,6 +391,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([
@@ -508,54 +600,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),

From 60ddc55d5e609d854f897a4605590b08a3660b2d Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sun, 18 Feb 2018 02:41:56 +0000
Subject: [PATCH 14/15] Send CommunityGoal TopTier info

---
 plugins/inara.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/plugins/inara.py b/plugins/inara.py
index 03a47d30..25293337 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -580,6 +580,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([

From 3a001666214a0a41a47a9e0fb4cad9daa5b801c4 Mon Sep 17 00:00:00 2001
From: Jonathan Harris <jonathan@marginal.org.uk>
Date: Sun, 18 Feb 2018 02:56:23 +0000
Subject: [PATCH 15/15] Send credits at startup

---
 plugins/inara.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/plugins/inara.py b/plugins/inara.py
index 25293337..3ee5d031 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -176,6 +176,15 @@ 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', 'Cargo'] or this.newuser):
                 for k,v in state['Rank'].iteritems():