eve-fit-engine 0.1.0 → 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 +596 -218
- package/dist/index.js +596 -218
- package/dist/node.cjs +596 -218
- package/dist/node.js +596 -218
- 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
|
|
@@ -1741,8 +2084,42 @@ var SUBSYSTEM_BONUS_SCALING_SKILL = /* @__PURE__ */ new Map([
|
|
|
1741
2084
|
// MinmatarDefensive
|
|
1742
2085
|
[1449, 30551],
|
|
1743
2086
|
// MinmatarOffensive
|
|
1744
|
-
[1450, 30554]
|
|
2087
|
+
[1450, 30554],
|
|
1745
2088
|
// MinmatarPropulsion
|
|
2089
|
+
// ----- Authoritative completion: every subsystem-bonus attr that the SDE
|
|
2090
|
+
// ----- scales by a racial subsystem skill, derived verbatim from the
|
|
2091
|
+
// ----- `subsystemSkillLevel*` skill effects (modAttr = bonus attr,
|
|
2092
|
+
// ----- modifying = 280/skillLevel, PreMul). The Amarr/Caldari secondaries
|
|
2093
|
+
// ----- above were hand-added; Gallente + Minmatar secondaries (and the
|
|
2094
|
+
// ----- 2680-2687 defensive/core block) were MISSING, so subsystem-sourced
|
|
2095
|
+
// ----- bonuses on those races (e.g. Loki Propulsion agility 1523, Offensive
|
|
2096
|
+
// ----- RoF 1522 / 1534) fell to the flat path and applied ×1 instead of ×5.
|
|
2097
|
+
// ----- Duplicates of the entries above are harmless (same value).
|
|
2098
|
+
[1517, 30540],
|
|
2099
|
+
[1519, 30546],
|
|
2100
|
+
[1520, 30553],
|
|
2101
|
+
[1521, 30550],
|
|
2102
|
+
// Gallente Def/Core/Prop/Off secondaries
|
|
2103
|
+
[1522, 30551],
|
|
2104
|
+
[1523, 30554],
|
|
2105
|
+
[1525, 30547],
|
|
2106
|
+
[1526, 30545],
|
|
2107
|
+
// Minmatar Off/Prop/Core/Def secondaries
|
|
2108
|
+
[1531, 30537],
|
|
2109
|
+
[1532, 30550],
|
|
2110
|
+
[1533, 30549],
|
|
2111
|
+
[1534, 30551],
|
|
2112
|
+
// Off cross-race tertiaries (Amarr/Gallente/Caldari/Minmatar)
|
|
2113
|
+
[2680, 30532],
|
|
2114
|
+
[2681, 30539],
|
|
2115
|
+
[2682, 30544],
|
|
2116
|
+
[2683, 30548],
|
|
2117
|
+
// Def/Core extra (Amarr/Caldari)
|
|
2118
|
+
[2684, 30540],
|
|
2119
|
+
[2685, 30546],
|
|
2120
|
+
[2686, 30545],
|
|
2121
|
+
[2687, 30547]
|
|
2122
|
+
// Def/Core extra (Gallente/Minmatar)
|
|
1746
2123
|
]);
|
|
1747
2124
|
function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
1748
2125
|
if (mi.modifyingAttributeID === void 0) return null;
|
|
@@ -1782,7 +2159,7 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
1782
2159
|
if (skillID === void 0) return null;
|
|
1783
2160
|
const level = ctx.skillLevel(skillID);
|
|
1784
2161
|
if (level === 0) return null;
|
|
1785
|
-
if (source.kind === "ship"
|
|
2162
|
+
if (source.kind === "ship") {
|
|
1786
2163
|
return { value: baseValue, scaled: false };
|
|
1787
2164
|
}
|
|
1788
2165
|
if (itemRequiresSkill(source, skillID)) {
|
|
@@ -1792,46 +2169,21 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
1792
2169
|
}
|
|
1793
2170
|
return { value: baseValue, scaled: false };
|
|
1794
2171
|
}
|
|
1795
|
-
|
|
1796
|
-
793,
|
|
1797
|
-
// shipBonusRole7
|
|
1798
|
-
1688,
|
|
1799
|
-
// shipBonusRole8
|
|
1800
|
-
1803,
|
|
1801
|
-
// MWDSignatureRadiusBonus — Assault Frigate / Interceptor MWD sig role bonus (flat)
|
|
1802
|
-
2059,
|
|
1803
|
-
// eliteBonusCommandDestroyer1 — Command Destroyer T2 specialisation (flat per-level applied via skill, but the SHIP-side reader is flat)
|
|
1804
|
-
2060,
|
|
1805
|
-
// eliteBonusCommandDestroyer2
|
|
1806
|
-
2064,
|
|
1807
|
-
// roleBonusCD — Command Destroyer command burst PG / activation cost reduction.
|
|
1808
|
-
// Without this entry, effect 6214 (Draugur's `roleBonusCDLinksPGReduction`)
|
|
1809
|
-
// double-applies the -95 % bonus at × Skirmish Command Burst V → -475 %
|
|
1810
|
-
// PostPercent on Skirmish Command Burst II `power` (110) → -412.5 MW per
|
|
1811
|
-
// burst → total ship power used reads −738 MW instead of +97 MW.
|
|
1812
|
-
2298,
|
|
1813
|
-
// shipBonusRole1
|
|
1814
|
-
2299,
|
|
1815
|
-
// shipBonusRole2
|
|
1816
|
-
2300,
|
|
1817
|
-
// shipBonusRole3
|
|
1818
|
-
2301,
|
|
1819
|
-
// shipBonusRole4
|
|
1820
|
-
2302,
|
|
1821
|
-
// shipBonusRole5
|
|
1822
|
-
2303,
|
|
1823
|
-
// shipBonusRole6
|
|
1824
|
-
5952
|
|
1825
|
-
// shipBonusGasCloudDurationRoleBonusOreMiningDestroyer
|
|
1826
|
-
]);
|
|
1827
|
-
function computeStackingGroup(source, mi, dataset) {
|
|
2172
|
+
function computeStackingGroup(source, mi, dataset, effect) {
|
|
1828
2173
|
if (NO_PENALTY_KINDS.has(source.kind)) return null;
|
|
1829
2174
|
if (mi.modifiedAttributeID !== void 0) {
|
|
1830
2175
|
const attr = dataset.attributes.get(mi.modifiedAttributeID);
|
|
1831
2176
|
if (attr?.stackable) return null;
|
|
1832
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
|
+
}
|
|
1833
2182
|
return `attr:${mi.modifiedAttributeID}`;
|
|
1834
2183
|
}
|
|
2184
|
+
var CUSTOM_STACK_GROUPS_HONOURED = /* @__PURE__ */ new Set([
|
|
2185
|
+
"cloakingScanResolutionMultiplier"
|
|
2186
|
+
]);
|
|
1835
2187
|
function mapSourceKind(kind) {
|
|
1836
2188
|
switch (kind) {
|
|
1837
2189
|
case "ship":
|
|
@@ -3549,6 +3901,13 @@ var LEGACY_HANDLED_HARDCODED_EFFECTS = /* @__PURE__ */ new Set([
|
|
|
3549
3901
|
6658
|
|
3550
3902
|
// Bastion Module — applyLegacyBastion
|
|
3551
3903
|
]);
|
|
3904
|
+
var SEC_STATUS_SCALED_EFFECT_IDS = /* @__PURE__ */ new Set([
|
|
3905
|
+
6871,
|
|
3906
|
+
12165,
|
|
3907
|
+
12181,
|
|
3908
|
+
12185,
|
|
3909
|
+
12202
|
|
3910
|
+
]);
|
|
3552
3911
|
var LEGACY_HANDLED_EFFECT_IDS = /* @__PURE__ */ new Set([
|
|
3553
3912
|
...LEGACY_HANDLED_PASSIVE_ADD_EFFECTS,
|
|
3554
3913
|
...LEGACY_HANDLED_HARDCODED_EFFECTS
|
|
@@ -3619,6 +3978,176 @@ function computeTotalEhp(shield, armor, hull, useProfile) {
|
|
|
3619
3978
|
return Number.isFinite(total) ? total : Number.MAX_SAFE_INTEGER;
|
|
3620
3979
|
}
|
|
3621
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
|
+
|
|
3622
4151
|
// src/derived/capacitor.ts
|
|
3623
4152
|
function computeCapacitor(ctx, dataset) {
|
|
3624
4153
|
const ship = ctx.ship;
|
|
@@ -3698,6 +4227,7 @@ var CAP_BOOSTER_EFFECT_ID = 48;
|
|
|
3698
4227
|
var ATTR_CAPACITOR_BONUS = 67;
|
|
3699
4228
|
var ATTR_CAPACITOR_NEED2 = 6;
|
|
3700
4229
|
var ATTR_DURATION_MS2 = 73;
|
|
4230
|
+
var ATTR_REACTIVATION_MS = 669;
|
|
3701
4231
|
var ATTR_RELOAD_TIME = 1795;
|
|
3702
4232
|
var ATTR_CAPACITY = 38;
|
|
3703
4233
|
var ATTR_VOLUME = 161;
|
|
@@ -3732,8 +4262,9 @@ function drainEntryFromEffect(effect, mod) {
|
|
|
3732
4262
|
if (effect.dischargeAttributeID === void 0) return null;
|
|
3733
4263
|
const discharge = mod.getFinal(effect.dischargeAttributeID, 0);
|
|
3734
4264
|
if (discharge <= 0) return null;
|
|
3735
|
-
const
|
|
3736
|
-
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));
|
|
3737
4268
|
return {
|
|
3738
4269
|
cycleMs,
|
|
3739
4270
|
capNeed: discharge,
|
|
@@ -3741,7 +4272,12 @@ function drainEntryFromEffect(effect, mod) {
|
|
|
3741
4272
|
// ammo doesn't reload for cap purposes on regular modules
|
|
3742
4273
|
reloadMs: 0,
|
|
3743
4274
|
isInjector: false,
|
|
3744
|
-
|
|
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)
|
|
3745
4281
|
};
|
|
3746
4282
|
}
|
|
3747
4283
|
function boosterDrainEntry(mod) {
|
|
@@ -3749,19 +4285,19 @@ function boosterDrainEntry(mod) {
|
|
|
3749
4285
|
if (!charge) return null;
|
|
3750
4286
|
const capNeed = mod.getFinal(ATTR_CAPACITOR_NEED2, 0);
|
|
3751
4287
|
const inject = charge.getFinal(ATTR_CAPACITOR_BONUS, 0);
|
|
3752
|
-
const cycleMs = mod.getFinal(ATTR_DURATION_MS2, 0);
|
|
4288
|
+
const cycleMs = Math.round(mod.getFinal(ATTR_DURATION_MS2, 0));
|
|
3753
4289
|
if (cycleMs <= 0) return null;
|
|
3754
|
-
let reloadMs = mod.getFinal(ATTR_RELOAD_TIME, 0);
|
|
4290
|
+
let reloadMs = Math.round(mod.getFinal(ATTR_RELOAD_TIME, 0));
|
|
3755
4291
|
if (reloadMs <= 0) reloadMs = 1e4;
|
|
3756
4292
|
const capacity = mod.getFinal(ATTR_CAPACITY, 0);
|
|
3757
4293
|
const chargeVol = charge.getFinal(ATTR_VOLUME, 0);
|
|
3758
|
-
const charges = capacity > 0 && chargeVol > 0 ? Math.floor(capacity / chargeVol) :
|
|
4294
|
+
const charges = capacity > 0 && chargeVol > 0 ? Math.floor(capacity / chargeVol) : 0;
|
|
3759
4295
|
return {
|
|
3760
4296
|
cycleMs,
|
|
3761
4297
|
// Pyfa convention: positive capNeed = drain, negative = injection.
|
|
3762
4298
|
// For boosters the *net* per-cycle change is (capNeed_module - inject_charge).
|
|
3763
4299
|
capNeed: capNeed - inject,
|
|
3764
|
-
clipSize:
|
|
4300
|
+
clipSize: charges,
|
|
3765
4301
|
reloadMs,
|
|
3766
4302
|
isInjector: true,
|
|
3767
4303
|
disableStagger: false
|
|
@@ -4693,8 +5229,8 @@ function computeFit(fit, dataset, opts) {
|
|
|
4693
5229
|
}
|
|
4694
5230
|
ctx.stoppedLocalEffectIDs = collectEffectStoppers(earlyProjected, dataset);
|
|
4695
5231
|
}
|
|
4696
|
-
for (const m of modules) applySourceItem(m, ctx, dataset);
|
|
4697
5232
|
for (const m of modules) if (m.charge) applySourceItem(m.charge, ctx, dataset);
|
|
5233
|
+
for (const m of modules) applySourceItem(m, ctx, dataset);
|
|
4698
5234
|
for (const d of drones) applySourceItem(d, ctx, dataset);
|
|
4699
5235
|
for (const f of fighters) applySourceItem(f, ctx, dataset);
|
|
4700
5236
|
for (const i of implants) applySourceItem(i, ctx, dataset);
|
|
@@ -4857,6 +5393,7 @@ function buildProjectionReport(source, ctx) {
|
|
|
4857
5393
|
summary: labels[cls.kind] ?? cls.kind
|
|
4858
5394
|
};
|
|
4859
5395
|
}
|
|
5396
|
+
var CHAR_BASE_MAX_LOCKED_TARGETS = 2;
|
|
4860
5397
|
function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
4861
5398
|
const ship = ctx.ship;
|
|
4862
5399
|
const slotUsed = { HI: 0, MED: 0, LO: 0, RIG: 0, SUBSYSTEM: 0, SERVICE: 0 };
|
|
@@ -4977,7 +5514,18 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
4977
5514
|
ship.getFinal(ATTR.MAX_TARGET_RANGE, 0),
|
|
4978
5515
|
ship.getFinal(ATTR.MAX_TARGET_RANGE_CAP, 3e5)
|
|
4979
5516
|
),
|
|
4980
|
-
|
|
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
|
+
)),
|
|
4981
5529
|
signatureRadius: ship.getFinal(ATTR.SIGNATURE_RADIUS, 0),
|
|
4982
5530
|
scanResolution: ship.getFinal(ATTR.SCAN_RESOLUTION, 0),
|
|
4983
5531
|
sensorStrength: pickSensorStrength(ship).value,
|
|
@@ -5237,176 +5785,6 @@ function tempId() {
|
|
|
5237
5785
|
return `tmp:${++tempCounter}:${Date.now().toString(36)}`;
|
|
5238
5786
|
}
|
|
5239
5787
|
|
|
5240
|
-
// src/fitChecks.ts
|
|
5241
|
-
function readAttr(t, id) {
|
|
5242
|
-
return t.attributes.find((a) => a.id === id)?.v;
|
|
5243
|
-
}
|
|
5244
|
-
function typeFitsSlotType(t, slot) {
|
|
5245
|
-
for (const e of t.effects) {
|
|
5246
|
-
const mapped = SLOT_EFFECT_TO_SLOT_TYPE[e.id];
|
|
5247
|
-
if (mapped === slot) return true;
|
|
5248
|
-
}
|
|
5249
|
-
return false;
|
|
5250
|
-
}
|
|
5251
|
-
function isTurretWeapon(t) {
|
|
5252
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "TURRET");
|
|
5253
|
-
}
|
|
5254
|
-
function isMissileLauncher(t) {
|
|
5255
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "MISSILE");
|
|
5256
|
-
}
|
|
5257
|
-
function isSmartBomb(t) {
|
|
5258
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "SMARTBOMB");
|
|
5259
|
-
}
|
|
5260
|
-
function shipGroupRestrictions(t) {
|
|
5261
|
-
const out = [];
|
|
5262
|
-
for (const id of CAN_FIT_SHIP_GROUP_ATTRS) {
|
|
5263
|
-
const v = readAttr(t, id);
|
|
5264
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
5265
|
-
}
|
|
5266
|
-
return out;
|
|
5267
|
-
}
|
|
5268
|
-
function shipTypeRestrictions(t) {
|
|
5269
|
-
const out = [];
|
|
5270
|
-
for (const id of CAN_FIT_SHIP_TYPE_ATTRS) {
|
|
5271
|
-
const v = readAttr(t, id);
|
|
5272
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
5273
|
-
}
|
|
5274
|
-
return out;
|
|
5275
|
-
}
|
|
5276
|
-
function maxGroupFittedFor(mod) {
|
|
5277
|
-
const v = readAttr(mod, ATTR.MAX_GROUP_FITTED);
|
|
5278
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
5279
|
-
}
|
|
5280
|
-
function maxTypeFittedFor(mod) {
|
|
5281
|
-
const v = readAttr(mod, ATTR.MAX_TYPE_FITTED);
|
|
5282
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
5283
|
-
}
|
|
5284
|
-
function freeFitGroupSlotsFor(mod, fittedModules, dataset) {
|
|
5285
|
-
const groupCap = maxGroupFittedFor(mod);
|
|
5286
|
-
const typeCap = maxTypeFittedFor(mod);
|
|
5287
|
-
if (groupCap === void 0 && typeCap === void 0) return Number.POSITIVE_INFINITY;
|
|
5288
|
-
let groupCount = 0;
|
|
5289
|
-
let typeCount = 0;
|
|
5290
|
-
for (const fm of fittedModules) {
|
|
5291
|
-
if (typeCap !== void 0 && fm.typeID === mod.id) typeCount++;
|
|
5292
|
-
if (groupCap !== void 0) {
|
|
5293
|
-
const t = dataset.getType(fm.typeID);
|
|
5294
|
-
if (t && t.groupID === mod.groupID) groupCount++;
|
|
5295
|
-
}
|
|
5296
|
-
}
|
|
5297
|
-
let free = Number.POSITIVE_INFINITY;
|
|
5298
|
-
if (groupCap !== void 0) free = Math.min(free, groupCap - groupCount);
|
|
5299
|
-
if (typeCap !== void 0) free = Math.min(free, typeCap - typeCount);
|
|
5300
|
-
return Math.max(0, free);
|
|
5301
|
-
}
|
|
5302
|
-
function canFitModuleOnShip(mod, ship, slot, fitContext) {
|
|
5303
|
-
if (!typeFitsSlotType(mod, slot)) {
|
|
5304
|
-
return { ok: false, reason: `Module does not declare a ${slot} slot effect` };
|
|
5305
|
-
}
|
|
5306
|
-
if (!ship) return { ok: true };
|
|
5307
|
-
const allowedGroups = shipGroupRestrictions(mod);
|
|
5308
|
-
const allowedTypes = shipTypeRestrictions(mod);
|
|
5309
|
-
if (allowedGroups.length > 0 || allowedTypes.length > 0) {
|
|
5310
|
-
const groupOk = allowedGroups.includes(ship.groupID);
|
|
5311
|
-
const typeOk = allowedTypes.includes(ship.id);
|
|
5312
|
-
if (!groupOk && !typeOk) {
|
|
5313
|
-
return { ok: false, reason: "Ship hull/group not allowed by this module" };
|
|
5314
|
-
}
|
|
5315
|
-
}
|
|
5316
|
-
if (slot === "RIG") {
|
|
5317
|
-
const modSize = readAttr(mod, ATTR.RIG_SIZE);
|
|
5318
|
-
const shipSize = readAttr(ship, ATTR.RIG_SIZE);
|
|
5319
|
-
if (modSize != null && shipSize != null && Math.round(modSize) !== Math.round(shipSize)) {
|
|
5320
|
-
return { ok: false, reason: "Rig size mismatch" };
|
|
5321
|
-
}
|
|
5322
|
-
}
|
|
5323
|
-
if (slot === "SUBSYSTEM") {
|
|
5324
|
-
const fitsTo = readAttr(mod, 1380);
|
|
5325
|
-
if (fitsTo != null && fitsTo > 0 && Math.round(fitsTo) !== ship.id) {
|
|
5326
|
-
return { ok: false, reason: "Subsystem locked to a different T3C hull" };
|
|
5327
|
-
}
|
|
5328
|
-
}
|
|
5329
|
-
if (slot === "HI") {
|
|
5330
|
-
if (isTurretWeapon(mod)) {
|
|
5331
|
-
const turrets = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
5332
|
-
if (turrets <= 0) {
|
|
5333
|
-
return { ok: false, reason: "Ship has no turret hardpoints" };
|
|
5334
|
-
}
|
|
5335
|
-
} else if (isMissileLauncher(mod)) {
|
|
5336
|
-
const launchers = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
5337
|
-
if (launchers <= 0) {
|
|
5338
|
-
return { ok: false, reason: "Ship has no launcher hardpoints" };
|
|
5339
|
-
}
|
|
5340
|
-
}
|
|
5341
|
-
}
|
|
5342
|
-
if (fitContext) {
|
|
5343
|
-
const free = freeFitGroupSlotsFor(mod, fitContext.fittedModules, fitContext.dataset);
|
|
5344
|
-
if (free <= 0) {
|
|
5345
|
-
const cap = maxTypeFittedFor(mod) ?? maxGroupFittedFor(mod);
|
|
5346
|
-
return { ok: false, reason: `Only ${cap} of this module type can be fitted to a ship` };
|
|
5347
|
-
}
|
|
5348
|
-
}
|
|
5349
|
-
return { ok: true };
|
|
5350
|
-
}
|
|
5351
|
-
function chargeGroupsForModule(mod) {
|
|
5352
|
-
const out = [];
|
|
5353
|
-
for (const attrID of CHARGE_GROUP_ATTRS) {
|
|
5354
|
-
const v = readAttr(mod, attrID);
|
|
5355
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
5356
|
-
}
|
|
5357
|
-
return out;
|
|
5358
|
-
}
|
|
5359
|
-
function moduleAcceptsAnyCharge(mod) {
|
|
5360
|
-
return chargeGroupsForModule(mod).length > 0;
|
|
5361
|
-
}
|
|
5362
|
-
function moduleAcceptsChargeType(mod, charge) {
|
|
5363
|
-
const allowedGroups = chargeGroupsForModule(mod);
|
|
5364
|
-
if (allowedGroups.length === 0) return false;
|
|
5365
|
-
if (!allowedGroups.includes(charge.groupID)) return false;
|
|
5366
|
-
const modSize = readAttr(mod, ATTR.CHARGE_SIZE);
|
|
5367
|
-
const chargeSize = readAttr(charge, ATTR.CHARGE_SIZE);
|
|
5368
|
-
if (modSize != null && chargeSize != null && Math.round(chargeSize) > Math.round(modSize)) {
|
|
5369
|
-
return false;
|
|
5370
|
-
}
|
|
5371
|
-
return true;
|
|
5372
|
-
}
|
|
5373
|
-
var ACTIVE_EFFECT_CATEGORIES = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
5374
|
-
function isActivatableModule(mod, effects) {
|
|
5375
|
-
for (const e of mod.effects) {
|
|
5376
|
-
const eff = effects.get(e.id);
|
|
5377
|
-
if (eff && eff.effectCategoryID !== void 0 && ACTIVE_EFFECT_CATEGORIES.has(eff.effectCategoryID)) {
|
|
5378
|
-
return true;
|
|
5379
|
-
}
|
|
5380
|
-
}
|
|
5381
|
-
return false;
|
|
5382
|
-
}
|
|
5383
|
-
var DEFAULT_OFFLINE_ACTIVATION_GROUPS = /* @__PURE__ */ new Set([
|
|
5384
|
-
330,
|
|
5385
|
-
4117
|
|
5386
|
-
]);
|
|
5387
|
-
function defaultStateForModule(mod, effects) {
|
|
5388
|
-
if (DEFAULT_OFFLINE_ACTIVATION_GROUPS.has(mod.groupID)) return "ONLINE";
|
|
5389
|
-
return isActivatableModule(mod, effects) ? "ACTIVE" : "ONLINE";
|
|
5390
|
-
}
|
|
5391
|
-
function freeHardpointsFor(mod, ship, fittedHiModules, dataset) {
|
|
5392
|
-
const turretCap = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
5393
|
-
const launcherCap = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
5394
|
-
let turretUsed = 0;
|
|
5395
|
-
let launcherUsed = 0;
|
|
5396
|
-
for (const m of fittedHiModules) {
|
|
5397
|
-
const t = dataset.getType(m.typeID);
|
|
5398
|
-
if (!t) continue;
|
|
5399
|
-
if (isTurretWeapon(t)) turretUsed++;
|
|
5400
|
-
else if (isMissileLauncher(t)) launcherUsed++;
|
|
5401
|
-
}
|
|
5402
|
-
let hardpointFree;
|
|
5403
|
-
if (isTurretWeapon(mod)) hardpointFree = Math.max(0, turretCap - turretUsed);
|
|
5404
|
-
else if (isMissileLauncher(mod)) hardpointFree = Math.max(0, launcherCap - launcherUsed);
|
|
5405
|
-
else hardpointFree = Number.POSITIVE_INFINITY;
|
|
5406
|
-
const groupFree = freeFitGroupSlotsFor(mod, fittedHiModules, dataset);
|
|
5407
|
-
return Math.min(hardpointFree, groupFree);
|
|
5408
|
-
}
|
|
5409
|
-
|
|
5410
5788
|
// src/eft/format.ts
|
|
5411
5789
|
var SLOT_ORDER = ["LO", "MED", "HI", "RIG"];
|
|
5412
5790
|
function formatEft(fit, dataset) {
|