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