eve-fit-engine 0.1.35 → 0.2.0
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/data/manifest.json +1 -1
- package/data/v0c61c617c9b9/attributes.json +1 -0
- package/dist/index.cjs +816 -548
- package/dist/index.d.cts +104 -4
- package/dist/index.d.ts +104 -4
- package/dist/index.js +815 -549
- package/dist/node.cjs +816 -548
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +815 -549
- package/package.json +3 -1
- package/data/v796a8249240a/attributes.json +0 -1
- /package/data/{v796a8249240a → v0c61c617c9b9}/categories.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/clone-grades.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/dbuff-collections.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/dynamic-attributes.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/effects.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/groups.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/market-groups.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/meta-groups.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/charges.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/drones.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/fighters.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/implants.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/modules.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/mutaplasmids.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/ships.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/skills.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/structure-modules.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/structures.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/subsystems.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/types/system-effects.json +0 -0
- /package/data/{v796a8249240a → v0c61c617c9b9}/units.json +0 -0
package/dist/index.cjs
CHANGED
|
@@ -466,6 +466,32 @@ var LEGACY_EFFECT_IDS = [
|
|
|
466
466
|
// Frigate MJD sig bloom.
|
|
467
467
|
{ id: 12126, name: null, handler: "modifierEngine::applyLegacyMjdSigBloom (Frigate MJD)" }
|
|
468
468
|
];
|
|
469
|
+
var BOOSTER_SIDE_EFFECT_IDS = /* @__PURE__ */ new Set([
|
|
470
|
+
2735,
|
|
471
|
+
// boosterArmorHpPenalty
|
|
472
|
+
2736,
|
|
473
|
+
// boosterArmorRepairAmountPenalty
|
|
474
|
+
2737,
|
|
475
|
+
// boosterShieldCapacityPenalty
|
|
476
|
+
2739,
|
|
477
|
+
// boosterTurretOptimalRangePenalty
|
|
478
|
+
2741,
|
|
479
|
+
// boosterTurretFalloffPenalty
|
|
480
|
+
2745,
|
|
481
|
+
// boosterCapacitorCapacityPenalty
|
|
482
|
+
2746,
|
|
483
|
+
// boosterMaxVelocityPenalty
|
|
484
|
+
2747,
|
|
485
|
+
// boosterTurretTrackingPenalty
|
|
486
|
+
2748,
|
|
487
|
+
// boosterMissileVelocityPenalty
|
|
488
|
+
2749,
|
|
489
|
+
// boosterMissileExplosionVelocityPenalty
|
|
490
|
+
2791,
|
|
491
|
+
// boosterMissileExplosionCloudPenaltyFixed
|
|
492
|
+
4970
|
|
493
|
+
// boosterShieldBoostAmountPenaltyShieldSkills
|
|
494
|
+
]);
|
|
469
495
|
var OUT_OF_SCOPE_EFFECT_IDS = [
|
|
470
496
|
// ---- Hardpoint / slot dispatch markers consumed via fitChecks (not a modifier dispatch path). ----
|
|
471
497
|
{ id: 40, name: null, handler: "fitChecks::isMissileLauncher (hardpoint marker)" },
|
|
@@ -553,7 +579,204 @@ function verifyLegacyEffectIds(effects) {
|
|
|
553
579
|
return complaints;
|
|
554
580
|
}
|
|
555
581
|
|
|
582
|
+
// src/fitChecks.ts
|
|
583
|
+
function readAttr(t, id) {
|
|
584
|
+
return t.attributes.find((a) => a.id === id)?.v;
|
|
585
|
+
}
|
|
586
|
+
function typeFitsSlotType(t, slot) {
|
|
587
|
+
for (const e of t.effects) {
|
|
588
|
+
const mapped = SLOT_EFFECT_TO_SLOT_TYPE[e.id];
|
|
589
|
+
if (mapped === slot) return true;
|
|
590
|
+
}
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
function isTurretWeapon(t) {
|
|
594
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "TURRET");
|
|
595
|
+
}
|
|
596
|
+
function isMissileLauncher(t) {
|
|
597
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "MISSILE");
|
|
598
|
+
}
|
|
599
|
+
function isSmartBomb(t) {
|
|
600
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "SMARTBOMB");
|
|
601
|
+
}
|
|
602
|
+
function shipGroupRestrictions(t) {
|
|
603
|
+
const out = [];
|
|
604
|
+
for (const id of CAN_FIT_SHIP_GROUP_ATTRS) {
|
|
605
|
+
const v = readAttr(t, id);
|
|
606
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
607
|
+
}
|
|
608
|
+
return out;
|
|
609
|
+
}
|
|
610
|
+
function shipTypeRestrictions(t) {
|
|
611
|
+
const out = [];
|
|
612
|
+
for (const id of CAN_FIT_SHIP_TYPE_ATTRS) {
|
|
613
|
+
const v = readAttr(t, id);
|
|
614
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
615
|
+
}
|
|
616
|
+
return out;
|
|
617
|
+
}
|
|
618
|
+
function maxGroupFittedFor(mod) {
|
|
619
|
+
const v = readAttr(mod, ATTR.MAX_GROUP_FITTED);
|
|
620
|
+
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
621
|
+
}
|
|
622
|
+
function maxTypeFittedFor(mod) {
|
|
623
|
+
const v = readAttr(mod, ATTR.MAX_TYPE_FITTED);
|
|
624
|
+
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
625
|
+
}
|
|
626
|
+
function freeFitGroupSlotsFor(mod, fittedModules, dataset) {
|
|
627
|
+
const groupCap = maxGroupFittedFor(mod);
|
|
628
|
+
const typeCap = maxTypeFittedFor(mod);
|
|
629
|
+
if (groupCap === void 0 && typeCap === void 0) return Number.POSITIVE_INFINITY;
|
|
630
|
+
let groupCount = 0;
|
|
631
|
+
let typeCount = 0;
|
|
632
|
+
for (const fm of fittedModules) {
|
|
633
|
+
if (typeCap !== void 0 && fm.typeID === mod.id) typeCount++;
|
|
634
|
+
if (groupCap !== void 0) {
|
|
635
|
+
const t = dataset.getType(fm.typeID);
|
|
636
|
+
if (t && t.groupID === mod.groupID) groupCount++;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
let free = Number.POSITIVE_INFINITY;
|
|
640
|
+
if (groupCap !== void 0) free = Math.min(free, groupCap - groupCount);
|
|
641
|
+
if (typeCap !== void 0) free = Math.min(free, typeCap - typeCount);
|
|
642
|
+
return Math.max(0, free);
|
|
643
|
+
}
|
|
644
|
+
function canFitModuleOnShip(mod, ship, slot, fitContext) {
|
|
645
|
+
if (!typeFitsSlotType(mod, slot)) {
|
|
646
|
+
return { ok: false, reason: `Module does not declare a ${slot} slot effect` };
|
|
647
|
+
}
|
|
648
|
+
if (!ship) return { ok: true };
|
|
649
|
+
const allowedGroups = shipGroupRestrictions(mod);
|
|
650
|
+
const allowedTypes = shipTypeRestrictions(mod);
|
|
651
|
+
if (allowedGroups.length > 0 || allowedTypes.length > 0) {
|
|
652
|
+
const groupOk = allowedGroups.includes(ship.groupID);
|
|
653
|
+
const typeOk = allowedTypes.includes(ship.id);
|
|
654
|
+
if (!groupOk && !typeOk) {
|
|
655
|
+
return { ok: false, reason: "Ship hull/group not allowed by this module" };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (slot === "RIG") {
|
|
659
|
+
const modSize = readAttr(mod, ATTR.RIG_SIZE);
|
|
660
|
+
const shipSize = readAttr(ship, ATTR.RIG_SIZE);
|
|
661
|
+
if (modSize != null && shipSize != null && Math.round(modSize) !== Math.round(shipSize)) {
|
|
662
|
+
return { ok: false, reason: "Rig size mismatch" };
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (slot === "SUBSYSTEM") {
|
|
666
|
+
const fitsTo = readAttr(mod, 1380);
|
|
667
|
+
if (fitsTo != null && fitsTo > 0 && Math.round(fitsTo) !== ship.id) {
|
|
668
|
+
return { ok: false, reason: "Subsystem locked to a different T3C hull" };
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (slot === "HI") {
|
|
672
|
+
if (isTurretWeapon(mod)) {
|
|
673
|
+
const turrets = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
674
|
+
if (turrets <= 0) {
|
|
675
|
+
return { ok: false, reason: "Ship has no turret hardpoints" };
|
|
676
|
+
}
|
|
677
|
+
} else if (isMissileLauncher(mod)) {
|
|
678
|
+
const launchers = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
679
|
+
if (launchers <= 0) {
|
|
680
|
+
return { ok: false, reason: "Ship has no launcher hardpoints" };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (fitContext) {
|
|
685
|
+
const free = freeFitGroupSlotsFor(mod, fitContext.fittedModules, fitContext.dataset);
|
|
686
|
+
if (free <= 0) {
|
|
687
|
+
const cap = maxTypeFittedFor(mod) ?? maxGroupFittedFor(mod);
|
|
688
|
+
return { ok: false, reason: `Only ${cap} of this module type can be fitted to a ship` };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { ok: true };
|
|
692
|
+
}
|
|
693
|
+
function chargeGroupsForModule(mod) {
|
|
694
|
+
const out = [];
|
|
695
|
+
for (const attrID of CHARGE_GROUP_ATTRS) {
|
|
696
|
+
const v = readAttr(mod, attrID);
|
|
697
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
698
|
+
}
|
|
699
|
+
return out;
|
|
700
|
+
}
|
|
701
|
+
function moduleAcceptsAnyCharge(mod) {
|
|
702
|
+
return chargeGroupsForModule(mod).length > 0;
|
|
703
|
+
}
|
|
704
|
+
function moduleAcceptsChargeType(mod, charge) {
|
|
705
|
+
const allowedGroups = chargeGroupsForModule(mod);
|
|
706
|
+
if (allowedGroups.length === 0) return false;
|
|
707
|
+
if (!allowedGroups.includes(charge.groupID)) return false;
|
|
708
|
+
const modSize = readAttr(mod, ATTR.CHARGE_SIZE);
|
|
709
|
+
const chargeSize = readAttr(charge, ATTR.CHARGE_SIZE);
|
|
710
|
+
if (modSize != null && chargeSize != null && Math.round(chargeSize) > Math.round(modSize)) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
var ACTIVE_EFFECT_CATEGORIES = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
716
|
+
function isActivatableModule(mod, effects) {
|
|
717
|
+
for (const e of mod.effects) {
|
|
718
|
+
const eff = effects.get(e.id);
|
|
719
|
+
if (eff && eff.effectCategoryID !== void 0 && ACTIVE_EFFECT_CATEGORIES.has(eff.effectCategoryID)) {
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
var DEFAULT_OFFLINE_ACTIVATION_GROUPS = /* @__PURE__ */ new Set([
|
|
726
|
+
330,
|
|
727
|
+
4117
|
|
728
|
+
]);
|
|
729
|
+
function defaultStateForModule(mod, effects) {
|
|
730
|
+
if (DEFAULT_OFFLINE_ACTIVATION_GROUPS.has(mod.groupID)) return "ONLINE";
|
|
731
|
+
return isActivatableModule(mod, effects) ? "ACTIVE" : "ONLINE";
|
|
732
|
+
}
|
|
733
|
+
function freeHardpointsFor(mod, ship, fittedHiModules, dataset) {
|
|
734
|
+
const turretCap = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
735
|
+
const launcherCap = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
736
|
+
let turretUsed = 0;
|
|
737
|
+
let launcherUsed = 0;
|
|
738
|
+
for (const m of fittedHiModules) {
|
|
739
|
+
const t = dataset.getType(m.typeID);
|
|
740
|
+
if (!t) continue;
|
|
741
|
+
if (isTurretWeapon(t)) turretUsed++;
|
|
742
|
+
else if (isMissileLauncher(t)) launcherUsed++;
|
|
743
|
+
}
|
|
744
|
+
let hardpointFree;
|
|
745
|
+
if (isTurretWeapon(mod)) hardpointFree = Math.max(0, turretCap - turretUsed);
|
|
746
|
+
else if (isMissileLauncher(mod)) hardpointFree = Math.max(0, launcherCap - launcherUsed);
|
|
747
|
+
else hardpointFree = Number.POSITIVE_INFINITY;
|
|
748
|
+
const groupFree = freeFitGroupSlotsFor(mod, fittedHiModules, dataset);
|
|
749
|
+
return Math.min(hardpointFree, groupFree);
|
|
750
|
+
}
|
|
751
|
+
var TACTICAL_DESTROYER_GROUP = 1305;
|
|
752
|
+
var MODED_HULL_NAMES = /* @__PURE__ */ new Set(["Anhinga"]);
|
|
753
|
+
var SHIP_MODIFIERS_GROUP = 1306;
|
|
754
|
+
function defaultModeTypeIDFor(ship, dataset) {
|
|
755
|
+
if (ship.groupID !== TACTICAL_DESTROYER_GROUP && !MODED_HULL_NAMES.has(ship.name ?? "")) return void 0;
|
|
756
|
+
const prefix = (ship.name ?? "").toLowerCase();
|
|
757
|
+
if (!prefix) return void 0;
|
|
758
|
+
let best;
|
|
759
|
+
for (const t of dataset.typesByBucket.modules?.values() ?? []) {
|
|
760
|
+
if (t.groupID !== SHIP_MODIFIERS_GROUP) continue;
|
|
761
|
+
if (!(t.name ?? "").toLowerCase().startsWith(prefix)) continue;
|
|
762
|
+
if (best === void 0 || t.id < best.id) best = t;
|
|
763
|
+
}
|
|
764
|
+
return best?.id;
|
|
765
|
+
}
|
|
766
|
+
|
|
556
767
|
// src/fitContext.ts
|
|
768
|
+
var MISSILE_BOOST_SKILLS = [
|
|
769
|
+
3319,
|
|
770
|
+
// Missile Launcher Operation
|
|
771
|
+
3323
|
|
772
|
+
// Defender Missiles
|
|
773
|
+
];
|
|
774
|
+
var PYFA_GROUP_FILTERED_EFFECTS = /* @__PURE__ */ new Map([
|
|
775
|
+
// 3495 shipCapPropulsionJamming — the Interceptor / Interdictor role bonus
|
|
776
|
+
// to Warp Scrambler + Stasis Web capacitor need. pyfa:
|
|
777
|
+
// `mod.item.group.name in ('Stasis Web', 'Warp Scrambler')`.
|
|
778
|
+
[3495, /* @__PURE__ */ new Set([52, 65])]
|
|
779
|
+
]);
|
|
557
780
|
var FitContext = class {
|
|
558
781
|
constructor(init) {
|
|
559
782
|
/** Projected hostile sources currently in scope. Populated by the
|
|
@@ -661,14 +884,19 @@ var FitContext = class {
|
|
|
661
884
|
* - EffectStopper: handled outside this resolver (stops other effects
|
|
662
885
|
* rather than applying a modifier).
|
|
663
886
|
*/
|
|
664
|
-
targetsForModifier(modifier, source) {
|
|
887
|
+
targetsForModifier(modifier, source, effectID) {
|
|
665
888
|
const root = this.resolveDomain(modifier.domain, source);
|
|
666
889
|
switch (modifier.func) {
|
|
667
890
|
case "ItemModifier":
|
|
891
|
+
if (modifier.domain === "charID" && modifier.modifiedAttributeID === ATTR.DRONE_CONTROL_RANGE) {
|
|
892
|
+
return this.ship ? [this.ship] : [];
|
|
893
|
+
}
|
|
668
894
|
if (modifier.domain === "charID" && source.kind === "module" && modifier.modifiedAttributeID === 212) {
|
|
669
895
|
const out = [];
|
|
670
896
|
for (const m of this.modules) {
|
|
671
|
-
if (m.charge)
|
|
897
|
+
if (!m.charge) continue;
|
|
898
|
+
if (!MISSILE_BOOST_SKILLS.some((sid) => itemRequiresSkill(m.charge, sid))) continue;
|
|
899
|
+
out.push(m.charge);
|
|
672
900
|
}
|
|
673
901
|
return out;
|
|
674
902
|
}
|
|
@@ -680,7 +908,10 @@ var FitContext = class {
|
|
|
680
908
|
return this.itemsInLocation(root).filter((it) => it.groupID === modifier.groupID);
|
|
681
909
|
}
|
|
682
910
|
case "LocationRequiredSkillModifier": {
|
|
683
|
-
if (!root
|
|
911
|
+
if (!root) return [];
|
|
912
|
+
const groups = effectID === void 0 ? void 0 : PYFA_GROUP_FILTERED_EFFECTS.get(effectID);
|
|
913
|
+
if (groups) return this.itemsInLocation(root).filter((it) => groups.has(it.groupID));
|
|
914
|
+
if (modifier.skillTypeID === void 0) return [];
|
|
684
915
|
const sid = modifier.skillTypeID;
|
|
685
916
|
return this.itemsInLocation(root).filter((it) => itemRequiresSkill(it, sid));
|
|
686
917
|
}
|
|
@@ -856,15 +1087,11 @@ var ModifiedAttribute = class {
|
|
|
856
1087
|
for (const a of byOp.get("PostDiv") ?? []) {
|
|
857
1088
|
postMulEntries.push({ value: 1 / a.value, stackingGroup: a.stackingGroup });
|
|
858
1089
|
}
|
|
859
|
-
if (postMulEntries.length > 0) {
|
|
860
|
-
value *= combineMultiplicative(postMulEntries);
|
|
861
|
-
}
|
|
862
|
-
const postPercentEntries = [];
|
|
863
1090
|
for (const a of byOp.get("PostPercent") ?? []) {
|
|
864
|
-
|
|
1091
|
+
postMulEntries.push({ value: 1 + a.value, stackingGroup: a.stackingGroup });
|
|
865
1092
|
}
|
|
866
|
-
if (
|
|
867
|
-
value *= combineMultiplicative(
|
|
1093
|
+
if (postMulEntries.length > 0) {
|
|
1094
|
+
value *= combineMultiplicative(postMulEntries);
|
|
868
1095
|
}
|
|
869
1096
|
const postAssign = byOp.get("PostAssign");
|
|
870
1097
|
if (postAssign && postAssign.length > 0) {
|
|
@@ -897,6 +1124,13 @@ var VOLUME_ATTR_ID = 161;
|
|
|
897
1124
|
var CAPACITY_ATTR_ID = 38;
|
|
898
1125
|
var ItemState = class {
|
|
899
1126
|
constructor(init) {
|
|
1127
|
+
/**
|
|
1128
|
+
* Attribute schema, needed to honour the SDE's attribute CAPS
|
|
1129
|
+
* (`maxAttributeID` / `minAttributeID`). Assigned by `computeFit` once for
|
|
1130
|
+
* every item in the fit; when absent, `getFinal` simply doesn't clamp — the
|
|
1131
|
+
* behaviour before caps existed.
|
|
1132
|
+
*/
|
|
1133
|
+
this.attrSchema = null;
|
|
900
1134
|
this.attrs = /* @__PURE__ */ new Map();
|
|
901
1135
|
this.kind = init.kind;
|
|
902
1136
|
this.id = init.id;
|
|
@@ -906,6 +1140,7 @@ var ItemState = class {
|
|
|
906
1140
|
this.categoryID = init.type.categoryID;
|
|
907
1141
|
this.state = init.state ?? "ONLINE";
|
|
908
1142
|
this.charge = init.charge ?? null;
|
|
1143
|
+
this.activeSideEffects = new Set(init.activeSideEffects ?? []);
|
|
909
1144
|
for (const a of init.type.attributes) {
|
|
910
1145
|
const base = init.attributeOverrides?.[a.id] ?? a.v;
|
|
911
1146
|
this.attrs.set(a.id, new ModifiedAttribute(a.id, base));
|
|
@@ -949,7 +1184,47 @@ var ItemState = class {
|
|
|
949
1184
|
getFinal(id, fallback = 0) {
|
|
950
1185
|
const ma = this.attrs.get(id);
|
|
951
1186
|
if (!ma) return fallback;
|
|
952
|
-
return ma.compute();
|
|
1187
|
+
return this.clampToCaps(id, ma.compute());
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Clamp a computed value to the ceiling / floor the SDE declares for that
|
|
1191
|
+
* attribute, mirroring pyfa's `__calculateValue` (which applies the same
|
|
1192
|
+
* bounds to forced values AND to normally-calculated ones).
|
|
1193
|
+
*
|
|
1194
|
+
* An attribute may name another attribute as its bound — resonances are
|
|
1195
|
+
* capped by `<layer>MaxDamageResonance` (1.0), `maxVelocity` by
|
|
1196
|
+
* `speedLimit`, the `*Bonus` attributes floored by `ConstantMinusNinetyNine`
|
|
1197
|
+
* — and the bound is read off THIS item, so a hull that raises its own
|
|
1198
|
+
* ceiling is honoured.
|
|
1199
|
+
*
|
|
1200
|
+
* Without it a Polarized weapon, which force-assigns every resonance to
|
|
1201
|
+
* `resistanceKiller` = 100, read as -9900% resistance instead of the 0% the
|
|
1202
|
+
* game shows: the cap is what turns "assign 100" into "resistances removed".
|
|
1203
|
+
* Measured on real published fits — five hulls, forty stats.
|
|
1204
|
+
*/
|
|
1205
|
+
clampToCaps(id, value) {
|
|
1206
|
+
const schema = this.attrSchema?.get(id);
|
|
1207
|
+
if (!schema) return value;
|
|
1208
|
+
let out = value;
|
|
1209
|
+
if (schema.minAttributeID !== void 0) {
|
|
1210
|
+
const floor = this.capValue(schema.minAttributeID);
|
|
1211
|
+
if (floor !== void 0) out = Math.max(out, floor);
|
|
1212
|
+
}
|
|
1213
|
+
if (schema.maxAttributeID !== void 0) {
|
|
1214
|
+
const ceil = this.capValue(schema.maxAttributeID);
|
|
1215
|
+
if (ceil !== void 0) out = Math.min(out, ceil);
|
|
1216
|
+
}
|
|
1217
|
+
return out;
|
|
1218
|
+
}
|
|
1219
|
+
/** Value of a bounding attribute on this item, falling back to the SDE
|
|
1220
|
+
* default when the item doesn't carry it (pyfa's `getOriginal(key,
|
|
1221
|
+
* default)`). Bounds never declare bounds of their own, so this recurses
|
|
1222
|
+
* exactly one level. */
|
|
1223
|
+
capValue(capAttrID) {
|
|
1224
|
+
const ma = this.attrs.get(capAttrID);
|
|
1225
|
+
if (ma) return ma.compute();
|
|
1226
|
+
const def = this.attrSchema?.get(capAttrID)?.defaultValue;
|
|
1227
|
+
return def;
|
|
953
1228
|
}
|
|
954
1229
|
/** `defaultBase` is the seed base value when the target attribute isn't
|
|
955
1230
|
* in the item's typeDogma — needed for SDE attrs whose `defaultValue`
|
|
@@ -1067,7 +1342,13 @@ function makeImplantState(fi, type) {
|
|
|
1067
1342
|
return new ItemState({ kind: "implant", id: fi.id, type, state: "ONLINE" });
|
|
1068
1343
|
}
|
|
1069
1344
|
function makeBoosterState(fb, type) {
|
|
1070
|
-
return new ItemState({
|
|
1345
|
+
return new ItemState({
|
|
1346
|
+
kind: "booster",
|
|
1347
|
+
id: fb.id,
|
|
1348
|
+
type,
|
|
1349
|
+
state: "ONLINE",
|
|
1350
|
+
activeSideEffects: fb.activeSideEffects ?? []
|
|
1351
|
+
});
|
|
1071
1352
|
}
|
|
1072
1353
|
function makeSubsystemState(fs, type) {
|
|
1073
1354
|
return new ItemState({ kind: "subsystem", id: fs.id, type, state: "ONLINE" });
|
|
@@ -1076,312 +1357,347 @@ function makeModeState(modeTypeID, type) {
|
|
|
1076
1357
|
return new ItemState({ kind: "mode", id: `mode:${modeTypeID}`, type, state: "ACTIVE" });
|
|
1077
1358
|
}
|
|
1078
1359
|
|
|
1079
|
-
// src/
|
|
1080
|
-
var
|
|
1081
|
-
[89, "default"],
|
|
1082
|
-
[91, "default"],
|
|
1083
|
-
[92, "default"],
|
|
1084
|
-
[93, "default"],
|
|
1085
|
-
[95, "default"],
|
|
1086
|
-
[96, "default"],
|
|
1087
|
-
[394, "default"],
|
|
1088
|
-
[395, "default"],
|
|
1089
|
-
[
|
|
1090
|
-
[
|
|
1091
|
-
[
|
|
1092
|
-
[
|
|
1093
|
-
[
|
|
1094
|
-
[
|
|
1095
|
-
[
|
|
1096
|
-
[
|
|
1097
|
-
[
|
|
1098
|
-
[
|
|
1099
|
-
[
|
|
1100
|
-
[
|
|
1101
|
-
[
|
|
1102
|
-
[
|
|
1103
|
-
[
|
|
1104
|
-
[
|
|
1105
|
-
[
|
|
1106
|
-
[
|
|
1107
|
-
[
|
|
1108
|
-
[
|
|
1109
|
-
[
|
|
1110
|
-
[
|
|
1111
|
-
[
|
|
1112
|
-
[
|
|
1113
|
-
[
|
|
1114
|
-
[
|
|
1115
|
-
[
|
|
1116
|
-
[
|
|
1117
|
-
[
|
|
1118
|
-
[
|
|
1119
|
-
[
|
|
1120
|
-
[
|
|
1121
|
-
[
|
|
1122
|
-
[
|
|
1123
|
-
[
|
|
1124
|
-
[
|
|
1125
|
-
[
|
|
1126
|
-
[
|
|
1127
|
-
[
|
|
1128
|
-
[
|
|
1129
|
-
[
|
|
1130
|
-
[
|
|
1131
|
-
[
|
|
1132
|
-
[
|
|
1133
|
-
[
|
|
1134
|
-
[
|
|
1135
|
-
[
|
|
1136
|
-
[
|
|
1137
|
-
[
|
|
1138
|
-
[
|
|
1139
|
-
[
|
|
1140
|
-
[
|
|
1141
|
-
[
|
|
1142
|
-
[
|
|
1143
|
-
[
|
|
1144
|
-
[
|
|
1145
|
-
[
|
|
1146
|
-
[
|
|
1147
|
-
[
|
|
1148
|
-
[
|
|
1149
|
-
[
|
|
1150
|
-
[
|
|
1151
|
-
[
|
|
1152
|
-
[
|
|
1153
|
-
[
|
|
1154
|
-
[
|
|
1155
|
-
[
|
|
1156
|
-
[
|
|
1157
|
-
[
|
|
1158
|
-
[
|
|
1159
|
-
[
|
|
1160
|
-
[
|
|
1161
|
-
[
|
|
1162
|
-
[
|
|
1163
|
-
[
|
|
1164
|
-
[
|
|
1165
|
-
[
|
|
1166
|
-
[
|
|
1167
|
-
[
|
|
1168
|
-
[
|
|
1169
|
-
[
|
|
1170
|
-
[
|
|
1171
|
-
[
|
|
1172
|
-
[
|
|
1173
|
-
[
|
|
1174
|
-
[
|
|
1175
|
-
[
|
|
1176
|
-
[
|
|
1177
|
-
[
|
|
1178
|
-
[
|
|
1179
|
-
[
|
|
1180
|
-
[
|
|
1181
|
-
[
|
|
1182
|
-
[
|
|
1183
|
-
[
|
|
1184
|
-
[
|
|
1185
|
-
[
|
|
1186
|
-
[
|
|
1187
|
-
[
|
|
1188
|
-
[
|
|
1189
|
-
[
|
|
1190
|
-
[
|
|
1191
|
-
[
|
|
1192
|
-
[
|
|
1193
|
-
[
|
|
1194
|
-
[
|
|
1195
|
-
[
|
|
1196
|
-
[
|
|
1197
|
-
[
|
|
1198
|
-
[
|
|
1199
|
-
[
|
|
1200
|
-
[
|
|
1201
|
-
[
|
|
1202
|
-
[
|
|
1203
|
-
[
|
|
1204
|
-
[
|
|
1205
|
-
[
|
|
1206
|
-
[
|
|
1207
|
-
[
|
|
1208
|
-
[
|
|
1209
|
-
[
|
|
1210
|
-
[
|
|
1211
|
-
[
|
|
1212
|
-
[
|
|
1213
|
-
[
|
|
1214
|
-
[
|
|
1215
|
-
[
|
|
1216
|
-
[
|
|
1217
|
-
[
|
|
1218
|
-
[
|
|
1219
|
-
[
|
|
1220
|
-
[
|
|
1221
|
-
[
|
|
1222
|
-
[
|
|
1223
|
-
[
|
|
1224
|
-
[
|
|
1225
|
-
[
|
|
1226
|
-
[
|
|
1227
|
-
[
|
|
1228
|
-
[
|
|
1229
|
-
[
|
|
1230
|
-
[
|
|
1231
|
-
[
|
|
1232
|
-
[
|
|
1233
|
-
[
|
|
1234
|
-
[
|
|
1235
|
-
[
|
|
1236
|
-
[
|
|
1237
|
-
[
|
|
1238
|
-
[
|
|
1239
|
-
[
|
|
1240
|
-
[
|
|
1241
|
-
[
|
|
1242
|
-
[
|
|
1243
|
-
[
|
|
1244
|
-
[
|
|
1245
|
-
[
|
|
1246
|
-
[
|
|
1247
|
-
[
|
|
1248
|
-
[
|
|
1249
|
-
[
|
|
1250
|
-
[
|
|
1251
|
-
[
|
|
1252
|
-
[
|
|
1253
|
-
[
|
|
1254
|
-
[
|
|
1255
|
-
[
|
|
1256
|
-
[
|
|
1257
|
-
[
|
|
1258
|
-
[
|
|
1259
|
-
[
|
|
1260
|
-
[
|
|
1261
|
-
[
|
|
1262
|
-
[
|
|
1263
|
-
[
|
|
1264
|
-
[
|
|
1265
|
-
[
|
|
1266
|
-
[
|
|
1267
|
-
[
|
|
1268
|
-
[
|
|
1269
|
-
[
|
|
1270
|
-
[
|
|
1271
|
-
[
|
|
1272
|
-
[
|
|
1273
|
-
[
|
|
1274
|
-
[
|
|
1275
|
-
[
|
|
1276
|
-
[
|
|
1277
|
-
[
|
|
1278
|
-
[
|
|
1279
|
-
[
|
|
1280
|
-
[
|
|
1281
|
-
[
|
|
1282
|
-
[
|
|
1283
|
-
[
|
|
1284
|
-
[
|
|
1285
|
-
[
|
|
1286
|
-
[
|
|
1287
|
-
[
|
|
1288
|
-
[
|
|
1289
|
-
[
|
|
1290
|
-
[
|
|
1291
|
-
[
|
|
1292
|
-
[
|
|
1293
|
-
[
|
|
1294
|
-
[
|
|
1295
|
-
[
|
|
1296
|
-
[
|
|
1297
|
-
[
|
|
1298
|
-
[
|
|
1299
|
-
[
|
|
1300
|
-
[
|
|
1301
|
-
[
|
|
1302
|
-
[
|
|
1303
|
-
[
|
|
1304
|
-
[
|
|
1305
|
-
[
|
|
1306
|
-
[
|
|
1307
|
-
[
|
|
1308
|
-
[
|
|
1309
|
-
[
|
|
1310
|
-
[
|
|
1311
|
-
[
|
|
1312
|
-
[
|
|
1313
|
-
[
|
|
1314
|
-
[
|
|
1315
|
-
[
|
|
1316
|
-
[
|
|
1317
|
-
[
|
|
1318
|
-
[
|
|
1319
|
-
[
|
|
1320
|
-
[
|
|
1321
|
-
[
|
|
1322
|
-
[
|
|
1323
|
-
[
|
|
1324
|
-
[
|
|
1325
|
-
[
|
|
1326
|
-
[
|
|
1327
|
-
[
|
|
1328
|
-
[
|
|
1329
|
-
[
|
|
1330
|
-
[
|
|
1331
|
-
[
|
|
1332
|
-
[
|
|
1333
|
-
[
|
|
1334
|
-
[
|
|
1335
|
-
[
|
|
1336
|
-
[
|
|
1337
|
-
[
|
|
1338
|
-
[
|
|
1339
|
-
[
|
|
1340
|
-
[
|
|
1341
|
-
[
|
|
1342
|
-
[
|
|
1343
|
-
[
|
|
1344
|
-
[
|
|
1345
|
-
[
|
|
1346
|
-
[
|
|
1347
|
-
[
|
|
1348
|
-
[
|
|
1349
|
-
[
|
|
1350
|
-
[
|
|
1351
|
-
[
|
|
1352
|
-
[
|
|
1353
|
-
[
|
|
1354
|
-
[
|
|
1355
|
-
[
|
|
1356
|
-
[
|
|
1357
|
-
[
|
|
1358
|
-
[
|
|
1359
|
-
[
|
|
1360
|
-
[
|
|
1361
|
-
[
|
|
1362
|
-
[
|
|
1363
|
-
[
|
|
1364
|
-
[
|
|
1365
|
-
[
|
|
1366
|
-
[
|
|
1367
|
-
[
|
|
1368
|
-
[12838, "postMul"],
|
|
1369
|
-
[12839, "postMul"]
|
|
1360
|
+
// src/stackingPenalised.ts
|
|
1361
|
+
var STACKING_PENALISED = /* @__PURE__ */ new Map([
|
|
1362
|
+
[89, ["51#default"]],
|
|
1363
|
+
[91, ["64#default"]],
|
|
1364
|
+
[92, ["64#default"]],
|
|
1365
|
+
[93, ["64#default"]],
|
|
1366
|
+
[95, ["51#default"]],
|
|
1367
|
+
[96, ["51#default"]],
|
|
1368
|
+
[394, ["37#default!booster+implant+skill"]],
|
|
1369
|
+
[395, ["70#default!booster+implant+skill"]],
|
|
1370
|
+
[607, ["37#postMul"]],
|
|
1371
|
+
[657, ["70#default"]],
|
|
1372
|
+
[699, ["564#default!booster+implant+skill"]],
|
|
1373
|
+
[763, ["*#default"]],
|
|
1374
|
+
[784, ["281#default!booster+implant+skill"]],
|
|
1375
|
+
[854, ["564#cloakingScanResolutionMultiplier"]],
|
|
1376
|
+
[856, ["600#default!booster+implant+skill"]],
|
|
1377
|
+
[889, ["51#default"]],
|
|
1378
|
+
[891, ["37#default!ship"]],
|
|
1379
|
+
[892, ["37#default!ship"]],
|
|
1380
|
+
[1024, ["37#default!ship"]],
|
|
1381
|
+
[1230, ["37#default!ship"]],
|
|
1382
|
+
[1281, ["84#default!implant"]],
|
|
1383
|
+
[1445, ["54#default!booster+skill"]],
|
|
1384
|
+
[1446, ["54#default!booster+skill"]],
|
|
1385
|
+
[1448, ["54#default!booster+skill"]],
|
|
1386
|
+
[1472, ["654#default!booster+implant+skill"]],
|
|
1387
|
+
[1590, ["653#default!booster+implant+skill"]],
|
|
1388
|
+
[1617, ["70#default"]],
|
|
1389
|
+
[1720, ["68#default"]],
|
|
1390
|
+
[1764, ["37#default!booster+implant+skill"]],
|
|
1391
|
+
[1885, ["51#default!ship"]],
|
|
1392
|
+
[1886, ["51#default!ship"]],
|
|
1393
|
+
[2013, ["37#default!booster+implant"]],
|
|
1394
|
+
[2014, ["54#default!skill"]],
|
|
1395
|
+
[2019, ["68#default!implant+skill"]],
|
|
1396
|
+
[2020, ["84#default!implant+skill"]],
|
|
1397
|
+
[2041, ["*#default"]],
|
|
1398
|
+
[2052, ["*#default"]],
|
|
1399
|
+
[2152, ["37#default"]],
|
|
1400
|
+
[2232, ["*#default"]],
|
|
1401
|
+
[2302, ["*#preMul"]],
|
|
1402
|
+
[2644, ["552#default"]],
|
|
1403
|
+
[2645, ["564#default"]],
|
|
1404
|
+
[2646, ["76#default"]],
|
|
1405
|
+
[2670, ["*#default", "564#default", "76#default"]],
|
|
1406
|
+
[2693, ["158#default!booster"]],
|
|
1407
|
+
[2694, ["158#default!booster"]],
|
|
1408
|
+
[2695, ["158#default!booster"]],
|
|
1409
|
+
[2696, ["54#default!booster"]],
|
|
1410
|
+
[2697, ["54#default!booster"]],
|
|
1411
|
+
[2698, ["54#default!booster"]],
|
|
1412
|
+
[2716, ["552#default"]],
|
|
1413
|
+
[2717, ["70#default"]],
|
|
1414
|
+
[2792, ["*#default"]],
|
|
1415
|
+
[2795, ["*#default"]],
|
|
1416
|
+
[2796, ["4#default"]],
|
|
1417
|
+
[2797, ["51#default"]],
|
|
1418
|
+
[2798, ["64#default!booster"]],
|
|
1419
|
+
[2799, ["51#default"]],
|
|
1420
|
+
[2801, ["51#default"]],
|
|
1421
|
+
[2802, ["64#default!booster"]],
|
|
1422
|
+
[2803, ["64#default!booster"]],
|
|
1423
|
+
[2804, ["51#default"]],
|
|
1424
|
+
[2851, ["*#default!booster"]],
|
|
1425
|
+
[2858, ["37#default"]],
|
|
1426
|
+
[2865, ["37#default"]],
|
|
1427
|
+
[2867, ["64#default"]],
|
|
1428
|
+
[2868, ["84#default"]],
|
|
1429
|
+
[3001, ["51#postPerc"]],
|
|
1430
|
+
[3046, ["37#default"]],
|
|
1431
|
+
[3174, ["54#default"]],
|
|
1432
|
+
[3175, ["20#default"]],
|
|
1433
|
+
[3182, ["*#default"]],
|
|
1434
|
+
[3200, ["84#default"]],
|
|
1435
|
+
[3201, ["68#default"]],
|
|
1436
|
+
[3586, ["566#default!skill"]],
|
|
1437
|
+
[3655, ["54#default"]],
|
|
1438
|
+
[3656, ["160#default"]],
|
|
1439
|
+
[3657, ["564#default"]],
|
|
1440
|
+
[3659, ["76#default"]],
|
|
1441
|
+
[3674, ["37#default"]],
|
|
1442
|
+
[3726, ["70#default"]],
|
|
1443
|
+
[3727, ["37#default"]],
|
|
1444
|
+
[3993, ["76#postMul"]],
|
|
1445
|
+
[3995, ["552#postMul"]],
|
|
1446
|
+
[3996, ["267#default"]],
|
|
1447
|
+
[3997, ["268#default"]],
|
|
1448
|
+
[3998, ["269#default"]],
|
|
1449
|
+
[3999, ["270#default"]],
|
|
1450
|
+
[4002, ["37#postMul"]],
|
|
1451
|
+
[4003, ["37#postMul"]],
|
|
1452
|
+
[4016, ["64#default"]],
|
|
1453
|
+
[4017, ["118#postMul"]],
|
|
1454
|
+
[4018, ["114#postMul"]],
|
|
1455
|
+
[4019, ["116#postMul"]],
|
|
1456
|
+
[4020, ["117#postMul"]],
|
|
1457
|
+
[4021, ["64#postMul"]],
|
|
1458
|
+
[4022, ["160#postMul"]],
|
|
1459
|
+
[4023, ["653#postMul"]],
|
|
1460
|
+
[4054, ["64#default"]],
|
|
1461
|
+
[4055, ["64#default"]],
|
|
1462
|
+
[4056, ["64#default"]],
|
|
1463
|
+
[4057, ["114#postMul"]],
|
|
1464
|
+
[4058, ["116#postMul"]],
|
|
1465
|
+
[4059, ["117#postMul"]],
|
|
1466
|
+
[4060, ["118#postMul"]],
|
|
1467
|
+
[4061, ["118#postMul"]],
|
|
1468
|
+
[4062, ["114#postMul"]],
|
|
1469
|
+
[4063, ["116#postMul"]],
|
|
1470
|
+
[4086, ["84#postMul"]],
|
|
1471
|
+
[4088, ["84#postMul"]],
|
|
1472
|
+
[4089, ["68#postMul"]],
|
|
1473
|
+
[4135, ["271#default"]],
|
|
1474
|
+
[4136, ["272#default"]],
|
|
1475
|
+
[4137, ["273#default"]],
|
|
1476
|
+
[4138, ["274#default"]],
|
|
1477
|
+
[4162, ["1371#default!booster+implant+skill"]],
|
|
1478
|
+
[4280, ["70#default"]],
|
|
1479
|
+
[4358, ["54#default"]],
|
|
1480
|
+
[4464, ["51#default"]],
|
|
1481
|
+
[4489, ["37#default"]],
|
|
1482
|
+
[4490, ["37#default"]],
|
|
1483
|
+
[4491, ["37#default"]],
|
|
1484
|
+
[4492, ["37#default"]],
|
|
1485
|
+
[4527, ["158#default"]],
|
|
1486
|
+
[4559, ["*#default"]],
|
|
1487
|
+
[4575, ["2044#default", "37#default", "54#default", "564#default", "64#default", "68#default"]],
|
|
1488
|
+
[4809, ["238#default"]],
|
|
1489
|
+
[4810, ["239#default"]],
|
|
1490
|
+
[4811, ["240#default"]],
|
|
1491
|
+
[4812, ["241#default"]],
|
|
1492
|
+
[4906, ["64#postMul"]],
|
|
1493
|
+
[4928, ["*#preMul"]],
|
|
1494
|
+
[4961, ["68#postMul"]],
|
|
1495
|
+
[5081, ["76#default"]],
|
|
1496
|
+
[5188, ["160#default!booster"]],
|
|
1497
|
+
[5189, ["160#default!booster"]],
|
|
1498
|
+
[5190, ["160#default!booster"]],
|
|
1499
|
+
[5213, ["37#default!ship"]],
|
|
1500
|
+
[5214, ["37#default!ship"]],
|
|
1501
|
+
[5230, ["*#default"]],
|
|
1502
|
+
[5231, ["*#default"]],
|
|
1503
|
+
[5397, ["1372#default"]],
|
|
1504
|
+
[5399, ["1371#default"]],
|
|
1505
|
+
[5440, ["117#postMul"]],
|
|
1506
|
+
[5468, ["70#default!ship"]],
|
|
1507
|
+
[5560, ["669#default!ship"]],
|
|
1508
|
+
[5618, ["51#default!ship"]],
|
|
1509
|
+
[5757, ["*#default", "566#default"]],
|
|
1510
|
+
[5867, ["281#default!ship"]],
|
|
1511
|
+
[5911, ["868#default"]],
|
|
1512
|
+
[5912, ["90#postMul"]],
|
|
1513
|
+
[5914, ["97#postMul"]],
|
|
1514
|
+
[5915, ["90#postMul"]],
|
|
1515
|
+
[5916, ["116#postMul"]],
|
|
1516
|
+
[5917, ["117#postMul"]],
|
|
1517
|
+
[5918, ["118#postMul"]],
|
|
1518
|
+
[5919, ["114#postMul"]],
|
|
1519
|
+
[5920, ["654#postMul"]],
|
|
1520
|
+
[5921, ["554#postMul"]],
|
|
1521
|
+
[5922, ["20#postMul"]],
|
|
1522
|
+
[5923, ["97#postMul"]],
|
|
1523
|
+
[5924, ["238#postMul"]],
|
|
1524
|
+
[5925, ["239#postMul"]],
|
|
1525
|
+
[5926, ["240#postMul"]],
|
|
1526
|
+
[5927, ["241#postMul"]],
|
|
1527
|
+
[5929, ["160#postMul"]],
|
|
1528
|
+
[5951, ["600#default"]],
|
|
1529
|
+
[5998, ["70#default"]],
|
|
1530
|
+
[6001, ["70#default"]],
|
|
1531
|
+
[6010, ["76#postDiv"]],
|
|
1532
|
+
[6011, ["54#postDiv"]],
|
|
1533
|
+
[6012, ["*#postDiv"]],
|
|
1534
|
+
[6014, ["552#postDiv"]],
|
|
1535
|
+
[6015, ["*#postDiv"]],
|
|
1536
|
+
[6016, ["70#postDiv"]],
|
|
1537
|
+
[6017, ["37#postDiv"]],
|
|
1538
|
+
[6039, ["160#postDiv"]],
|
|
1539
|
+
[6040, ["554#postDiv"]],
|
|
1540
|
+
[6041, ["*#postDiv"]],
|
|
1541
|
+
[6063, ["*#default"]],
|
|
1542
|
+
[6076, ["37#postDiv"]],
|
|
1543
|
+
[6110, ["37#default"]],
|
|
1544
|
+
[6111, ["281#default"]],
|
|
1545
|
+
[6112, ["654#default"]],
|
|
1546
|
+
[6113, ["653#default"]],
|
|
1547
|
+
[6135, ["*#default"]],
|
|
1548
|
+
[6152, ["54#postDiv"]],
|
|
1549
|
+
[6154, ["20#postDiv"]],
|
|
1550
|
+
[6164, ["37#default"]],
|
|
1551
|
+
[6201, ["37#default"]],
|
|
1552
|
+
[6208, ["552#default"]],
|
|
1553
|
+
[6402, ["653#default"]],
|
|
1554
|
+
[6403, ["37#default"]],
|
|
1555
|
+
[6404, ["2044#default", "54#default"]],
|
|
1556
|
+
[6405, ["6#default"]],
|
|
1557
|
+
[6406, ["158#default", "2044#default", "54#default"]],
|
|
1558
|
+
[6409, ["564#default"]],
|
|
1559
|
+
[6410, ["654#default"]],
|
|
1560
|
+
[6411, ["37#default"]],
|
|
1561
|
+
[6412, ["99#default"]],
|
|
1562
|
+
[6422, ["564#default", "76#default"]],
|
|
1563
|
+
[6423, ["*#default"]],
|
|
1564
|
+
[6424, ["*#default"]],
|
|
1565
|
+
[6425, ["552#default"]],
|
|
1566
|
+
[6426, ["37#default"]],
|
|
1567
|
+
[6427, ["*#default"]],
|
|
1568
|
+
[6428, ["*#default"]],
|
|
1569
|
+
[6435, ["37#default"]],
|
|
1570
|
+
[6439, ["271#default", "272#default", "273#default", "274#default", "37#default", "552#default"]],
|
|
1571
|
+
[6440, ["37#default"]],
|
|
1572
|
+
[6441, ["37#default", "552#default"]],
|
|
1573
|
+
[6448, ["*#default"]],
|
|
1574
|
+
[6449, ["*#default", "51#default"]],
|
|
1575
|
+
[6472, ["37#default"]],
|
|
1576
|
+
[6473, ["37#default"]],
|
|
1577
|
+
[6474, ["37#default"]],
|
|
1578
|
+
[6476, ["37#default"]],
|
|
1579
|
+
[6478, ["552#default"]],
|
|
1580
|
+
[6479, ["*#default"]],
|
|
1581
|
+
[6481, ["564#default", "76#default"]],
|
|
1582
|
+
[6484, ["*#postMul"]],
|
|
1583
|
+
[6487, ["2045#default"]],
|
|
1584
|
+
[6555, ["37#default"]],
|
|
1585
|
+
[6556, ["2130#default", "2178#default", "2226#default", "64#default"]],
|
|
1586
|
+
[6557, ["158#default", "160#default", "2125#default", "2126#default", "2149#default", "2175#default", "2176#default", "2180#default", "2234#default", "2235#default", "2236#default", "2237#default", "54#default"]],
|
|
1587
|
+
[6559, ["158#default", "160#default", "2125#default", "2126#default", "2149#default", "2175#default", "2176#default", "2180#default", "2234#default", "2235#default", "2236#default", "2237#default", "54#default"]],
|
|
1588
|
+
[6566, ["2177#postMul", "2182#postMul", "2233#postMul", "37#postMul"]],
|
|
1589
|
+
[6567, ["*#default", "564#default"]],
|
|
1590
|
+
[6581, ["*#default", "2044#default", "37#default", "4#default", "54#default", "564#default", "64#default"]],
|
|
1591
|
+
[6582, ["4#postMul", "68#default", "84#default", "charge:37#default", "module:51#postPercent"]],
|
|
1592
|
+
[6658, ["158#default", "51#postPerc", "54#default", "68#default", "84#default", "charge:37#default", "ship:*#default", "ship:*#preMul"]],
|
|
1593
|
+
[6670, ["2149#default", "2175#default", "2236#default", "54#default"]],
|
|
1594
|
+
[6671, ["37#default"]],
|
|
1595
|
+
[6682, ["37#default"]],
|
|
1596
|
+
[6683, ["552#default"]],
|
|
1597
|
+
[6684, ["564#default", "76#default"]],
|
|
1598
|
+
[6686, ["*#default"]],
|
|
1599
|
+
[6690, ["37#default"]],
|
|
1600
|
+
[6692, ["552#default"]],
|
|
1601
|
+
[6693, ["564#default", "76#default"]],
|
|
1602
|
+
[6694, ["*#default"]],
|
|
1603
|
+
[6727, ["2044#default"]],
|
|
1604
|
+
[6730, ["37#postMul", "552#default"]],
|
|
1605
|
+
[6731, ["37#postMul"]],
|
|
1606
|
+
[6796, ["64#postDiv"]],
|
|
1607
|
+
[6797, ["64#postDiv"]],
|
|
1608
|
+
[6798, ["64#postDiv"]],
|
|
1609
|
+
[6799, ["*#postDiv"]],
|
|
1610
|
+
[6801, ["20#postDiv"]],
|
|
1611
|
+
[6877, ["600#default"]],
|
|
1612
|
+
[7029, ["2751#default"]],
|
|
1613
|
+
[7077, ["64#default"]],
|
|
1614
|
+
[7078, ["51#default"]],
|
|
1615
|
+
[7098, ["564#default", "76#default"]],
|
|
1616
|
+
[7111, ["64#default"]],
|
|
1617
|
+
[7142, ["20#default", "37#default", "4#default"]],
|
|
1618
|
+
[7202, ["37#default"]],
|
|
1619
|
+
[7203, ["64#default"]],
|
|
1620
|
+
[7223, ["70#default"]],
|
|
1621
|
+
[7237, ["600#default"]],
|
|
1622
|
+
[8033, ["54#default"]],
|
|
1623
|
+
[8057, ["51#default", "64#default"]],
|
|
1624
|
+
[8076, ["110#default"]],
|
|
1625
|
+
[8082, ["564#default"]],
|
|
1626
|
+
[8108, ["552#postMul"]],
|
|
1627
|
+
[8109, ["552#postMul"]],
|
|
1628
|
+
[8111, ["113#default", "1371#default", "267#default", "271#default"]],
|
|
1629
|
+
[8112, ["109#default", "269#default", "273#default", "564#default", "600#default"]],
|
|
1630
|
+
[8113, ["111#default", "268#default", "272#default", "552#postMul", "68#postMul", "84#postMul"]],
|
|
1631
|
+
[8114, ["*#postMul", "110#default", "160#postMul", "270#default", "274#default", "654#postMul", "drone:64#postMul", "fighter:64#postMul", "module:64#default"]],
|
|
1632
|
+
[8119, ["37#default", "54#default", "64#default", "68#default"]],
|
|
1633
|
+
[11445, ["*#default"]],
|
|
1634
|
+
[11691, ["37#default"]],
|
|
1635
|
+
[11946, ["64#default"]],
|
|
1636
|
+
[11947, ["654#postMul"]],
|
|
1637
|
+
[11948, ["64#postMul"]],
|
|
1638
|
+
[11953, ["653#postMul"]],
|
|
1639
|
+
[12126, ["552#default"]],
|
|
1640
|
+
[12597, ["*#default", "564#default", "76#default"]],
|
|
1641
|
+
[12761, ["3154#default"]],
|
|
1642
|
+
[12794, ["37#postDiv"]],
|
|
1643
|
+
[12795, ["37#postDiv"]],
|
|
1644
|
+
[12796, ["281#postDiv"]],
|
|
1645
|
+
[12798, ["37#postDiv"]],
|
|
1646
|
+
[12799, ["51#postDiv"]],
|
|
1647
|
+
[12838, ["1371#postMul"]],
|
|
1648
|
+
[12839, ["1372#postMul"]]
|
|
1370
1649
|
]);
|
|
1650
|
+
var CONTEXT_KIND = {
|
|
1651
|
+
skill: ["skill", "character"],
|
|
1652
|
+
ship: ["ship"],
|
|
1653
|
+
implant: ["implant"],
|
|
1654
|
+
booster: ["booster"],
|
|
1655
|
+
module: ["module"],
|
|
1656
|
+
drone: ["drone"],
|
|
1657
|
+
fighter: ["fighter"],
|
|
1658
|
+
subsystem: ["subsystem"],
|
|
1659
|
+
mode: ["mode"]
|
|
1660
|
+
};
|
|
1661
|
+
function stackPenaltyGroup(effectID, attributeID, receiver, sourceKind) {
|
|
1662
|
+
const keys = STACKING_PENALISED.get(effectID);
|
|
1663
|
+
if (!keys) return null;
|
|
1664
|
+
const id = attributeID === void 0 ? null : String(attributeID);
|
|
1665
|
+
for (const key of keys) {
|
|
1666
|
+
const bang = key.indexOf("!");
|
|
1667
|
+
const exempt = bang < 0 ? [] : key.slice(bang + 1).split("+");
|
|
1668
|
+
const head = bang < 0 ? key : key.slice(0, bang);
|
|
1669
|
+
const hash = head.lastIndexOf("#");
|
|
1670
|
+
const group = head.slice(hash + 1);
|
|
1671
|
+
const target = head.slice(0, hash);
|
|
1672
|
+
const colon = target.indexOf(":");
|
|
1673
|
+
const recv = colon < 0 ? null : target.slice(0, colon);
|
|
1674
|
+
const attr = colon < 0 ? target : target.slice(colon + 1);
|
|
1675
|
+
if (recv !== null && recv !== receiver) continue;
|
|
1676
|
+
if (attr !== "*" && (id === null || attr !== id)) continue;
|
|
1677
|
+
if (exempt.some((c) => (CONTEXT_KIND[c] ?? []).includes(sourceKind))) return null;
|
|
1678
|
+
return group;
|
|
1679
|
+
}
|
|
1680
|
+
return null;
|
|
1681
|
+
}
|
|
1371
1682
|
|
|
1372
1683
|
// src/modifierEngine.ts
|
|
1373
1684
|
var NO_PENALTY_KINDS = /* @__PURE__ */ new Set([
|
|
1374
1685
|
"skill",
|
|
1375
|
-
"
|
|
1376
|
-
"
|
|
1377
|
-
"
|
|
1378
|
-
"character"
|
|
1686
|
+
"character",
|
|
1687
|
+
"implant",
|
|
1688
|
+
"booster"
|
|
1379
1689
|
]);
|
|
1380
1690
|
function scaleForPipeline(rawValue, _unitID, op) {
|
|
1381
1691
|
if (op === "PostPercent") return rawValue / 100;
|
|
1382
1692
|
return rawValue;
|
|
1383
1693
|
}
|
|
1384
|
-
function
|
|
1694
|
+
function isPeerModifier(source, mi, ctx, effectID) {
|
|
1695
|
+
for (const t of ctx.targetsForModifier(mi, source, effectID)) {
|
|
1696
|
+
if (t.kind === "implant" || t.kind === "booster") return true;
|
|
1697
|
+
}
|
|
1698
|
+
return false;
|
|
1699
|
+
}
|
|
1700
|
+
function applySourceItem(source, ctx, dataset, pass = "all") {
|
|
1385
1701
|
const isLocalModule = source.kind === "module";
|
|
1386
1702
|
const selfMods = [];
|
|
1387
1703
|
const outMods = [];
|
|
@@ -1389,10 +1705,15 @@ function applySourceItem(source, ctx, dataset) {
|
|
|
1389
1705
|
if (LEGACY_HANDLED_EFFECT_IDS.has(eid)) continue;
|
|
1390
1706
|
if (SEC_STATUS_SCALED_EFFECT_IDS.has(eid)) continue;
|
|
1391
1707
|
if (isLocalModule && ctx.stoppedLocalEffectIDs.has(eid)) continue;
|
|
1708
|
+
if (source.kind === "booster" && BOOSTER_SIDE_EFFECT_IDS.has(eid) && !source.activeSideEffects.has(eid)) continue;
|
|
1392
1709
|
const effect = dataset.effects.get(eid);
|
|
1393
1710
|
if (!effect) continue;
|
|
1394
1711
|
if (!source.appliesAtState(effect)) continue;
|
|
1395
1712
|
for (const mi of effect.modifierInfo) {
|
|
1713
|
+
if (pass !== "all") {
|
|
1714
|
+
const peer = isPeerModifier(source, mi, ctx, effect.id);
|
|
1715
|
+
if (pass === "peer" ? !peer : peer) continue;
|
|
1716
|
+
}
|
|
1396
1717
|
(mi.domain === "itemID" ? selfMods : outMods).push({ effect, mi });
|
|
1397
1718
|
}
|
|
1398
1719
|
}
|
|
@@ -1435,7 +1756,7 @@ function applyLegacyFighterProjection(fighter, ctx) {
|
|
|
1435
1756
|
sourceID: fighter.id,
|
|
1436
1757
|
operation: "PostPercent",
|
|
1437
1758
|
value: pen / 100,
|
|
1438
|
-
stackingGroup: `
|
|
1759
|
+
stackingGroup: `default:${ATTR_MAX_VEL}`
|
|
1439
1760
|
});
|
|
1440
1761
|
}
|
|
1441
1762
|
}
|
|
@@ -1461,9 +1782,8 @@ function applyOneModifier(source, effect, mi, ctx, dataset) {
|
|
|
1461
1782
|
if (mi.modifiedAttributeID === void 0) return;
|
|
1462
1783
|
const computed = computeModifierValue(source, mi, ctx, dataset, op);
|
|
1463
1784
|
if (computed === null) return;
|
|
1464
|
-
const targets = ctx.targetsForModifier(mi, source);
|
|
1785
|
+
const targets = ctx.targetsForModifier(mi, source, effect.id);
|
|
1465
1786
|
if (targets.length === 0) return;
|
|
1466
|
-
const stackingGroup = computeStackingGroup(source, mi, dataset, effect);
|
|
1467
1787
|
const isMul = op === "PreMul" || op === "PostMul" || op === "PreDiv" || op === "PostDiv";
|
|
1468
1788
|
const value = isMul && computed.scaled ? 1 + computed.value : computed.value;
|
|
1469
1789
|
const sdeDefault = isMul ? dataset.attributes.get(mi.modifiedAttributeID)?.defaultValue ?? 0 : 0;
|
|
@@ -1473,7 +1793,7 @@ function applyOneModifier(source, effect, mi, ctx, dataset) {
|
|
|
1473
1793
|
sourceID: source.id,
|
|
1474
1794
|
operation: op,
|
|
1475
1795
|
value,
|
|
1476
|
-
stackingGroup,
|
|
1796
|
+
stackingGroup: computeStackingGroup(source, mi, dataset, effect, target, ctx),
|
|
1477
1797
|
resistanceAttributeID: effect.resistanceAttributeID
|
|
1478
1798
|
};
|
|
1479
1799
|
target.addAffliction(mi.modifiedAttributeID, affliction, sdeDefault);
|
|
@@ -1980,8 +2300,17 @@ var SHIP_BONUS_SCALING_SKILL = /* @__PURE__ */ new Map([
|
|
|
1980
2300
|
// Notable: Exhumer/Barge shield+armor resist role bonuses (Hulk/Skiff/
|
|
1981
2301
|
// Mackinaw shield resist 4 %→20 %), Bhaalgorn drone+laser (492), industrial
|
|
1982
2302
|
// command (Orca/Rorqual), Marauder/pirate/expedition hull bonuses.
|
|
2303
|
+
// NB: attr 310 (`cpuNeedBonus`) is deliberately ABSENT. The auto-derivation
|
|
2304
|
+
// proposed it because the Weapon Upgrades SKILL scales its own attr 310 per
|
|
2305
|
+
// level (pyfa Effect3519 — a skill source, handled by the skill path), but
|
|
2306
|
+
// no SHIP-side effect does: pyfa's Effect5901 (`roleBonusBulkheadCPU`,
|
|
2307
|
+
// freighters + jump freighters) takes `ship.getModifiedItemAttr('cpuNeedBonus')`
|
|
2308
|
+
// verbatim, and every one of the 18 hulls carrying the attribute holds a
|
|
2309
|
+
// FULL-value role bonus (-99 % / -100 %), never a per-level one. Scaling it
|
|
2310
|
+
// by Electronics Upgrades V turned an Obelisk's -100 % bulkhead CPU into
|
|
2311
|
+
// -500 %, so three Reinforced Bulkheads reported -480 tf of CPU used
|
|
2312
|
+
// instead of 0. Caught on real published freighter fits.
|
|
1983
2313
|
[66, 89611],
|
|
1984
|
-
[310, 3432],
|
|
1985
2314
|
[349, 19760],
|
|
1986
2315
|
[492, 3339],
|
|
1987
2316
|
[1296, 21610],
|
|
@@ -2149,6 +2478,9 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
2149
2478
|
if (source.kind === "ship") {
|
|
2150
2479
|
return { value: baseValue, scaled: false };
|
|
2151
2480
|
}
|
|
2481
|
+
if (source.kind === "implant" || source.kind === "booster") {
|
|
2482
|
+
return { value: baseValue, scaled: false };
|
|
2483
|
+
}
|
|
2152
2484
|
if (itemRequiresSkill(source, skillID)) {
|
|
2153
2485
|
return { value: baseValue, scaled: false };
|
|
2154
2486
|
}
|
|
@@ -2156,19 +2488,42 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
2156
2488
|
}
|
|
2157
2489
|
return { value: baseValue, scaled: false };
|
|
2158
2490
|
}
|
|
2159
|
-
function
|
|
2160
|
-
if (
|
|
2161
|
-
if (
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2491
|
+
function penaltyReceiver(target, source, ctx) {
|
|
2492
|
+
if (target === ctx.ship) return "ship";
|
|
2493
|
+
if (target === source) return "self";
|
|
2494
|
+
switch (target.kind) {
|
|
2495
|
+
case "charge":
|
|
2496
|
+
return "charge";
|
|
2497
|
+
case "drone":
|
|
2498
|
+
return "drone";
|
|
2499
|
+
case "fighter":
|
|
2500
|
+
return "fighter";
|
|
2501
|
+
case "character":
|
|
2502
|
+
return "character";
|
|
2503
|
+
case "module":
|
|
2504
|
+
case "subsystem":
|
|
2505
|
+
return "module";
|
|
2506
|
+
default:
|
|
2507
|
+
return "self";
|
|
2168
2508
|
}
|
|
2169
|
-
return `attr:${mi.modifiedAttributeID}`;
|
|
2170
2509
|
}
|
|
2171
|
-
|
|
2510
|
+
function computeStackingGroup(source, mi, dataset, effect, target, ctx) {
|
|
2511
|
+
if (NO_PENALTY_KINDS.has(source.kind)) return null;
|
|
2512
|
+
const group = stackPenaltyGroup(
|
|
2513
|
+
effect.id,
|
|
2514
|
+
mi.modifiedAttributeID,
|
|
2515
|
+
penaltyReceiver(target, source, ctx),
|
|
2516
|
+
source.kind
|
|
2517
|
+
);
|
|
2518
|
+
if (group === null) return null;
|
|
2519
|
+
if (!HONOURED_PENALTY_GROUPS.has(group)) return `default:${mi.modifiedAttributeID}`;
|
|
2520
|
+
return `${group}:${mi.modifiedAttributeID}`;
|
|
2521
|
+
}
|
|
2522
|
+
var HONOURED_PENALTY_GROUPS = /* @__PURE__ */ new Set([
|
|
2523
|
+
"postMul",
|
|
2524
|
+
"postDiv",
|
|
2525
|
+
"postPerc",
|
|
2526
|
+
"postPercent",
|
|
2172
2527
|
"cloakingScanResolutionMultiplier"
|
|
2173
2528
|
]);
|
|
2174
2529
|
function mapSourceKind(kind) {
|
|
@@ -2232,7 +2587,7 @@ function applySkills(ctx, dataset) {
|
|
|
2232
2587
|
const sourceAttrMeta = dataset.attributes.get(mi.modifyingAttributeID);
|
|
2233
2588
|
const baseVal = scaleForPipeline(rawBase, sourceAttrMeta?.unitID, op);
|
|
2234
2589
|
const value = isMul ? 1 + baseVal * level : baseVal * level;
|
|
2235
|
-
const stackingGroup = NO_PENALTY_KINDS.has("character") ? null : `
|
|
2590
|
+
const stackingGroup = NO_PENALTY_KINDS.has("character") ? null : `default:${mi.modifiedAttributeID}`;
|
|
2236
2591
|
const targets = resolveSkillTargets(mi, ctx);
|
|
2237
2592
|
if (targets.length === 0) continue;
|
|
2238
2593
|
const affliction = {
|
|
@@ -2444,7 +2799,7 @@ function applyLegacyPropMods(ctx) {
|
|
|
2444
2799
|
sourceID: mod.id,
|
|
2445
2800
|
operation: "PostPercent",
|
|
2446
2801
|
value: sigBonus / 100,
|
|
2447
|
-
stackingGroup: `
|
|
2802
|
+
stackingGroup: `default:${ATTR_SIGNATURE_RADIUS}`
|
|
2448
2803
|
});
|
|
2449
2804
|
}
|
|
2450
2805
|
}
|
|
@@ -2452,13 +2807,13 @@ function applyLegacyPropMods(ctx) {
|
|
|
2452
2807
|
var ENTOSIS_LINK_EFFECT_ID = 6063;
|
|
2453
2808
|
function applyLegacyEntosisLink(ctx) {
|
|
2454
2809
|
for (const mod of ctx.modules) {
|
|
2455
|
-
if (mod.state
|
|
2810
|
+
if (mod.state !== "ACTIVE" && mod.state !== "OVERLOAD") continue;
|
|
2456
2811
|
if (!mod.effectIDs.has(ENTOSIS_LINK_EFFECT_ID)) continue;
|
|
2457
2812
|
const sensorPairs = [
|
|
2458
|
-
[1027,
|
|
2813
|
+
[1027, ATTR_SCAN_GRAVIMETRIC],
|
|
2459
2814
|
[1028, ATTR_SCAN_LADAR],
|
|
2460
2815
|
[1029, ATTR_SCAN_MAGNETOMETRIC],
|
|
2461
|
-
[1030,
|
|
2816
|
+
[1030, ATTR_SCAN_RADAR]
|
|
2462
2817
|
];
|
|
2463
2818
|
for (const [srcAttr, tgtAttr] of sensorPairs) {
|
|
2464
2819
|
const bonusPct = mod.getBase(srcAttr) ?? 0;
|
|
@@ -2492,7 +2847,7 @@ function applyLegacyCapitalEhe(ctx) {
|
|
|
2492
2847
|
sourceID: mod.id,
|
|
2493
2848
|
operation: "PostMul",
|
|
2494
2849
|
value: factor,
|
|
2495
|
-
stackingGroup: `
|
|
2850
|
+
stackingGroup: `default:${tgtAttr}`
|
|
2496
2851
|
});
|
|
2497
2852
|
}
|
|
2498
2853
|
}
|
|
@@ -2571,13 +2926,13 @@ function applyLegacyMjdSigBloom(ctx) {
|
|
|
2571
2926
|
if (!isMjd) continue;
|
|
2572
2927
|
const bonusPct = mod.getFinal(ATTR_SIGNATURE_RADIUS_BONUS_PERCENT, 0);
|
|
2573
2928
|
if (bonusPct === 0) continue;
|
|
2929
|
+
const penalised = [...LEGACY_MJD_EFFECT_IDS].some((eid) => mod.effectIDs.has(eid) && stackPenaltyGroup(eid, ATTR_SIGNATURE_RADIUS, "ship", "module") !== null);
|
|
2574
2930
|
ctx.ship.addAffliction(ATTR_SIGNATURE_RADIUS, {
|
|
2575
2931
|
sourceKind: "module",
|
|
2576
2932
|
sourceID: mod.id,
|
|
2577
2933
|
operation: "PostPercent",
|
|
2578
2934
|
value: bonusPct / 100,
|
|
2579
|
-
stackingGroup: `
|
|
2580
|
-
// stack-penalised with MWD bloom
|
|
2935
|
+
stackingGroup: penalised ? `default:${ATTR_SIGNATURE_RADIUS}` : null
|
|
2581
2936
|
});
|
|
2582
2937
|
}
|
|
2583
2938
|
}
|
|
@@ -2980,7 +3335,7 @@ function applyLegacyBastion(ctx) {
|
|
|
2980
3335
|
sourceID,
|
|
2981
3336
|
operation: "PreMul",
|
|
2982
3337
|
value: factor,
|
|
2983
|
-
stackingGroup: HULL_RES_ATTRS.has(tgt) ? null : `
|
|
3338
|
+
stackingGroup: HULL_RES_ATTRS.has(tgt) ? null : `default:${tgt}`
|
|
2984
3339
|
});
|
|
2985
3340
|
}
|
|
2986
3341
|
const maxRangeBonus = mod.getFinal(ATTR_MAX_RANGE_BONUS, 0);
|
|
@@ -2995,14 +3350,14 @@ function applyLegacyBastion(ctx) {
|
|
|
2995
3350
|
sourceID,
|
|
2996
3351
|
operation: "PostPercent",
|
|
2997
3352
|
value: maxRangeBonus / 100,
|
|
2998
|
-
stackingGroup: `
|
|
3353
|
+
stackingGroup: `default:${ATTR_MAX_RANGE}`
|
|
2999
3354
|
});
|
|
3000
3355
|
if (turretRofBonus !== 0) target.addAffliction(ATTR_SPEED, {
|
|
3001
3356
|
sourceKind: "module",
|
|
3002
3357
|
sourceID,
|
|
3003
3358
|
operation: "PostPercent",
|
|
3004
3359
|
value: turretRofBonus / 100,
|
|
3005
|
-
stackingGroup: `
|
|
3360
|
+
stackingGroup: `default:${ATTR_SPEED}`
|
|
3006
3361
|
});
|
|
3007
3362
|
}
|
|
3008
3363
|
if (matchTurretFalloff(target) && falloffBonusV !== 0) {
|
|
@@ -3011,7 +3366,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3011
3366
|
sourceID,
|
|
3012
3367
|
operation: "PostPercent",
|
|
3013
3368
|
value: falloffBonusV / 100,
|
|
3014
|
-
stackingGroup: `
|
|
3369
|
+
stackingGroup: `default:${ATTR_FALLOFF}`
|
|
3015
3370
|
});
|
|
3016
3371
|
}
|
|
3017
3372
|
}
|
|
@@ -3026,7 +3381,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3026
3381
|
sourceID,
|
|
3027
3382
|
operation: "PostPercent",
|
|
3028
3383
|
value: missileVelocityBonus / 100,
|
|
3029
|
-
stackingGroup: `
|
|
3384
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3030
3385
|
});
|
|
3031
3386
|
}
|
|
3032
3387
|
if (matchMissileLauncherROF(target) && missileRofBonus !== 0) {
|
|
@@ -3035,7 +3390,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3035
3390
|
sourceID,
|
|
3036
3391
|
operation: "PostPercent",
|
|
3037
3392
|
value: missileRofBonus / 100,
|
|
3038
|
-
stackingGroup: `
|
|
3393
|
+
stackingGroup: `default:${ATTR_SPEED}`
|
|
3039
3394
|
});
|
|
3040
3395
|
}
|
|
3041
3396
|
}
|
|
@@ -3051,7 +3406,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3051
3406
|
sourceID,
|
|
3052
3407
|
operation: "PostPercent",
|
|
3053
3408
|
value: armorRepBonus / 100,
|
|
3054
|
-
stackingGroup: `
|
|
3409
|
+
stackingGroup: `default:${ATTR_ARMOR_REP_AMOUNT}`
|
|
3055
3410
|
});
|
|
3056
3411
|
}
|
|
3057
3412
|
if (isShield && shieldBoostBonus !== 0) {
|
|
@@ -3060,7 +3415,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3060
3415
|
sourceID,
|
|
3061
3416
|
operation: "PostPercent",
|
|
3062
3417
|
value: shieldBoostBonus / 100,
|
|
3063
|
-
stackingGroup: `
|
|
3418
|
+
stackingGroup: `default:${ATTR_SHIELD_REP_AMOUNT}`
|
|
3064
3419
|
});
|
|
3065
3420
|
}
|
|
3066
3421
|
if ((isArmor || isShield) && repairDurationBonus !== 0) {
|
|
@@ -3104,7 +3459,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3104
3459
|
sourceID,
|
|
3105
3460
|
operation: "PostPercent",
|
|
3106
3461
|
value: v / 100,
|
|
3107
|
-
stackingGroup: `
|
|
3462
|
+
stackingGroup: `default:${tgt}`
|
|
3108
3463
|
});
|
|
3109
3464
|
}
|
|
3110
3465
|
const resistPairs = [
|
|
@@ -3203,7 +3558,7 @@ function applyLegacyDoomsdaySelfEffects(ctx) {
|
|
|
3203
3558
|
sourceID: mod.id,
|
|
3204
3559
|
operation: "PostPercent",
|
|
3205
3560
|
value: speedFactor / 100,
|
|
3206
|
-
stackingGroup: `
|
|
3561
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3207
3562
|
});
|
|
3208
3563
|
}
|
|
3209
3564
|
const siegeStatus = mod.getFinal(ATTR_SIEGE_WARP_STATUS, 0);
|
|
@@ -3274,7 +3629,7 @@ function applyCommandBuff(buffID, value, sourceMod, ctx) {
|
|
|
3274
3629
|
sourceID: sourceMod.id,
|
|
3275
3630
|
operation,
|
|
3276
3631
|
value: scaledValue,
|
|
3277
|
-
stackingGroup: `
|
|
3632
|
+
stackingGroup: `default:${attrID}`
|
|
3278
3633
|
});
|
|
3279
3634
|
for (const mi of dbuff.itemModifiers) {
|
|
3280
3635
|
ctx.ship.addAffliction(mi.dogmaAttributeID, affliction(op, mi.dogmaAttributeID));
|
|
@@ -3620,7 +3975,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3620
3975
|
sourceID: fighter.id,
|
|
3621
3976
|
operation: "PostPercent",
|
|
3622
3977
|
value: speedPct / 100,
|
|
3623
|
-
stackingGroup: `
|
|
3978
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3624
3979
|
});
|
|
3625
3980
|
}
|
|
3626
3981
|
}
|
|
@@ -3632,7 +3987,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3632
3987
|
sourceID: fighter.id,
|
|
3633
3988
|
operation: "PostPercent",
|
|
3634
3989
|
value: speedPct / 100,
|
|
3635
|
-
stackingGroup: `
|
|
3990
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3636
3991
|
});
|
|
3637
3992
|
}
|
|
3638
3993
|
const sigPct = fighter.getFinal(ATTR_FIGHTER_MWD_SIG_BONUS, 0);
|
|
@@ -3642,7 +3997,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3642
3997
|
sourceID: fighter.id,
|
|
3643
3998
|
operation: "PostPercent",
|
|
3644
3999
|
value: sigPct / 100,
|
|
3645
|
-
stackingGroup: `
|
|
4000
|
+
stackingGroup: `default:${ATTR_SIGNATURE_RADIUS}`
|
|
3646
4001
|
});
|
|
3647
4002
|
}
|
|
3648
4003
|
}
|
|
@@ -3654,7 +4009,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3654
4009
|
sourceID: fighter.id,
|
|
3655
4010
|
operation: "PostPercent",
|
|
3656
4011
|
value: speedPct / 100,
|
|
3657
|
-
stackingGroup: `
|
|
4012
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3658
4013
|
});
|
|
3659
4014
|
}
|
|
3660
4015
|
const sigPct = fighter.getFinal(ATTR_FIGHTER_EVASIVE_SIG_BONUS, 0);
|
|
@@ -3664,7 +4019,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3664
4019
|
sourceID: fighter.id,
|
|
3665
4020
|
operation: "PostPercent",
|
|
3666
4021
|
value: sigPct / 100,
|
|
3667
|
-
stackingGroup: `
|
|
4022
|
+
stackingGroup: `default:${ATTR_SIGNATURE_RADIUS}`
|
|
3668
4023
|
});
|
|
3669
4024
|
}
|
|
3670
4025
|
const resonances = [
|
|
@@ -3681,7 +4036,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3681
4036
|
sourceID: fighter.id,
|
|
3682
4037
|
operation: "PostMul",
|
|
3683
4038
|
value: mul,
|
|
3684
|
-
stackingGroup: `
|
|
4039
|
+
stackingGroup: `default:${tgtAttr}`
|
|
3685
4040
|
});
|
|
3686
4041
|
}
|
|
3687
4042
|
}
|
|
@@ -3801,7 +4156,7 @@ function applyLegacyRAH(ctx, damageProfile) {
|
|
|
3801
4156
|
sourceID: mod.id,
|
|
3802
4157
|
operation: "PreMul",
|
|
3803
4158
|
value: factor,
|
|
3804
|
-
stackingGroup: `
|
|
4159
|
+
stackingGroup: `default:${tgtAttr}`
|
|
3805
4160
|
});
|
|
3806
4161
|
}
|
|
3807
4162
|
}
|
|
@@ -3965,176 +4320,6 @@ function computeTotalEhp(shield, armor, hull, useProfile) {
|
|
|
3965
4320
|
return Number.isFinite(total) ? total : Number.MAX_SAFE_INTEGER;
|
|
3966
4321
|
}
|
|
3967
4322
|
|
|
3968
|
-
// src/fitChecks.ts
|
|
3969
|
-
function readAttr(t, id) {
|
|
3970
|
-
return t.attributes.find((a) => a.id === id)?.v;
|
|
3971
|
-
}
|
|
3972
|
-
function typeFitsSlotType(t, slot) {
|
|
3973
|
-
for (const e of t.effects) {
|
|
3974
|
-
const mapped = SLOT_EFFECT_TO_SLOT_TYPE[e.id];
|
|
3975
|
-
if (mapped === slot) return true;
|
|
3976
|
-
}
|
|
3977
|
-
return false;
|
|
3978
|
-
}
|
|
3979
|
-
function isTurretWeapon(t) {
|
|
3980
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "TURRET");
|
|
3981
|
-
}
|
|
3982
|
-
function isMissileLauncher(t) {
|
|
3983
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "MISSILE");
|
|
3984
|
-
}
|
|
3985
|
-
function isSmartBomb(t) {
|
|
3986
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "SMARTBOMB");
|
|
3987
|
-
}
|
|
3988
|
-
function shipGroupRestrictions(t) {
|
|
3989
|
-
const out = [];
|
|
3990
|
-
for (const id of CAN_FIT_SHIP_GROUP_ATTRS) {
|
|
3991
|
-
const v = readAttr(t, id);
|
|
3992
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
3993
|
-
}
|
|
3994
|
-
return out;
|
|
3995
|
-
}
|
|
3996
|
-
function shipTypeRestrictions(t) {
|
|
3997
|
-
const out = [];
|
|
3998
|
-
for (const id of CAN_FIT_SHIP_TYPE_ATTRS) {
|
|
3999
|
-
const v = readAttr(t, id);
|
|
4000
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
4001
|
-
}
|
|
4002
|
-
return out;
|
|
4003
|
-
}
|
|
4004
|
-
function maxGroupFittedFor(mod) {
|
|
4005
|
-
const v = readAttr(mod, ATTR.MAX_GROUP_FITTED);
|
|
4006
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
4007
|
-
}
|
|
4008
|
-
function maxTypeFittedFor(mod) {
|
|
4009
|
-
const v = readAttr(mod, ATTR.MAX_TYPE_FITTED);
|
|
4010
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
4011
|
-
}
|
|
4012
|
-
function freeFitGroupSlotsFor(mod, fittedModules, dataset) {
|
|
4013
|
-
const groupCap = maxGroupFittedFor(mod);
|
|
4014
|
-
const typeCap = maxTypeFittedFor(mod);
|
|
4015
|
-
if (groupCap === void 0 && typeCap === void 0) return Number.POSITIVE_INFINITY;
|
|
4016
|
-
let groupCount = 0;
|
|
4017
|
-
let typeCount = 0;
|
|
4018
|
-
for (const fm of fittedModules) {
|
|
4019
|
-
if (typeCap !== void 0 && fm.typeID === mod.id) typeCount++;
|
|
4020
|
-
if (groupCap !== void 0) {
|
|
4021
|
-
const t = dataset.getType(fm.typeID);
|
|
4022
|
-
if (t && t.groupID === mod.groupID) groupCount++;
|
|
4023
|
-
}
|
|
4024
|
-
}
|
|
4025
|
-
let free = Number.POSITIVE_INFINITY;
|
|
4026
|
-
if (groupCap !== void 0) free = Math.min(free, groupCap - groupCount);
|
|
4027
|
-
if (typeCap !== void 0) free = Math.min(free, typeCap - typeCount);
|
|
4028
|
-
return Math.max(0, free);
|
|
4029
|
-
}
|
|
4030
|
-
function canFitModuleOnShip(mod, ship, slot, fitContext) {
|
|
4031
|
-
if (!typeFitsSlotType(mod, slot)) {
|
|
4032
|
-
return { ok: false, reason: `Module does not declare a ${slot} slot effect` };
|
|
4033
|
-
}
|
|
4034
|
-
if (!ship) return { ok: true };
|
|
4035
|
-
const allowedGroups = shipGroupRestrictions(mod);
|
|
4036
|
-
const allowedTypes = shipTypeRestrictions(mod);
|
|
4037
|
-
if (allowedGroups.length > 0 || allowedTypes.length > 0) {
|
|
4038
|
-
const groupOk = allowedGroups.includes(ship.groupID);
|
|
4039
|
-
const typeOk = allowedTypes.includes(ship.id);
|
|
4040
|
-
if (!groupOk && !typeOk) {
|
|
4041
|
-
return { ok: false, reason: "Ship hull/group not allowed by this module" };
|
|
4042
|
-
}
|
|
4043
|
-
}
|
|
4044
|
-
if (slot === "RIG") {
|
|
4045
|
-
const modSize = readAttr(mod, ATTR.RIG_SIZE);
|
|
4046
|
-
const shipSize = readAttr(ship, ATTR.RIG_SIZE);
|
|
4047
|
-
if (modSize != null && shipSize != null && Math.round(modSize) !== Math.round(shipSize)) {
|
|
4048
|
-
return { ok: false, reason: "Rig size mismatch" };
|
|
4049
|
-
}
|
|
4050
|
-
}
|
|
4051
|
-
if (slot === "SUBSYSTEM") {
|
|
4052
|
-
const fitsTo = readAttr(mod, 1380);
|
|
4053
|
-
if (fitsTo != null && fitsTo > 0 && Math.round(fitsTo) !== ship.id) {
|
|
4054
|
-
return { ok: false, reason: "Subsystem locked to a different T3C hull" };
|
|
4055
|
-
}
|
|
4056
|
-
}
|
|
4057
|
-
if (slot === "HI") {
|
|
4058
|
-
if (isTurretWeapon(mod)) {
|
|
4059
|
-
const turrets = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
4060
|
-
if (turrets <= 0) {
|
|
4061
|
-
return { ok: false, reason: "Ship has no turret hardpoints" };
|
|
4062
|
-
}
|
|
4063
|
-
} else if (isMissileLauncher(mod)) {
|
|
4064
|
-
const launchers = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
4065
|
-
if (launchers <= 0) {
|
|
4066
|
-
return { ok: false, reason: "Ship has no launcher hardpoints" };
|
|
4067
|
-
}
|
|
4068
|
-
}
|
|
4069
|
-
}
|
|
4070
|
-
if (fitContext) {
|
|
4071
|
-
const free = freeFitGroupSlotsFor(mod, fitContext.fittedModules, fitContext.dataset);
|
|
4072
|
-
if (free <= 0) {
|
|
4073
|
-
const cap = maxTypeFittedFor(mod) ?? maxGroupFittedFor(mod);
|
|
4074
|
-
return { ok: false, reason: `Only ${cap} of this module type can be fitted to a ship` };
|
|
4075
|
-
}
|
|
4076
|
-
}
|
|
4077
|
-
return { ok: true };
|
|
4078
|
-
}
|
|
4079
|
-
function chargeGroupsForModule(mod) {
|
|
4080
|
-
const out = [];
|
|
4081
|
-
for (const attrID of CHARGE_GROUP_ATTRS) {
|
|
4082
|
-
const v = readAttr(mod, attrID);
|
|
4083
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
4084
|
-
}
|
|
4085
|
-
return out;
|
|
4086
|
-
}
|
|
4087
|
-
function moduleAcceptsAnyCharge(mod) {
|
|
4088
|
-
return chargeGroupsForModule(mod).length > 0;
|
|
4089
|
-
}
|
|
4090
|
-
function moduleAcceptsChargeType(mod, charge) {
|
|
4091
|
-
const allowedGroups = chargeGroupsForModule(mod);
|
|
4092
|
-
if (allowedGroups.length === 0) return false;
|
|
4093
|
-
if (!allowedGroups.includes(charge.groupID)) return false;
|
|
4094
|
-
const modSize = readAttr(mod, ATTR.CHARGE_SIZE);
|
|
4095
|
-
const chargeSize = readAttr(charge, ATTR.CHARGE_SIZE);
|
|
4096
|
-
if (modSize != null && chargeSize != null && Math.round(chargeSize) > Math.round(modSize)) {
|
|
4097
|
-
return false;
|
|
4098
|
-
}
|
|
4099
|
-
return true;
|
|
4100
|
-
}
|
|
4101
|
-
var ACTIVE_EFFECT_CATEGORIES = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
4102
|
-
function isActivatableModule(mod, effects) {
|
|
4103
|
-
for (const e of mod.effects) {
|
|
4104
|
-
const eff = effects.get(e.id);
|
|
4105
|
-
if (eff && eff.effectCategoryID !== void 0 && ACTIVE_EFFECT_CATEGORIES.has(eff.effectCategoryID)) {
|
|
4106
|
-
return true;
|
|
4107
|
-
}
|
|
4108
|
-
}
|
|
4109
|
-
return false;
|
|
4110
|
-
}
|
|
4111
|
-
var DEFAULT_OFFLINE_ACTIVATION_GROUPS = /* @__PURE__ */ new Set([
|
|
4112
|
-
330,
|
|
4113
|
-
4117
|
|
4114
|
-
]);
|
|
4115
|
-
function defaultStateForModule(mod, effects) {
|
|
4116
|
-
if (DEFAULT_OFFLINE_ACTIVATION_GROUPS.has(mod.groupID)) return "ONLINE";
|
|
4117
|
-
return isActivatableModule(mod, effects) ? "ACTIVE" : "ONLINE";
|
|
4118
|
-
}
|
|
4119
|
-
function freeHardpointsFor(mod, ship, fittedHiModules, dataset) {
|
|
4120
|
-
const turretCap = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
4121
|
-
const launcherCap = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
4122
|
-
let turretUsed = 0;
|
|
4123
|
-
let launcherUsed = 0;
|
|
4124
|
-
for (const m of fittedHiModules) {
|
|
4125
|
-
const t = dataset.getType(m.typeID);
|
|
4126
|
-
if (!t) continue;
|
|
4127
|
-
if (isTurretWeapon(t)) turretUsed++;
|
|
4128
|
-
else if (isMissileLauncher(t)) launcherUsed++;
|
|
4129
|
-
}
|
|
4130
|
-
let hardpointFree;
|
|
4131
|
-
if (isTurretWeapon(mod)) hardpointFree = Math.max(0, turretCap - turretUsed);
|
|
4132
|
-
else if (isMissileLauncher(mod)) hardpointFree = Math.max(0, launcherCap - launcherUsed);
|
|
4133
|
-
else hardpointFree = Number.POSITIVE_INFINITY;
|
|
4134
|
-
const groupFree = freeFitGroupSlotsFor(mod, fittedHiModules, dataset);
|
|
4135
|
-
return Math.min(hardpointFree, groupFree);
|
|
4136
|
-
}
|
|
4137
|
-
|
|
4138
4323
|
// src/derived/capacitor.ts
|
|
4139
4324
|
function computeCapacitor(ctx, dataset) {
|
|
4140
4325
|
const ship = ctx.ship;
|
|
@@ -5171,22 +5356,46 @@ function computeFit(fit, dataset, opts) {
|
|
|
5171
5356
|
const t = dataset.getType(ff.typeID);
|
|
5172
5357
|
return t ? [makeFighterState(ff, t)] : [];
|
|
5173
5358
|
});
|
|
5174
|
-
const implants =
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5359
|
+
const implants = dropSlotCollisions(
|
|
5360
|
+
fit.implants.flatMap((fi) => {
|
|
5361
|
+
const t = dataset.getType(fi.typeID);
|
|
5362
|
+
return t ? [{ slot: t.attributes.find((a) => a.id === ATTR_IMPLANTNESS)?.v ?? fi.slot, item: makeImplantState(fi, t) }] : [];
|
|
5363
|
+
})
|
|
5364
|
+
);
|
|
5365
|
+
const boosters = dropSlotCollisions(
|
|
5366
|
+
fit.boosters.flatMap((fb) => {
|
|
5367
|
+
const t = dataset.getType(fb.typeID);
|
|
5368
|
+
return t ? [{ slot: t.attributes.find((a) => a.id === ATTR_BOOSTERNESS)?.v ?? fb.slot, item: makeBoosterState(fb, t) }] : [];
|
|
5369
|
+
})
|
|
5370
|
+
);
|
|
5182
5371
|
const subsystems = fit.subsystems.flatMap((fs) => {
|
|
5183
5372
|
const t = dataset.getType(fs.typeID);
|
|
5184
5373
|
return t ? [makeSubsystemState(fs, t)] : [];
|
|
5185
5374
|
});
|
|
5186
5375
|
let mode;
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5376
|
+
const modeTypeID = fit.modeTypeID ?? defaultModeTypeIDFor(shipType, dataset);
|
|
5377
|
+
if (modeTypeID !== void 0) {
|
|
5378
|
+
const t = dataset.getType(modeTypeID);
|
|
5379
|
+
if (t) mode = makeModeState(modeTypeID, t);
|
|
5380
|
+
}
|
|
5381
|
+
ship.attr(
|
|
5382
|
+
ATTR.DRONE_CONTROL_RANGE,
|
|
5383
|
+
dataset.attributes.get(ATTR.DRONE_CONTROL_RANGE)?.defaultValue ?? 2e4
|
|
5384
|
+
);
|
|
5385
|
+
for (const it of [
|
|
5386
|
+
ship,
|
|
5387
|
+
character,
|
|
5388
|
+
mode,
|
|
5389
|
+
...modules,
|
|
5390
|
+
...drones,
|
|
5391
|
+
...fighters,
|
|
5392
|
+
...implants,
|
|
5393
|
+
...boosters,
|
|
5394
|
+
...subsystems
|
|
5395
|
+
]) {
|
|
5396
|
+
if (!it) continue;
|
|
5397
|
+
it.attrSchema = dataset.attributes;
|
|
5398
|
+
if (it.charge) it.charge.attrSchema = dataset.attributes;
|
|
5190
5399
|
}
|
|
5191
5400
|
const skillLevels = /* @__PURE__ */ new Map();
|
|
5192
5401
|
for (const [k, v] of Object.entries(opts.skillProfile.skills)) {
|
|
@@ -5232,8 +5441,9 @@ function computeFit(fit, dataset, opts) {
|
|
|
5232
5441
|
for (const m of modules) applySourceItem(m, ctx, dataset);
|
|
5233
5442
|
for (const d of drones) applySourceItem(d, ctx, dataset);
|
|
5234
5443
|
for (const f of fighters) applySourceItem(f, ctx, dataset);
|
|
5235
|
-
|
|
5236
|
-
for (const
|
|
5444
|
+
const charSide = [...implants, ...boosters];
|
|
5445
|
+
for (const it of charSide) applySourceItem(it, ctx, dataset, "peer");
|
|
5446
|
+
for (const it of charSide) applySourceItem(it, ctx, dataset, "outgoing");
|
|
5237
5447
|
if (mode) applySourceItem(mode, ctx, dataset);
|
|
5238
5448
|
applyLegacyOverload(ctx);
|
|
5239
5449
|
applyLegacyPropMods(ctx);
|
|
@@ -5440,6 +5650,7 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
5440
5650
|
const cap = computeCapacitor(ctx, dataset);
|
|
5441
5651
|
const tank = computeTank(ctx);
|
|
5442
5652
|
const offense = computeOffense(ctx, dataset, fit);
|
|
5653
|
+
const sensor = pickSensorStrength(ship);
|
|
5443
5654
|
return {
|
|
5444
5655
|
fitting: {
|
|
5445
5656
|
cpuUsed,
|
|
@@ -5528,8 +5739,8 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
5528
5739
|
)),
|
|
5529
5740
|
signatureRadius: ship.getFinal(ATTR.SIGNATURE_RADIUS, 0),
|
|
5530
5741
|
scanResolution: ship.getFinal(ATTR.SCAN_RESOLUTION, 0),
|
|
5531
|
-
sensorStrength:
|
|
5532
|
-
sensorType:
|
|
5742
|
+
sensorStrength: sensor.value,
|
|
5743
|
+
sensorType: sensor.type
|
|
5533
5744
|
},
|
|
5534
5745
|
drones: {
|
|
5535
5746
|
bayUsed: droneBayUsed,
|
|
@@ -5540,13 +5751,12 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
5540
5751
|
// ItemState per FitDrone stack, so filtering by state counted five
|
|
5541
5752
|
// active Ogre IIs as 1 — and this is the number bandwidth gates.
|
|
5542
5753
|
active: fit.drones.reduce((n, d) => n + d.countActive, 0),
|
|
5543
|
-
//
|
|
5544
|
-
//
|
|
5545
|
-
//
|
|
5546
|
-
//
|
|
5547
|
-
//
|
|
5548
|
-
|
|
5549
|
-
controlRange: ship.getFinal(ATTR.DRONE_CONTROL_RANGE, 2e4) + ctx.character.getFinal(ATTR.DRONE_CONTROL_RANGE, 0)
|
|
5754
|
+
// ONE accumulator, on the ship (attr 458, SDE default 20 km) — the
|
|
5755
|
+
// charID-domain modifiers that carry the skills and the hull bonus
|
|
5756
|
+
// are re-routed there, see FitContext.targetsForModifier. Summing a
|
|
5757
|
+
// ship half and a character half instead let a percentage bonus
|
|
5758
|
+
// apply to only one of them.
|
|
5759
|
+
controlRange: ship.getFinal(ATTR.DRONE_CONTROL_RANGE, 2e4)
|
|
5550
5760
|
},
|
|
5551
5761
|
projected: projectionReports,
|
|
5552
5762
|
structure: computeStructureMeta(ctx),
|
|
@@ -5573,18 +5783,36 @@ function computeAlignTime(ship) {
|
|
|
5573
5783
|
if (!mass || !agility) return 0;
|
|
5574
5784
|
return Math.log(4) * mass * agility / 1e6;
|
|
5575
5785
|
}
|
|
5786
|
+
var ATTR_IMPLANTNESS = 331;
|
|
5787
|
+
var ATTR_BOOSTERNESS = 1087;
|
|
5788
|
+
function dropSlotCollisions(entries) {
|
|
5789
|
+
const taken = /* @__PURE__ */ new Set();
|
|
5790
|
+
const out = [];
|
|
5791
|
+
for (const e of entries) {
|
|
5792
|
+
if (taken.has(e.slot)) continue;
|
|
5793
|
+
taken.add(e.slot);
|
|
5794
|
+
out.push(e.item);
|
|
5795
|
+
}
|
|
5796
|
+
return out;
|
|
5797
|
+
}
|
|
5576
5798
|
function pickSensorStrength(ship) {
|
|
5577
5799
|
const candidates = [
|
|
5578
|
-
{ attr: ATTR.SCAN_RADAR_STRENGTH, type: "radar" },
|
|
5579
|
-
{ attr: ATTR.SCAN_LADAR_STRENGTH, type: "ladar" },
|
|
5580
5800
|
{ attr: ATTR.SCAN_MAGNETOMETRIC_STRENGTH, type: "magnetometric" },
|
|
5801
|
+
{ attr: ATTR.SCAN_LADAR_STRENGTH, type: "ladar" },
|
|
5802
|
+
{ attr: ATTR.SCAN_RADAR_STRENGTH, type: "radar" },
|
|
5581
5803
|
{ attr: ATTR.SCAN_GRAVIMETRIC_STRENGTH, type: "gravimetric" }
|
|
5582
5804
|
];
|
|
5805
|
+
let best = -1;
|
|
5806
|
+
let type = "unknown";
|
|
5583
5807
|
for (const c of candidates) {
|
|
5584
5808
|
const v = ship.getFinal(c.attr, 0);
|
|
5585
|
-
if (v >
|
|
5809
|
+
if (v > best) {
|
|
5810
|
+
best = v;
|
|
5811
|
+
type = c.type;
|
|
5812
|
+
} else if (v === best) type = "multispectral";
|
|
5586
5813
|
}
|
|
5587
|
-
return { value: 0, type: "unknown" };
|
|
5814
|
+
if (best <= 0) return { value: Math.max(best, 0), type: best === 0 ? type : "unknown" };
|
|
5815
|
+
return { value: best, type };
|
|
5588
5816
|
}
|
|
5589
5817
|
|
|
5590
5818
|
// src/eft/parser.ts
|
|
@@ -5687,6 +5915,20 @@ function parseEft(text, dataset) {
|
|
|
5687
5915
|
return { fit, warnings };
|
|
5688
5916
|
}
|
|
5689
5917
|
function classifySection(entries, idx, _dataset) {
|
|
5918
|
+
let sawImplant = false;
|
|
5919
|
+
let allImplant = true;
|
|
5920
|
+
for (const e of entries) {
|
|
5921
|
+
const { name, quantity } = parseEntryName(e.text);
|
|
5922
|
+
const typeID = idx.map.get(name.toLowerCase());
|
|
5923
|
+
if (typeID === void 0) {
|
|
5924
|
+
allImplant = false;
|
|
5925
|
+
continue;
|
|
5926
|
+
}
|
|
5927
|
+
const t = idx.types.get(typeID);
|
|
5928
|
+
if (quantity === null && isFittableImplant(t)) sawImplant = true;
|
|
5929
|
+
else allImplant = false;
|
|
5930
|
+
}
|
|
5931
|
+
if (sawImplant && allImplant) return "implants";
|
|
5690
5932
|
for (const e of entries) {
|
|
5691
5933
|
const { name, quantity } = parseEntryName(e.text);
|
|
5692
5934
|
const typeID = idx.map.get(name.toLowerCase());
|
|
@@ -5703,6 +5945,13 @@ function classifySection(entries, idx, _dataset) {
|
|
|
5703
5945
|
}
|
|
5704
5946
|
return "cargo";
|
|
5705
5947
|
}
|
|
5948
|
+
var ATTR_IMPLANTNESS2 = 331;
|
|
5949
|
+
var ATTR_BOOSTERNESS2 = 1087;
|
|
5950
|
+
var attrValue = (t, id) => t.attributes?.find((a) => a.id === id)?.v;
|
|
5951
|
+
function isFittableImplant(t) {
|
|
5952
|
+
if (t.categoryID !== CATEGORY.IMPLANT) return false;
|
|
5953
|
+
return attrValue(t, ATTR_IMPLANTNESS2) !== void 0 || attrValue(t, ATTR_BOOSTERNESS2) !== void 0;
|
|
5954
|
+
}
|
|
5706
5955
|
function parseEntryName(text) {
|
|
5707
5956
|
const m = QUANTITY_RE.exec(text);
|
|
5708
5957
|
if (m) {
|
|
@@ -5739,6 +5988,23 @@ function consumeEntry(entry, sectionKind, fit, slotCursor, idx, dataset, warning
|
|
|
5739
5988
|
fit.drones.push(drone);
|
|
5740
5989
|
return;
|
|
5741
5990
|
}
|
|
5991
|
+
if (sectionKind === "implants" && quantity === null && isFittableImplant(type)) {
|
|
5992
|
+
const implantSlot = attrValue(type, ATTR_IMPLANTNESS2);
|
|
5993
|
+
if (implantSlot !== void 0) {
|
|
5994
|
+
fit.implants.push({ id: tempId(), typeID, slot: implantSlot });
|
|
5995
|
+
} else {
|
|
5996
|
+
fit.boosters.push({
|
|
5997
|
+
id: tempId(),
|
|
5998
|
+
typeID,
|
|
5999
|
+
slot: attrValue(type, ATTR_BOOSTERNESS2) ?? 1,
|
|
6000
|
+
// Side effects are opt-in and an EFT cannot express them, so an
|
|
6001
|
+
// imported booster arrives with its drawbacks off — the same
|
|
6002
|
+
// default pyfa gives it.
|
|
6003
|
+
activeSideEffects: []
|
|
6004
|
+
});
|
|
6005
|
+
}
|
|
6006
|
+
return;
|
|
6007
|
+
}
|
|
5742
6008
|
if (sectionKind === "cargo" || quantity !== null) {
|
|
5743
6009
|
const cargo = { id: tempId(), typeID, count: quantity ?? 1 };
|
|
5744
6010
|
fit.cargo.push(cargo);
|
|
@@ -6104,7 +6370,7 @@ var SUBSYSTEM_RANK_BY_TYPE_ID = /* @__PURE__ */ new Map([
|
|
|
6104
6370
|
[45621, 3]
|
|
6105
6371
|
// Loki Propulsion - Wake Limiter
|
|
6106
6372
|
]);
|
|
6107
|
-
function
|
|
6373
|
+
function attrValue2(t, id) {
|
|
6108
6374
|
for (const a of t.attributes) if (a.id === id) return a.v;
|
|
6109
6375
|
return void 0;
|
|
6110
6376
|
}
|
|
@@ -6118,7 +6384,7 @@ function computeT3CVariantCode(shipTypeID, subsystems, dataset) {
|
|
|
6118
6384
|
for (const fs of subsystems) {
|
|
6119
6385
|
const t = subsBucket.get(fs.typeID);
|
|
6120
6386
|
if (!t) return null;
|
|
6121
|
-
if (
|
|
6387
|
+
if (attrValue2(t, PARENT_SHIP_ATTR) !== shipTypeID) return null;
|
|
6122
6388
|
fittedByGroup.set(t.groupID, fs.typeID);
|
|
6123
6389
|
}
|
|
6124
6390
|
let code = "";
|
|
@@ -6249,6 +6515,7 @@ var TARGET_PROFILE_PRESETS = [
|
|
|
6249
6515
|
|
|
6250
6516
|
exports.ACTIVATION_EFFECT_ID = ACTIVATION_EFFECT_ID;
|
|
6251
6517
|
exports.ATTR = ATTR;
|
|
6518
|
+
exports.BOOSTER_SIDE_EFFECT_IDS = BOOSTER_SIDE_EFFECT_IDS;
|
|
6252
6519
|
exports.CATEGORY = CATEGORY;
|
|
6253
6520
|
exports.DAMAGE_PROFILE_PRESETS = DAMAGE_PROFILE_PRESETS;
|
|
6254
6521
|
exports.FitContext = FitContext;
|
|
@@ -6286,6 +6553,7 @@ exports.computeStructureMeta = computeStructureMeta;
|
|
|
6286
6553
|
exports.computeT3CVariantCode = computeT3CVariantCode;
|
|
6287
6554
|
exports.computeTank = computeTank;
|
|
6288
6555
|
exports.computeTotalEhp = computeTotalEhp;
|
|
6556
|
+
exports.defaultModeTypeIDFor = defaultModeTypeIDFor;
|
|
6289
6557
|
exports.defaultStateForModule = defaultStateForModule;
|
|
6290
6558
|
exports.disintegratorCyclesToFullSpool = disintegratorCyclesToFullSpool;
|
|
6291
6559
|
exports.disintegratorSpoolBonus = disintegratorSpoolBonus;
|