eve-fit-engine 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -8
- package/dist/index.cjs +561 -226
- package/dist/index.js +561 -226
- package/dist/node.cjs +561 -226
- package/dist/node.js +561 -226
- package/package.json +4 -1
package/dist/node.cjs
CHANGED
|
@@ -677,7 +677,7 @@ var FitContext = class {
|
|
|
677
677
|
const root = this.resolveDomain(modifier.domain, source);
|
|
678
678
|
switch (modifier.func) {
|
|
679
679
|
case "ItemModifier":
|
|
680
|
-
if (modifier.domain === "charID" && source.kind === "module") {
|
|
680
|
+
if (modifier.domain === "charID" && source.kind === "module" && modifier.modifiedAttributeID === 212) {
|
|
681
681
|
const out = [];
|
|
682
682
|
for (const m of this.modules) {
|
|
683
683
|
if (m.charge) out.push(m.charge);
|
|
@@ -1089,6 +1089,299 @@ function makeModeState(modeTypeID, type) {
|
|
|
1089
1089
|
return new ItemState({ kind: "mode", id: `mode:${modeTypeID}`, type, state: "ACTIVE" });
|
|
1090
1090
|
}
|
|
1091
1091
|
|
|
1092
|
+
// src/stackingGroups.ts
|
|
1093
|
+
var STACKING_PENALTY_GROUPS = /* @__PURE__ */ new Map([
|
|
1094
|
+
[89, "default"],
|
|
1095
|
+
[91, "default"],
|
|
1096
|
+
[92, "default"],
|
|
1097
|
+
[93, "default"],
|
|
1098
|
+
[95, "default"],
|
|
1099
|
+
[96, "default"],
|
|
1100
|
+
[394, "default"],
|
|
1101
|
+
[395, "default"],
|
|
1102
|
+
[494, "default"],
|
|
1103
|
+
[607, "postMul"],
|
|
1104
|
+
[657, "default"],
|
|
1105
|
+
[699, "default"],
|
|
1106
|
+
[763, "default"],
|
|
1107
|
+
[784, "default"],
|
|
1108
|
+
[854, "cloakingScanResolutionMultiplier"],
|
|
1109
|
+
[856, "default"],
|
|
1110
|
+
[889, "default"],
|
|
1111
|
+
[891, "default"],
|
|
1112
|
+
[892, "default"],
|
|
1113
|
+
[1024, "default"],
|
|
1114
|
+
[1230, "default"],
|
|
1115
|
+
[1281, "default"],
|
|
1116
|
+
[1318, "default"],
|
|
1117
|
+
[1445, "default"],
|
|
1118
|
+
[1446, "default"],
|
|
1119
|
+
[1448, "default"],
|
|
1120
|
+
[1452, "default"],
|
|
1121
|
+
[1472, "default"],
|
|
1122
|
+
[1590, "default"],
|
|
1123
|
+
[1617, "default"],
|
|
1124
|
+
[1720, "default"],
|
|
1125
|
+
[1764, "default"],
|
|
1126
|
+
[1885, "postPerc"],
|
|
1127
|
+
[1886, "postPerc"],
|
|
1128
|
+
[2013, "default"],
|
|
1129
|
+
[2014, "default"],
|
|
1130
|
+
[2019, "default"],
|
|
1131
|
+
[2020, "default"],
|
|
1132
|
+
[2041, "default"],
|
|
1133
|
+
[2052, "default"],
|
|
1134
|
+
[2152, "default"],
|
|
1135
|
+
[2232, "default"],
|
|
1136
|
+
[2302, "preMul"],
|
|
1137
|
+
[2644, "default"],
|
|
1138
|
+
[2645, "default"],
|
|
1139
|
+
[2646, "default"],
|
|
1140
|
+
[2670, "default"],
|
|
1141
|
+
[2693, "default"],
|
|
1142
|
+
[2694, "default"],
|
|
1143
|
+
[2695, "default"],
|
|
1144
|
+
[2696, "default"],
|
|
1145
|
+
[2697, "default"],
|
|
1146
|
+
[2698, "default"],
|
|
1147
|
+
[2716, "default"],
|
|
1148
|
+
[2717, "default"],
|
|
1149
|
+
[2792, "default"],
|
|
1150
|
+
[2795, "default"],
|
|
1151
|
+
[2796, "default"],
|
|
1152
|
+
[2797, "default"],
|
|
1153
|
+
[2798, "default"],
|
|
1154
|
+
[2799, "default"],
|
|
1155
|
+
[2801, "default"],
|
|
1156
|
+
[2802, "default"],
|
|
1157
|
+
[2803, "default"],
|
|
1158
|
+
[2804, "default"],
|
|
1159
|
+
[2851, "default"],
|
|
1160
|
+
[2858, "default"],
|
|
1161
|
+
[2865, "default"],
|
|
1162
|
+
[2867, "default"],
|
|
1163
|
+
[2868, "default"],
|
|
1164
|
+
[3001, "postPerc"],
|
|
1165
|
+
[3046, "default"],
|
|
1166
|
+
[3174, "default"],
|
|
1167
|
+
[3175, "default"],
|
|
1168
|
+
[3182, "default"],
|
|
1169
|
+
[3200, "default"],
|
|
1170
|
+
[3201, "default"],
|
|
1171
|
+
[3586, "default"],
|
|
1172
|
+
[3655, "default"],
|
|
1173
|
+
[3656, "default"],
|
|
1174
|
+
[3657, "default"],
|
|
1175
|
+
[3659, "default"],
|
|
1176
|
+
[3674, "default"],
|
|
1177
|
+
[3726, "default"],
|
|
1178
|
+
[3727, "default"],
|
|
1179
|
+
[3993, "postMul"],
|
|
1180
|
+
[3995, "postMul"],
|
|
1181
|
+
[3996, "default"],
|
|
1182
|
+
[3997, "default"],
|
|
1183
|
+
[3998, "default"],
|
|
1184
|
+
[3999, "default"],
|
|
1185
|
+
[4002, "postMul"],
|
|
1186
|
+
[4003, "postMul"],
|
|
1187
|
+
[4016, "default"],
|
|
1188
|
+
[4017, "postMul"],
|
|
1189
|
+
[4018, "postMul"],
|
|
1190
|
+
[4019, "postMul"],
|
|
1191
|
+
[4020, "postMul"],
|
|
1192
|
+
[4021, "postMul"],
|
|
1193
|
+
[4022, "postMul"],
|
|
1194
|
+
[4023, "postMul"],
|
|
1195
|
+
[4054, "default"],
|
|
1196
|
+
[4055, "default"],
|
|
1197
|
+
[4056, "default"],
|
|
1198
|
+
[4057, "postMul"],
|
|
1199
|
+
[4058, "postMul"],
|
|
1200
|
+
[4059, "postMul"],
|
|
1201
|
+
[4060, "postMul"],
|
|
1202
|
+
[4061, "postMul"],
|
|
1203
|
+
[4062, "postMul"],
|
|
1204
|
+
[4063, "postMul"],
|
|
1205
|
+
[4086, "postMul"],
|
|
1206
|
+
[4088, "postMul"],
|
|
1207
|
+
[4089, "postMul"],
|
|
1208
|
+
[4135, "default"],
|
|
1209
|
+
[4136, "default"],
|
|
1210
|
+
[4137, "default"],
|
|
1211
|
+
[4138, "default"],
|
|
1212
|
+
[4162, "default"],
|
|
1213
|
+
[4280, "default"],
|
|
1214
|
+
[4358, "default"],
|
|
1215
|
+
[4464, "default"],
|
|
1216
|
+
[4489, "default"],
|
|
1217
|
+
[4490, "default"],
|
|
1218
|
+
[4491, "default"],
|
|
1219
|
+
[4492, "default"],
|
|
1220
|
+
[4527, "default"],
|
|
1221
|
+
[4559, "default"],
|
|
1222
|
+
[4575, "default"],
|
|
1223
|
+
[4809, "default"],
|
|
1224
|
+
[4810, "default"],
|
|
1225
|
+
[4811, "default"],
|
|
1226
|
+
[4812, "default"],
|
|
1227
|
+
[4906, "postMul"],
|
|
1228
|
+
[4928, "preMul"],
|
|
1229
|
+
[4961, "postMul"],
|
|
1230
|
+
[5081, "default"],
|
|
1231
|
+
[5188, "default"],
|
|
1232
|
+
[5189, "default"],
|
|
1233
|
+
[5190, "default"],
|
|
1234
|
+
[5213, "default"],
|
|
1235
|
+
[5214, "default"],
|
|
1236
|
+
[5230, "default"],
|
|
1237
|
+
[5231, "default"],
|
|
1238
|
+
[5397, "default"],
|
|
1239
|
+
[5399, "default"],
|
|
1240
|
+
[5440, "postMul"],
|
|
1241
|
+
[5468, "default"],
|
|
1242
|
+
[5560, "default"],
|
|
1243
|
+
[5618, "postPerc"],
|
|
1244
|
+
[5757, "default"],
|
|
1245
|
+
[5867, "default"],
|
|
1246
|
+
[5911, "default"],
|
|
1247
|
+
[5912, "postMul"],
|
|
1248
|
+
[5914, "postMul"],
|
|
1249
|
+
[5915, "postMul"],
|
|
1250
|
+
[5916, "postMul"],
|
|
1251
|
+
[5917, "postMul"],
|
|
1252
|
+
[5918, "postMul"],
|
|
1253
|
+
[5919, "postMul"],
|
|
1254
|
+
[5920, "postMul"],
|
|
1255
|
+
[5921, "postMul"],
|
|
1256
|
+
[5922, "postMul"],
|
|
1257
|
+
[5923, "postMul"],
|
|
1258
|
+
[5924, "postMul"],
|
|
1259
|
+
[5925, "postMul"],
|
|
1260
|
+
[5926, "postMul"],
|
|
1261
|
+
[5927, "postMul"],
|
|
1262
|
+
[5929, "postMul"],
|
|
1263
|
+
[5951, "default"],
|
|
1264
|
+
[5998, "default"],
|
|
1265
|
+
[6010, "postDiv"],
|
|
1266
|
+
[6011, "postDiv"],
|
|
1267
|
+
[6012, "postDiv"],
|
|
1268
|
+
[6014, "postDiv"],
|
|
1269
|
+
[6015, "postDiv"],
|
|
1270
|
+
[6016, "postDiv"],
|
|
1271
|
+
[6017, "postDiv"],
|
|
1272
|
+
[6039, "postDiv"],
|
|
1273
|
+
[6040, "postDiv"],
|
|
1274
|
+
[6041, "postDiv"],
|
|
1275
|
+
[6063, "default"],
|
|
1276
|
+
[6076, "postDiv"],
|
|
1277
|
+
[6110, "default"],
|
|
1278
|
+
[6111, "default"],
|
|
1279
|
+
[6112, "default"],
|
|
1280
|
+
[6113, "default"],
|
|
1281
|
+
[6135, "default"],
|
|
1282
|
+
[6152, "postDiv"],
|
|
1283
|
+
[6154, "postDiv"],
|
|
1284
|
+
[6164, "default"],
|
|
1285
|
+
[6201, "default"],
|
|
1286
|
+
[6208, "default"],
|
|
1287
|
+
[6402, "default"],
|
|
1288
|
+
[6403, "default"],
|
|
1289
|
+
[6404, "default"],
|
|
1290
|
+
[6405, "default"],
|
|
1291
|
+
[6406, "default"],
|
|
1292
|
+
[6409, "default"],
|
|
1293
|
+
[6410, "default"],
|
|
1294
|
+
[6411, "default"],
|
|
1295
|
+
[6412, "default"],
|
|
1296
|
+
[6422, "default"],
|
|
1297
|
+
[6423, "default"],
|
|
1298
|
+
[6424, "default"],
|
|
1299
|
+
[6425, "default"],
|
|
1300
|
+
[6426, "default"],
|
|
1301
|
+
[6427, "default"],
|
|
1302
|
+
[6428, "default"],
|
|
1303
|
+
[6435, "default"],
|
|
1304
|
+
[6439, "default"],
|
|
1305
|
+
[6440, "default"],
|
|
1306
|
+
[6441, "default"],
|
|
1307
|
+
[6448, "default"],
|
|
1308
|
+
[6449, "default"],
|
|
1309
|
+
[6472, "default"],
|
|
1310
|
+
[6473, "default"],
|
|
1311
|
+
[6474, "default"],
|
|
1312
|
+
[6476, "default"],
|
|
1313
|
+
[6478, "default"],
|
|
1314
|
+
[6479, "default"],
|
|
1315
|
+
[6481, "default"],
|
|
1316
|
+
[6484, "postMul"],
|
|
1317
|
+
[6487, "default"],
|
|
1318
|
+
[6555, "default"],
|
|
1319
|
+
[6556, "default"],
|
|
1320
|
+
[6557, "default"],
|
|
1321
|
+
[6559, "default"],
|
|
1322
|
+
[6566, "postMul"],
|
|
1323
|
+
[6567, "default"],
|
|
1324
|
+
[6581, "default"],
|
|
1325
|
+
[6582, "postPercent"],
|
|
1326
|
+
[6658, "preMul"],
|
|
1327
|
+
[6670, "default"],
|
|
1328
|
+
[6671, "default"],
|
|
1329
|
+
[6682, "default"],
|
|
1330
|
+
[6683, "default"],
|
|
1331
|
+
[6684, "default"],
|
|
1332
|
+
[6686, "default"],
|
|
1333
|
+
[6690, "default"],
|
|
1334
|
+
[6692, "default"],
|
|
1335
|
+
[6693, "default"],
|
|
1336
|
+
[6694, "default"],
|
|
1337
|
+
[6727, "default"],
|
|
1338
|
+
[6730, "postMul"],
|
|
1339
|
+
[6731, "postMul"],
|
|
1340
|
+
[6796, "postDiv"],
|
|
1341
|
+
[6797, "postDiv"],
|
|
1342
|
+
[6798, "postDiv"],
|
|
1343
|
+
[6799, "postDiv"],
|
|
1344
|
+
[6801, "postDiv"],
|
|
1345
|
+
[6877, "default"],
|
|
1346
|
+
[7029, "default"],
|
|
1347
|
+
[7077, "default"],
|
|
1348
|
+
[7078, "default"],
|
|
1349
|
+
[7098, "default"],
|
|
1350
|
+
[7111, "default"],
|
|
1351
|
+
[7142, "default"],
|
|
1352
|
+
[7202, "default"],
|
|
1353
|
+
[7203, "default"],
|
|
1354
|
+
[7223, "default"],
|
|
1355
|
+
[7237, "default"],
|
|
1356
|
+
[8033, "default"],
|
|
1357
|
+
[8057, "default"],
|
|
1358
|
+
[8076, "default"],
|
|
1359
|
+
[8082, "default"],
|
|
1360
|
+
[8108, "postMul"],
|
|
1361
|
+
[8109, "postMul"],
|
|
1362
|
+
[8111, "default"],
|
|
1363
|
+
[8112, "default"],
|
|
1364
|
+
[8113, "postMul"],
|
|
1365
|
+
[8114, "postMul"],
|
|
1366
|
+
[8119, "default"],
|
|
1367
|
+
[11445, "default"],
|
|
1368
|
+
[11691, "default"],
|
|
1369
|
+
[11946, "default"],
|
|
1370
|
+
[11947, "postMul"],
|
|
1371
|
+
[11948, "postMul"],
|
|
1372
|
+
[11953, "postMul"],
|
|
1373
|
+
[12126, "default"],
|
|
1374
|
+
[12597, "default"],
|
|
1375
|
+
[12761, "default"],
|
|
1376
|
+
[12794, "postDiv"],
|
|
1377
|
+
[12795, "postDiv"],
|
|
1378
|
+
[12796, "postDiv"],
|
|
1379
|
+
[12798, "postDiv"],
|
|
1380
|
+
[12799, "postDiv"],
|
|
1381
|
+
[12838, "postMul"],
|
|
1382
|
+
[12839, "postMul"]
|
|
1383
|
+
]);
|
|
1384
|
+
|
|
1092
1385
|
// src/modifierEngine.ts
|
|
1093
1386
|
var NO_PENALTY_KINDS = /* @__PURE__ */ new Set([
|
|
1094
1387
|
"skill",
|
|
@@ -1103,16 +1396,21 @@ function scaleForPipeline(rawValue, _unitID, op) {
|
|
|
1103
1396
|
}
|
|
1104
1397
|
function applySourceItem(source, ctx, dataset) {
|
|
1105
1398
|
const isLocalModule = source.kind === "module";
|
|
1399
|
+
const selfMods = [];
|
|
1400
|
+
const outMods = [];
|
|
1106
1401
|
for (const eid of source.effectIDs) {
|
|
1107
1402
|
if (LEGACY_HANDLED_EFFECT_IDS.has(eid)) continue;
|
|
1403
|
+
if (SEC_STATUS_SCALED_EFFECT_IDS.has(eid)) continue;
|
|
1108
1404
|
if (isLocalModule && ctx.stoppedLocalEffectIDs.has(eid)) continue;
|
|
1109
1405
|
const effect = dataset.effects.get(eid);
|
|
1110
1406
|
if (!effect) continue;
|
|
1111
1407
|
if (!source.appliesAtState(effect)) continue;
|
|
1112
1408
|
for (const mi of effect.modifierInfo) {
|
|
1113
|
-
|
|
1409
|
+
(mi.domain === "itemID" ? selfMods : outMods).push({ effect, mi });
|
|
1114
1410
|
}
|
|
1115
1411
|
}
|
|
1412
|
+
for (const { effect, mi } of selfMods) applyOneModifier(source, effect, mi, ctx, dataset);
|
|
1413
|
+
for (const { effect, mi } of outMods) applyOneModifier(source, effect, mi, ctx, dataset);
|
|
1116
1414
|
}
|
|
1117
1415
|
function collectEffectStoppers(projectedSources, dataset) {
|
|
1118
1416
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -1178,7 +1476,7 @@ function applyOneModifier(source, effect, mi, ctx, dataset) {
|
|
|
1178
1476
|
if (computed === null) return;
|
|
1179
1477
|
const targets = ctx.targetsForModifier(mi, source);
|
|
1180
1478
|
if (targets.length === 0) return;
|
|
1181
|
-
const stackingGroup = computeStackingGroup(source, mi, dataset);
|
|
1479
|
+
const stackingGroup = computeStackingGroup(source, mi, dataset, effect);
|
|
1182
1480
|
const isMul = op === "PreMul" || op === "PostMul" || op === "PreDiv" || op === "PostDiv";
|
|
1183
1481
|
const value = isMul && computed.scaled ? 1 + computed.value : computed.value;
|
|
1184
1482
|
const sdeDefault = isMul ? dataset.attributes.get(mi.modifiedAttributeID)?.defaultValue ?? 0 : 0;
|
|
@@ -1686,7 +1984,52 @@ var SHIP_BONUS_SCALING_SKILL = /* @__PURE__ */ new Map([
|
|
|
1686
1984
|
[6112, 24313],
|
|
1687
1985
|
[6113, 24312],
|
|
1688
1986
|
[6114, 24311],
|
|
1689
|
-
[6116, 24314]
|
|
1987
|
+
[6116, 24314],
|
|
1988
|
+
// ----- Auto-derived from the SDE: attrs that are BOTH skill-level-scaled
|
|
1989
|
+
// (a skill effect does `attr ×= skillLevel` via attr 280) AND read by a
|
|
1990
|
+
// SHIP-side effect as the bonus value. These are per-racial/role-skill hull
|
|
1991
|
+
// bonuses whose ship-side reader must scale by the skill level. Previously
|
|
1992
|
+
// missing → the bonus was taken at base (×1) instead of ×5 at All-V.
|
|
1993
|
+
// Notable: Exhumer/Barge shield+armor resist role bonuses (Hulk/Skiff/
|
|
1994
|
+
// Mackinaw shield resist 4 %→20 %), Bhaalgorn drone+laser (492), industrial
|
|
1995
|
+
// command (Orca/Rorqual), Marauder/pirate/expedition hull bonuses.
|
|
1996
|
+
[66, 89611],
|
|
1997
|
+
[310, 3432],
|
|
1998
|
+
[349, 19760],
|
|
1999
|
+
[492, 3339],
|
|
2000
|
+
[1296, 21610],
|
|
2001
|
+
[1669, 3184],
|
|
2002
|
+
[1670, 3184],
|
|
2003
|
+
[1842, 32918],
|
|
2004
|
+
[3167, 33856],
|
|
2005
|
+
[3181, 17940],
|
|
2006
|
+
[3182, 17940],
|
|
2007
|
+
[3183, 17940],
|
|
2008
|
+
[3184, 17940],
|
|
2009
|
+
[3185, 17940],
|
|
2010
|
+
[3187, 17940],
|
|
2011
|
+
[3188, 17940],
|
|
2012
|
+
[3190, 33856],
|
|
2013
|
+
[3191, 33856],
|
|
2014
|
+
[3192, 33856],
|
|
2015
|
+
[3193, 22551],
|
|
2016
|
+
[3194, 22551],
|
|
2017
|
+
[3197, 22551],
|
|
2018
|
+
[3198, 22551],
|
|
2019
|
+
[3199, 22551],
|
|
2020
|
+
[3203, 29637],
|
|
2021
|
+
[3204, 29637],
|
|
2022
|
+
[3205, 29637],
|
|
2023
|
+
[3210, 3341],
|
|
2024
|
+
[3221, 29637],
|
|
2025
|
+
[3222, 29637],
|
|
2026
|
+
[3223, 28374],
|
|
2027
|
+
[3224, 28374],
|
|
2028
|
+
[3237, 32918],
|
|
2029
|
+
[3240, 32918],
|
|
2030
|
+
[3326, 28374],
|
|
2031
|
+
[6088, 33092],
|
|
2032
|
+
[6089, 33094]
|
|
1690
2033
|
]);
|
|
1691
2034
|
var SUBSYSTEM_BONUS_SCALING_SKILL = /* @__PURE__ */ new Map([
|
|
1692
2035
|
// Amarr
|
|
@@ -1816,7 +2159,7 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
1816
2159
|
if (skillID === void 0) return null;
|
|
1817
2160
|
const level = ctx.skillLevel(skillID);
|
|
1818
2161
|
if (level === 0) return null;
|
|
1819
|
-
if (source.kind === "ship"
|
|
2162
|
+
if (source.kind === "ship") {
|
|
1820
2163
|
return { value: baseValue, scaled: false };
|
|
1821
2164
|
}
|
|
1822
2165
|
if (itemRequiresSkill(source, skillID)) {
|
|
@@ -1826,55 +2169,21 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
1826
2169
|
}
|
|
1827
2170
|
return { value: baseValue, scaled: false };
|
|
1828
2171
|
}
|
|
1829
|
-
|
|
1830
|
-
793,
|
|
1831
|
-
// shipBonusRole7
|
|
1832
|
-
1688,
|
|
1833
|
-
// shipBonusRole8
|
|
1834
|
-
1803,
|
|
1835
|
-
// MWDSignatureRadiusBonus — Assault Frigate / Interceptor MWD sig role bonus (flat)
|
|
1836
|
-
2059,
|
|
1837
|
-
// eliteBonusCommandDestroyer1 — Command Destroyer T2 specialisation (flat per-level applied via skill, but the SHIP-side reader is flat)
|
|
1838
|
-
2060,
|
|
1839
|
-
// eliteBonusCommandDestroyer2
|
|
1840
|
-
2064,
|
|
1841
|
-
// roleBonusCD — Command Destroyer command burst PG / activation cost reduction.
|
|
1842
|
-
// Without this entry, effect 6214 (Draugur's `roleBonusCDLinksPGReduction`)
|
|
1843
|
-
// double-applies the -95 % bonus at × Skirmish Command Burst V → -475 %
|
|
1844
|
-
// PostPercent on Skirmish Command Burst II `power` (110) → -412.5 MW per
|
|
1845
|
-
// burst → total ship power used reads −738 MW instead of +97 MW.
|
|
1846
|
-
2298,
|
|
1847
|
-
// shipBonusRole1
|
|
1848
|
-
2299,
|
|
1849
|
-
// shipBonusRole2
|
|
1850
|
-
2300,
|
|
1851
|
-
// shipBonusRole3
|
|
1852
|
-
2301,
|
|
1853
|
-
// shipBonusRole4
|
|
1854
|
-
2302,
|
|
1855
|
-
// shipBonusRole5
|
|
1856
|
-
2303,
|
|
1857
|
-
// shipBonusRole6
|
|
1858
|
-
5952,
|
|
1859
|
-
// shipBonusGasCloudDurationRoleBonusOreMiningDestroyer
|
|
1860
|
-
1989
|
|
1861
|
-
// probeLauncherCPUPercentRoleBonusT3 value — effect 6009 on T3C hulls
|
|
1862
|
-
// (Loki/Tengu/…): "-99 % CPU for Scan Probe Launchers". Declared as
|
|
1863
|
-
// LocationRequiredSkillModifier gated on Astrometrics (3412), but
|
|
1864
|
-
// the skill only SELECTS the recipient (probe launchers) — the bonus
|
|
1865
|
-
// is FLAT. Without this entry the ship-domain reader scales it ×
|
|
1866
|
-
// Astrometrics level → -99 % becomes -495 % PostPercent → a Loki
|
|
1867
|
-
// Expanded Probe Launcher's 242 tf CPU flips to -955.9 tf and total
|
|
1868
|
-
// CPU used reads -388 instead of +569.5.
|
|
1869
|
-
]);
|
|
1870
|
-
function computeStackingGroup(source, mi, dataset) {
|
|
2172
|
+
function computeStackingGroup(source, mi, dataset, effect) {
|
|
1871
2173
|
if (NO_PENALTY_KINDS.has(source.kind)) return null;
|
|
1872
2174
|
if (mi.modifiedAttributeID !== void 0) {
|
|
1873
2175
|
const attr = dataset.attributes.get(mi.modifiedAttributeID);
|
|
1874
2176
|
if (attr?.stackable) return null;
|
|
1875
2177
|
}
|
|
2178
|
+
const group = STACKING_PENALTY_GROUPS.get(effect.id);
|
|
2179
|
+
if (group !== void 0 && group !== "default" && CUSTOM_STACK_GROUPS_HONOURED.has(group)) {
|
|
2180
|
+
return `${group}:${mi.modifiedAttributeID}`;
|
|
2181
|
+
}
|
|
1876
2182
|
return `attr:${mi.modifiedAttributeID}`;
|
|
1877
2183
|
}
|
|
2184
|
+
var CUSTOM_STACK_GROUPS_HONOURED = /* @__PURE__ */ new Set([
|
|
2185
|
+
"cloakingScanResolutionMultiplier"
|
|
2186
|
+
]);
|
|
1878
2187
|
function mapSourceKind(kind) {
|
|
1879
2188
|
switch (kind) {
|
|
1880
2189
|
case "ship":
|
|
@@ -3592,6 +3901,13 @@ var LEGACY_HANDLED_HARDCODED_EFFECTS = /* @__PURE__ */ new Set([
|
|
|
3592
3901
|
6658
|
|
3593
3902
|
// Bastion Module — applyLegacyBastion
|
|
3594
3903
|
]);
|
|
3904
|
+
var SEC_STATUS_SCALED_EFFECT_IDS = /* @__PURE__ */ new Set([
|
|
3905
|
+
6871,
|
|
3906
|
+
12165,
|
|
3907
|
+
12181,
|
|
3908
|
+
12185,
|
|
3909
|
+
12202
|
|
3910
|
+
]);
|
|
3595
3911
|
var LEGACY_HANDLED_EFFECT_IDS = /* @__PURE__ */ new Set([
|
|
3596
3912
|
...LEGACY_HANDLED_PASSIVE_ADD_EFFECTS,
|
|
3597
3913
|
...LEGACY_HANDLED_HARDCODED_EFFECTS
|
|
@@ -3662,6 +3978,176 @@ function computeTotalEhp(shield, armor, hull, useProfile) {
|
|
|
3662
3978
|
return Number.isFinite(total) ? total : Number.MAX_SAFE_INTEGER;
|
|
3663
3979
|
}
|
|
3664
3980
|
|
|
3981
|
+
// src/fitChecks.ts
|
|
3982
|
+
function readAttr(t, id) {
|
|
3983
|
+
return t.attributes.find((a) => a.id === id)?.v;
|
|
3984
|
+
}
|
|
3985
|
+
function typeFitsSlotType(t, slot) {
|
|
3986
|
+
for (const e of t.effects) {
|
|
3987
|
+
const mapped = SLOT_EFFECT_TO_SLOT_TYPE[e.id];
|
|
3988
|
+
if (mapped === slot) return true;
|
|
3989
|
+
}
|
|
3990
|
+
return false;
|
|
3991
|
+
}
|
|
3992
|
+
function isTurretWeapon(t) {
|
|
3993
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "TURRET");
|
|
3994
|
+
}
|
|
3995
|
+
function isMissileLauncher(t) {
|
|
3996
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "MISSILE");
|
|
3997
|
+
}
|
|
3998
|
+
function isSmartBomb(t) {
|
|
3999
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "SMARTBOMB");
|
|
4000
|
+
}
|
|
4001
|
+
function shipGroupRestrictions(t) {
|
|
4002
|
+
const out = [];
|
|
4003
|
+
for (const id of CAN_FIT_SHIP_GROUP_ATTRS) {
|
|
4004
|
+
const v = readAttr(t, id);
|
|
4005
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
4006
|
+
}
|
|
4007
|
+
return out;
|
|
4008
|
+
}
|
|
4009
|
+
function shipTypeRestrictions(t) {
|
|
4010
|
+
const out = [];
|
|
4011
|
+
for (const id of CAN_FIT_SHIP_TYPE_ATTRS) {
|
|
4012
|
+
const v = readAttr(t, id);
|
|
4013
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
4014
|
+
}
|
|
4015
|
+
return out;
|
|
4016
|
+
}
|
|
4017
|
+
function maxGroupFittedFor(mod) {
|
|
4018
|
+
const v = readAttr(mod, ATTR.MAX_GROUP_FITTED);
|
|
4019
|
+
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
4020
|
+
}
|
|
4021
|
+
function maxTypeFittedFor(mod) {
|
|
4022
|
+
const v = readAttr(mod, ATTR.MAX_TYPE_FITTED);
|
|
4023
|
+
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
4024
|
+
}
|
|
4025
|
+
function freeFitGroupSlotsFor(mod, fittedModules, dataset) {
|
|
4026
|
+
const groupCap = maxGroupFittedFor(mod);
|
|
4027
|
+
const typeCap = maxTypeFittedFor(mod);
|
|
4028
|
+
if (groupCap === void 0 && typeCap === void 0) return Number.POSITIVE_INFINITY;
|
|
4029
|
+
let groupCount = 0;
|
|
4030
|
+
let typeCount = 0;
|
|
4031
|
+
for (const fm of fittedModules) {
|
|
4032
|
+
if (typeCap !== void 0 && fm.typeID === mod.id) typeCount++;
|
|
4033
|
+
if (groupCap !== void 0) {
|
|
4034
|
+
const t = dataset.getType(fm.typeID);
|
|
4035
|
+
if (t && t.groupID === mod.groupID) groupCount++;
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
let free = Number.POSITIVE_INFINITY;
|
|
4039
|
+
if (groupCap !== void 0) free = Math.min(free, groupCap - groupCount);
|
|
4040
|
+
if (typeCap !== void 0) free = Math.min(free, typeCap - typeCount);
|
|
4041
|
+
return Math.max(0, free);
|
|
4042
|
+
}
|
|
4043
|
+
function canFitModuleOnShip(mod, ship, slot, fitContext) {
|
|
4044
|
+
if (!typeFitsSlotType(mod, slot)) {
|
|
4045
|
+
return { ok: false, reason: `Module does not declare a ${slot} slot effect` };
|
|
4046
|
+
}
|
|
4047
|
+
if (!ship) return { ok: true };
|
|
4048
|
+
const allowedGroups = shipGroupRestrictions(mod);
|
|
4049
|
+
const allowedTypes = shipTypeRestrictions(mod);
|
|
4050
|
+
if (allowedGroups.length > 0 || allowedTypes.length > 0) {
|
|
4051
|
+
const groupOk = allowedGroups.includes(ship.groupID);
|
|
4052
|
+
const typeOk = allowedTypes.includes(ship.id);
|
|
4053
|
+
if (!groupOk && !typeOk) {
|
|
4054
|
+
return { ok: false, reason: "Ship hull/group not allowed by this module" };
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
if (slot === "RIG") {
|
|
4058
|
+
const modSize = readAttr(mod, ATTR.RIG_SIZE);
|
|
4059
|
+
const shipSize = readAttr(ship, ATTR.RIG_SIZE);
|
|
4060
|
+
if (modSize != null && shipSize != null && Math.round(modSize) !== Math.round(shipSize)) {
|
|
4061
|
+
return { ok: false, reason: "Rig size mismatch" };
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
if (slot === "SUBSYSTEM") {
|
|
4065
|
+
const fitsTo = readAttr(mod, 1380);
|
|
4066
|
+
if (fitsTo != null && fitsTo > 0 && Math.round(fitsTo) !== ship.id) {
|
|
4067
|
+
return { ok: false, reason: "Subsystem locked to a different T3C hull" };
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
if (slot === "HI") {
|
|
4071
|
+
if (isTurretWeapon(mod)) {
|
|
4072
|
+
const turrets = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
4073
|
+
if (turrets <= 0) {
|
|
4074
|
+
return { ok: false, reason: "Ship has no turret hardpoints" };
|
|
4075
|
+
}
|
|
4076
|
+
} else if (isMissileLauncher(mod)) {
|
|
4077
|
+
const launchers = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
4078
|
+
if (launchers <= 0) {
|
|
4079
|
+
return { ok: false, reason: "Ship has no launcher hardpoints" };
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
if (fitContext) {
|
|
4084
|
+
const free = freeFitGroupSlotsFor(mod, fitContext.fittedModules, fitContext.dataset);
|
|
4085
|
+
if (free <= 0) {
|
|
4086
|
+
const cap = maxTypeFittedFor(mod) ?? maxGroupFittedFor(mod);
|
|
4087
|
+
return { ok: false, reason: `Only ${cap} of this module type can be fitted to a ship` };
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
return { ok: true };
|
|
4091
|
+
}
|
|
4092
|
+
function chargeGroupsForModule(mod) {
|
|
4093
|
+
const out = [];
|
|
4094
|
+
for (const attrID of CHARGE_GROUP_ATTRS) {
|
|
4095
|
+
const v = readAttr(mod, attrID);
|
|
4096
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
4097
|
+
}
|
|
4098
|
+
return out;
|
|
4099
|
+
}
|
|
4100
|
+
function moduleAcceptsAnyCharge(mod) {
|
|
4101
|
+
return chargeGroupsForModule(mod).length > 0;
|
|
4102
|
+
}
|
|
4103
|
+
function moduleAcceptsChargeType(mod, charge) {
|
|
4104
|
+
const allowedGroups = chargeGroupsForModule(mod);
|
|
4105
|
+
if (allowedGroups.length === 0) return false;
|
|
4106
|
+
if (!allowedGroups.includes(charge.groupID)) return false;
|
|
4107
|
+
const modSize = readAttr(mod, ATTR.CHARGE_SIZE);
|
|
4108
|
+
const chargeSize = readAttr(charge, ATTR.CHARGE_SIZE);
|
|
4109
|
+
if (modSize != null && chargeSize != null && Math.round(chargeSize) > Math.round(modSize)) {
|
|
4110
|
+
return false;
|
|
4111
|
+
}
|
|
4112
|
+
return true;
|
|
4113
|
+
}
|
|
4114
|
+
var ACTIVE_EFFECT_CATEGORIES = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
4115
|
+
function isActivatableModule(mod, effects) {
|
|
4116
|
+
for (const e of mod.effects) {
|
|
4117
|
+
const eff = effects.get(e.id);
|
|
4118
|
+
if (eff && eff.effectCategoryID !== void 0 && ACTIVE_EFFECT_CATEGORIES.has(eff.effectCategoryID)) {
|
|
4119
|
+
return true;
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
return false;
|
|
4123
|
+
}
|
|
4124
|
+
var DEFAULT_OFFLINE_ACTIVATION_GROUPS = /* @__PURE__ */ new Set([
|
|
4125
|
+
330,
|
|
4126
|
+
4117
|
|
4127
|
+
]);
|
|
4128
|
+
function defaultStateForModule(mod, effects) {
|
|
4129
|
+
if (DEFAULT_OFFLINE_ACTIVATION_GROUPS.has(mod.groupID)) return "ONLINE";
|
|
4130
|
+
return isActivatableModule(mod, effects) ? "ACTIVE" : "ONLINE";
|
|
4131
|
+
}
|
|
4132
|
+
function freeHardpointsFor(mod, ship, fittedHiModules, dataset) {
|
|
4133
|
+
const turretCap = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
4134
|
+
const launcherCap = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
4135
|
+
let turretUsed = 0;
|
|
4136
|
+
let launcherUsed = 0;
|
|
4137
|
+
for (const m of fittedHiModules) {
|
|
4138
|
+
const t = dataset.getType(m.typeID);
|
|
4139
|
+
if (!t) continue;
|
|
4140
|
+
if (isTurretWeapon(t)) turretUsed++;
|
|
4141
|
+
else if (isMissileLauncher(t)) launcherUsed++;
|
|
4142
|
+
}
|
|
4143
|
+
let hardpointFree;
|
|
4144
|
+
if (isTurretWeapon(mod)) hardpointFree = Math.max(0, turretCap - turretUsed);
|
|
4145
|
+
else if (isMissileLauncher(mod)) hardpointFree = Math.max(0, launcherCap - launcherUsed);
|
|
4146
|
+
else hardpointFree = Number.POSITIVE_INFINITY;
|
|
4147
|
+
const groupFree = freeFitGroupSlotsFor(mod, fittedHiModules, dataset);
|
|
4148
|
+
return Math.min(hardpointFree, groupFree);
|
|
4149
|
+
}
|
|
4150
|
+
|
|
3665
4151
|
// src/derived/capacitor.ts
|
|
3666
4152
|
function computeCapacitor(ctx, dataset) {
|
|
3667
4153
|
const ship = ctx.ship;
|
|
@@ -3741,6 +4227,7 @@ var CAP_BOOSTER_EFFECT_ID = 48;
|
|
|
3741
4227
|
var ATTR_CAPACITOR_BONUS = 67;
|
|
3742
4228
|
var ATTR_CAPACITOR_NEED2 = 6;
|
|
3743
4229
|
var ATTR_DURATION_MS2 = 73;
|
|
4230
|
+
var ATTR_REACTIVATION_MS = 669;
|
|
3744
4231
|
var ATTR_RELOAD_TIME = 1795;
|
|
3745
4232
|
var ATTR_CAPACITY = 38;
|
|
3746
4233
|
var ATTR_VOLUME = 161;
|
|
@@ -3775,8 +4262,9 @@ function drainEntryFromEffect(effect, mod) {
|
|
|
3775
4262
|
if (effect.dischargeAttributeID === void 0) return null;
|
|
3776
4263
|
const discharge = mod.getFinal(effect.dischargeAttributeID, 0);
|
|
3777
4264
|
if (discharge <= 0) return null;
|
|
3778
|
-
const
|
|
3779
|
-
if (
|
|
4265
|
+
const baseCycleMs = effect.durationAttributeID !== void 0 ? mod.getFinal(effect.durationAttributeID, 0) : 1e3;
|
|
4266
|
+
if (baseCycleMs <= 0) return null;
|
|
4267
|
+
const cycleMs = Math.round(baseCycleMs + mod.getFinal(ATTR_REACTIVATION_MS, 0));
|
|
3780
4268
|
return {
|
|
3781
4269
|
cycleMs,
|
|
3782
4270
|
capNeed: discharge,
|
|
@@ -3784,7 +4272,12 @@ function drainEntryFromEffect(effect, mod) {
|
|
|
3784
4272
|
// ammo doesn't reload for cap purposes on regular modules
|
|
3785
4273
|
reloadMs: 0,
|
|
3786
4274
|
isInjector: false,
|
|
3787
|
-
|
|
4275
|
+
// Pyfa: `disableStagger = mod.hardpoint == TURRET`. Turrets fire as a
|
|
4276
|
+
// synchronized volley (their cap drains aggregate, capNeed × N at the
|
|
4277
|
+
// shared cycle); everything else is staggered evenly across its cycle.
|
|
4278
|
+
// Without this, N turrets were staggered (one drain at cycle/N) instead
|
|
4279
|
+
// of N together, shifting the cap timeline (time-to-empty off ~5-10 %).
|
|
4280
|
+
disableStagger: isTurretWeapon(mod.type)
|
|
3788
4281
|
};
|
|
3789
4282
|
}
|
|
3790
4283
|
function boosterDrainEntry(mod) {
|
|
@@ -3792,19 +4285,19 @@ function boosterDrainEntry(mod) {
|
|
|
3792
4285
|
if (!charge) return null;
|
|
3793
4286
|
const capNeed = mod.getFinal(ATTR_CAPACITOR_NEED2, 0);
|
|
3794
4287
|
const inject = charge.getFinal(ATTR_CAPACITOR_BONUS, 0);
|
|
3795
|
-
const cycleMs = mod.getFinal(ATTR_DURATION_MS2, 0);
|
|
4288
|
+
const cycleMs = Math.round(mod.getFinal(ATTR_DURATION_MS2, 0));
|
|
3796
4289
|
if (cycleMs <= 0) return null;
|
|
3797
|
-
let reloadMs = mod.getFinal(ATTR_RELOAD_TIME, 0);
|
|
4290
|
+
let reloadMs = Math.round(mod.getFinal(ATTR_RELOAD_TIME, 0));
|
|
3798
4291
|
if (reloadMs <= 0) reloadMs = 1e4;
|
|
3799
4292
|
const capacity = mod.getFinal(ATTR_CAPACITY, 0);
|
|
3800
4293
|
const chargeVol = charge.getFinal(ATTR_VOLUME, 0);
|
|
3801
|
-
const charges = capacity > 0 && chargeVol > 0 ? Math.floor(capacity / chargeVol) :
|
|
4294
|
+
const charges = capacity > 0 && chargeVol > 0 ? Math.floor(capacity / chargeVol) : 0;
|
|
3802
4295
|
return {
|
|
3803
4296
|
cycleMs,
|
|
3804
4297
|
// Pyfa convention: positive capNeed = drain, negative = injection.
|
|
3805
4298
|
// For boosters the *net* per-cycle change is (capNeed_module - inject_charge).
|
|
3806
4299
|
capNeed: capNeed - inject,
|
|
3807
|
-
clipSize:
|
|
4300
|
+
clipSize: charges,
|
|
3808
4301
|
reloadMs,
|
|
3809
4302
|
isInjector: true,
|
|
3810
4303
|
disableStagger: false
|
|
@@ -4736,8 +5229,8 @@ function computeFit(fit, dataset, opts) {
|
|
|
4736
5229
|
}
|
|
4737
5230
|
ctx.stoppedLocalEffectIDs = collectEffectStoppers(earlyProjected, dataset);
|
|
4738
5231
|
}
|
|
4739
|
-
for (const m of modules) applySourceItem(m, ctx, dataset);
|
|
4740
5232
|
for (const m of modules) if (m.charge) applySourceItem(m.charge, ctx, dataset);
|
|
5233
|
+
for (const m of modules) applySourceItem(m, ctx, dataset);
|
|
4741
5234
|
for (const d of drones) applySourceItem(d, ctx, dataset);
|
|
4742
5235
|
for (const f of fighters) applySourceItem(f, ctx, dataset);
|
|
4743
5236
|
for (const i of implants) applySourceItem(i, ctx, dataset);
|
|
@@ -4900,6 +5393,7 @@ function buildProjectionReport(source, ctx) {
|
|
|
4900
5393
|
summary: labels[cls.kind] ?? cls.kind
|
|
4901
5394
|
};
|
|
4902
5395
|
}
|
|
5396
|
+
var CHAR_BASE_MAX_LOCKED_TARGETS = 2;
|
|
4903
5397
|
function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
4904
5398
|
const ship = ctx.ship;
|
|
4905
5399
|
const slotUsed = { HI: 0, MED: 0, LO: 0, RIG: 0, SUBSYSTEM: 0, SERVICE: 0 };
|
|
@@ -5020,7 +5514,18 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
5020
5514
|
ship.getFinal(ATTR.MAX_TARGET_RANGE, 0),
|
|
5021
5515
|
ship.getFinal(ATTR.MAX_TARGET_RANGE_CAP, 3e5)
|
|
5022
5516
|
),
|
|
5023
|
-
|
|
5517
|
+
// In-game lockable-target count is the LOWER of the ship's
|
|
5518
|
+
// maxLockedTargets and the pilot's skill-derived cap. Pyfa:
|
|
5519
|
+
// ceil(min(maxTargetsLockedFromSkills, ship maxLockedTargets)),
|
|
5520
|
+
// where maxTargetsLockedFromSkills seeds at a base of 2 (every
|
|
5521
|
+
// capsuleer locks 2 without skills) + Target Management / Advanced
|
|
5522
|
+
// Target Management (+1/level each) → 2 + 5 + 5 = 12 at All-V. The
|
|
5523
|
+
// character item carries only the skill ModAdds (10), so add the
|
|
5524
|
+
// base 2. Without the char cap, high-slot capitals reported 14-20.
|
|
5525
|
+
maxLockedTargets: Math.ceil(Math.min(
|
|
5526
|
+
CHAR_BASE_MAX_LOCKED_TARGETS + ctx.character.getFinal(ATTR.MAX_LOCKED_TARGETS, 0),
|
|
5527
|
+
ship.getFinal(ATTR.MAX_LOCKED_TARGETS, 0)
|
|
5528
|
+
)),
|
|
5024
5529
|
signatureRadius: ship.getFinal(ATTR.SIGNATURE_RADIUS, 0),
|
|
5025
5530
|
scanResolution: ship.getFinal(ATTR.SCAN_RESOLUTION, 0),
|
|
5026
5531
|
sensorStrength: pickSensorStrength(ship).value,
|
|
@@ -5280,176 +5785,6 @@ function tempId() {
|
|
|
5280
5785
|
return `tmp:${++tempCounter}:${Date.now().toString(36)}`;
|
|
5281
5786
|
}
|
|
5282
5787
|
|
|
5283
|
-
// src/fitChecks.ts
|
|
5284
|
-
function readAttr(t, id) {
|
|
5285
|
-
return t.attributes.find((a) => a.id === id)?.v;
|
|
5286
|
-
}
|
|
5287
|
-
function typeFitsSlotType(t, slot) {
|
|
5288
|
-
for (const e of t.effects) {
|
|
5289
|
-
const mapped = SLOT_EFFECT_TO_SLOT_TYPE[e.id];
|
|
5290
|
-
if (mapped === slot) return true;
|
|
5291
|
-
}
|
|
5292
|
-
return false;
|
|
5293
|
-
}
|
|
5294
|
-
function isTurretWeapon(t) {
|
|
5295
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "TURRET");
|
|
5296
|
-
}
|
|
5297
|
-
function isMissileLauncher(t) {
|
|
5298
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "MISSILE");
|
|
5299
|
-
}
|
|
5300
|
-
function isSmartBomb(t) {
|
|
5301
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "SMARTBOMB");
|
|
5302
|
-
}
|
|
5303
|
-
function shipGroupRestrictions(t) {
|
|
5304
|
-
const out = [];
|
|
5305
|
-
for (const id of CAN_FIT_SHIP_GROUP_ATTRS) {
|
|
5306
|
-
const v = readAttr(t, id);
|
|
5307
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
5308
|
-
}
|
|
5309
|
-
return out;
|
|
5310
|
-
}
|
|
5311
|
-
function shipTypeRestrictions(t) {
|
|
5312
|
-
const out = [];
|
|
5313
|
-
for (const id of CAN_FIT_SHIP_TYPE_ATTRS) {
|
|
5314
|
-
const v = readAttr(t, id);
|
|
5315
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
5316
|
-
}
|
|
5317
|
-
return out;
|
|
5318
|
-
}
|
|
5319
|
-
function maxGroupFittedFor(mod) {
|
|
5320
|
-
const v = readAttr(mod, ATTR.MAX_GROUP_FITTED);
|
|
5321
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
5322
|
-
}
|
|
5323
|
-
function maxTypeFittedFor(mod) {
|
|
5324
|
-
const v = readAttr(mod, ATTR.MAX_TYPE_FITTED);
|
|
5325
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
5326
|
-
}
|
|
5327
|
-
function freeFitGroupSlotsFor(mod, fittedModules, dataset) {
|
|
5328
|
-
const groupCap = maxGroupFittedFor(mod);
|
|
5329
|
-
const typeCap = maxTypeFittedFor(mod);
|
|
5330
|
-
if (groupCap === void 0 && typeCap === void 0) return Number.POSITIVE_INFINITY;
|
|
5331
|
-
let groupCount = 0;
|
|
5332
|
-
let typeCount = 0;
|
|
5333
|
-
for (const fm of fittedModules) {
|
|
5334
|
-
if (typeCap !== void 0 && fm.typeID === mod.id) typeCount++;
|
|
5335
|
-
if (groupCap !== void 0) {
|
|
5336
|
-
const t = dataset.getType(fm.typeID);
|
|
5337
|
-
if (t && t.groupID === mod.groupID) groupCount++;
|
|
5338
|
-
}
|
|
5339
|
-
}
|
|
5340
|
-
let free = Number.POSITIVE_INFINITY;
|
|
5341
|
-
if (groupCap !== void 0) free = Math.min(free, groupCap - groupCount);
|
|
5342
|
-
if (typeCap !== void 0) free = Math.min(free, typeCap - typeCount);
|
|
5343
|
-
return Math.max(0, free);
|
|
5344
|
-
}
|
|
5345
|
-
function canFitModuleOnShip(mod, ship, slot, fitContext) {
|
|
5346
|
-
if (!typeFitsSlotType(mod, slot)) {
|
|
5347
|
-
return { ok: false, reason: `Module does not declare a ${slot} slot effect` };
|
|
5348
|
-
}
|
|
5349
|
-
if (!ship) return { ok: true };
|
|
5350
|
-
const allowedGroups = shipGroupRestrictions(mod);
|
|
5351
|
-
const allowedTypes = shipTypeRestrictions(mod);
|
|
5352
|
-
if (allowedGroups.length > 0 || allowedTypes.length > 0) {
|
|
5353
|
-
const groupOk = allowedGroups.includes(ship.groupID);
|
|
5354
|
-
const typeOk = allowedTypes.includes(ship.id);
|
|
5355
|
-
if (!groupOk && !typeOk) {
|
|
5356
|
-
return { ok: false, reason: "Ship hull/group not allowed by this module" };
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
if (slot === "RIG") {
|
|
5360
|
-
const modSize = readAttr(mod, ATTR.RIG_SIZE);
|
|
5361
|
-
const shipSize = readAttr(ship, ATTR.RIG_SIZE);
|
|
5362
|
-
if (modSize != null && shipSize != null && Math.round(modSize) !== Math.round(shipSize)) {
|
|
5363
|
-
return { ok: false, reason: "Rig size mismatch" };
|
|
5364
|
-
}
|
|
5365
|
-
}
|
|
5366
|
-
if (slot === "SUBSYSTEM") {
|
|
5367
|
-
const fitsTo = readAttr(mod, 1380);
|
|
5368
|
-
if (fitsTo != null && fitsTo > 0 && Math.round(fitsTo) !== ship.id) {
|
|
5369
|
-
return { ok: false, reason: "Subsystem locked to a different T3C hull" };
|
|
5370
|
-
}
|
|
5371
|
-
}
|
|
5372
|
-
if (slot === "HI") {
|
|
5373
|
-
if (isTurretWeapon(mod)) {
|
|
5374
|
-
const turrets = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
5375
|
-
if (turrets <= 0) {
|
|
5376
|
-
return { ok: false, reason: "Ship has no turret hardpoints" };
|
|
5377
|
-
}
|
|
5378
|
-
} else if (isMissileLauncher(mod)) {
|
|
5379
|
-
const launchers = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
5380
|
-
if (launchers <= 0) {
|
|
5381
|
-
return { ok: false, reason: "Ship has no launcher hardpoints" };
|
|
5382
|
-
}
|
|
5383
|
-
}
|
|
5384
|
-
}
|
|
5385
|
-
if (fitContext) {
|
|
5386
|
-
const free = freeFitGroupSlotsFor(mod, fitContext.fittedModules, fitContext.dataset);
|
|
5387
|
-
if (free <= 0) {
|
|
5388
|
-
const cap = maxTypeFittedFor(mod) ?? maxGroupFittedFor(mod);
|
|
5389
|
-
return { ok: false, reason: `Only ${cap} of this module type can be fitted to a ship` };
|
|
5390
|
-
}
|
|
5391
|
-
}
|
|
5392
|
-
return { ok: true };
|
|
5393
|
-
}
|
|
5394
|
-
function chargeGroupsForModule(mod) {
|
|
5395
|
-
const out = [];
|
|
5396
|
-
for (const attrID of CHARGE_GROUP_ATTRS) {
|
|
5397
|
-
const v = readAttr(mod, attrID);
|
|
5398
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
5399
|
-
}
|
|
5400
|
-
return out;
|
|
5401
|
-
}
|
|
5402
|
-
function moduleAcceptsAnyCharge(mod) {
|
|
5403
|
-
return chargeGroupsForModule(mod).length > 0;
|
|
5404
|
-
}
|
|
5405
|
-
function moduleAcceptsChargeType(mod, charge) {
|
|
5406
|
-
const allowedGroups = chargeGroupsForModule(mod);
|
|
5407
|
-
if (allowedGroups.length === 0) return false;
|
|
5408
|
-
if (!allowedGroups.includes(charge.groupID)) return false;
|
|
5409
|
-
const modSize = readAttr(mod, ATTR.CHARGE_SIZE);
|
|
5410
|
-
const chargeSize = readAttr(charge, ATTR.CHARGE_SIZE);
|
|
5411
|
-
if (modSize != null && chargeSize != null && Math.round(chargeSize) > Math.round(modSize)) {
|
|
5412
|
-
return false;
|
|
5413
|
-
}
|
|
5414
|
-
return true;
|
|
5415
|
-
}
|
|
5416
|
-
var ACTIVE_EFFECT_CATEGORIES = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
5417
|
-
function isActivatableModule(mod, effects) {
|
|
5418
|
-
for (const e of mod.effects) {
|
|
5419
|
-
const eff = effects.get(e.id);
|
|
5420
|
-
if (eff && eff.effectCategoryID !== void 0 && ACTIVE_EFFECT_CATEGORIES.has(eff.effectCategoryID)) {
|
|
5421
|
-
return true;
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
|
-
return false;
|
|
5425
|
-
}
|
|
5426
|
-
var DEFAULT_OFFLINE_ACTIVATION_GROUPS = /* @__PURE__ */ new Set([
|
|
5427
|
-
330,
|
|
5428
|
-
4117
|
|
5429
|
-
]);
|
|
5430
|
-
function defaultStateForModule(mod, effects) {
|
|
5431
|
-
if (DEFAULT_OFFLINE_ACTIVATION_GROUPS.has(mod.groupID)) return "ONLINE";
|
|
5432
|
-
return isActivatableModule(mod, effects) ? "ACTIVE" : "ONLINE";
|
|
5433
|
-
}
|
|
5434
|
-
function freeHardpointsFor(mod, ship, fittedHiModules, dataset) {
|
|
5435
|
-
const turretCap = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
5436
|
-
const launcherCap = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
5437
|
-
let turretUsed = 0;
|
|
5438
|
-
let launcherUsed = 0;
|
|
5439
|
-
for (const m of fittedHiModules) {
|
|
5440
|
-
const t = dataset.getType(m.typeID);
|
|
5441
|
-
if (!t) continue;
|
|
5442
|
-
if (isTurretWeapon(t)) turretUsed++;
|
|
5443
|
-
else if (isMissileLauncher(t)) launcherUsed++;
|
|
5444
|
-
}
|
|
5445
|
-
let hardpointFree;
|
|
5446
|
-
if (isTurretWeapon(mod)) hardpointFree = Math.max(0, turretCap - turretUsed);
|
|
5447
|
-
else if (isMissileLauncher(mod)) hardpointFree = Math.max(0, launcherCap - launcherUsed);
|
|
5448
|
-
else hardpointFree = Number.POSITIVE_INFINITY;
|
|
5449
|
-
const groupFree = freeFitGroupSlotsFor(mod, fittedHiModules, dataset);
|
|
5450
|
-
return Math.min(hardpointFree, groupFree);
|
|
5451
|
-
}
|
|
5452
|
-
|
|
5453
5788
|
// src/eft/format.ts
|
|
5454
5789
|
var SLOT_ORDER = ["LO", "MED", "HI", "RIG"];
|
|
5455
5790
|
function formatEft(fit, dataset) {
|