eve-fit-engine 0.1.34 → 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/.build +1 -1
- 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/v1e9ac4ea6d60/attributes.json +0 -1
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/categories.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/clone-grades.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/dbuff-collections.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/dynamic-attributes.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/effects.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/groups.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/market-groups.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/meta-groups.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/charges.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/drones.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/fighters.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/implants.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/modules.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/mutaplasmids.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/ships.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/skills.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/structure-modules.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/structures.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/subsystems.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/types/system-effects.json +0 -0
- /package/data/{v1e9ac4ea6d60 → v0c61c617c9b9}/units.json +0 -0
package/dist/node.cjs
CHANGED
|
@@ -494,6 +494,32 @@ var LEGACY_EFFECT_IDS = [
|
|
|
494
494
|
// Frigate MJD sig bloom.
|
|
495
495
|
{ id: 12126, name: null, handler: "modifierEngine::applyLegacyMjdSigBloom (Frigate MJD)" }
|
|
496
496
|
];
|
|
497
|
+
var BOOSTER_SIDE_EFFECT_IDS = /* @__PURE__ */ new Set([
|
|
498
|
+
2735,
|
|
499
|
+
// boosterArmorHpPenalty
|
|
500
|
+
2736,
|
|
501
|
+
// boosterArmorRepairAmountPenalty
|
|
502
|
+
2737,
|
|
503
|
+
// boosterShieldCapacityPenalty
|
|
504
|
+
2739,
|
|
505
|
+
// boosterTurretOptimalRangePenalty
|
|
506
|
+
2741,
|
|
507
|
+
// boosterTurretFalloffPenalty
|
|
508
|
+
2745,
|
|
509
|
+
// boosterCapacitorCapacityPenalty
|
|
510
|
+
2746,
|
|
511
|
+
// boosterMaxVelocityPenalty
|
|
512
|
+
2747,
|
|
513
|
+
// boosterTurretTrackingPenalty
|
|
514
|
+
2748,
|
|
515
|
+
// boosterMissileVelocityPenalty
|
|
516
|
+
2749,
|
|
517
|
+
// boosterMissileExplosionVelocityPenalty
|
|
518
|
+
2791,
|
|
519
|
+
// boosterMissileExplosionCloudPenaltyFixed
|
|
520
|
+
4970
|
|
521
|
+
// boosterShieldBoostAmountPenaltyShieldSkills
|
|
522
|
+
]);
|
|
497
523
|
var OUT_OF_SCOPE_EFFECT_IDS = [
|
|
498
524
|
// ---- Hardpoint / slot dispatch markers consumed via fitChecks (not a modifier dispatch path). ----
|
|
499
525
|
{ id: 40, name: null, handler: "fitChecks::isMissileLauncher (hardpoint marker)" },
|
|
@@ -581,7 +607,204 @@ function verifyLegacyEffectIds(effects) {
|
|
|
581
607
|
return complaints;
|
|
582
608
|
}
|
|
583
609
|
|
|
610
|
+
// src/fitChecks.ts
|
|
611
|
+
function readAttr(t, id) {
|
|
612
|
+
return t.attributes.find((a) => a.id === id)?.v;
|
|
613
|
+
}
|
|
614
|
+
function typeFitsSlotType(t, slot) {
|
|
615
|
+
for (const e of t.effects) {
|
|
616
|
+
const mapped = SLOT_EFFECT_TO_SLOT_TYPE[e.id];
|
|
617
|
+
if (mapped === slot) return true;
|
|
618
|
+
}
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
function isTurretWeapon(t) {
|
|
622
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "TURRET");
|
|
623
|
+
}
|
|
624
|
+
function isMissileLauncher(t) {
|
|
625
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "MISSILE");
|
|
626
|
+
}
|
|
627
|
+
function isSmartBomb(t) {
|
|
628
|
+
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "SMARTBOMB");
|
|
629
|
+
}
|
|
630
|
+
function shipGroupRestrictions(t) {
|
|
631
|
+
const out = [];
|
|
632
|
+
for (const id of CAN_FIT_SHIP_GROUP_ATTRS) {
|
|
633
|
+
const v = readAttr(t, id);
|
|
634
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
635
|
+
}
|
|
636
|
+
return out;
|
|
637
|
+
}
|
|
638
|
+
function shipTypeRestrictions(t) {
|
|
639
|
+
const out = [];
|
|
640
|
+
for (const id of CAN_FIT_SHIP_TYPE_ATTRS) {
|
|
641
|
+
const v = readAttr(t, id);
|
|
642
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
643
|
+
}
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
function maxGroupFittedFor(mod) {
|
|
647
|
+
const v = readAttr(mod, ATTR.MAX_GROUP_FITTED);
|
|
648
|
+
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
649
|
+
}
|
|
650
|
+
function maxTypeFittedFor(mod) {
|
|
651
|
+
const v = readAttr(mod, ATTR.MAX_TYPE_FITTED);
|
|
652
|
+
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
653
|
+
}
|
|
654
|
+
function freeFitGroupSlotsFor(mod, fittedModules, dataset) {
|
|
655
|
+
const groupCap = maxGroupFittedFor(mod);
|
|
656
|
+
const typeCap = maxTypeFittedFor(mod);
|
|
657
|
+
if (groupCap === void 0 && typeCap === void 0) return Number.POSITIVE_INFINITY;
|
|
658
|
+
let groupCount = 0;
|
|
659
|
+
let typeCount = 0;
|
|
660
|
+
for (const fm of fittedModules) {
|
|
661
|
+
if (typeCap !== void 0 && fm.typeID === mod.id) typeCount++;
|
|
662
|
+
if (groupCap !== void 0) {
|
|
663
|
+
const t = dataset.getType(fm.typeID);
|
|
664
|
+
if (t && t.groupID === mod.groupID) groupCount++;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
let free = Number.POSITIVE_INFINITY;
|
|
668
|
+
if (groupCap !== void 0) free = Math.min(free, groupCap - groupCount);
|
|
669
|
+
if (typeCap !== void 0) free = Math.min(free, typeCap - typeCount);
|
|
670
|
+
return Math.max(0, free);
|
|
671
|
+
}
|
|
672
|
+
function canFitModuleOnShip(mod, ship, slot, fitContext) {
|
|
673
|
+
if (!typeFitsSlotType(mod, slot)) {
|
|
674
|
+
return { ok: false, reason: `Module does not declare a ${slot} slot effect` };
|
|
675
|
+
}
|
|
676
|
+
if (!ship) return { ok: true };
|
|
677
|
+
const allowedGroups = shipGroupRestrictions(mod);
|
|
678
|
+
const allowedTypes = shipTypeRestrictions(mod);
|
|
679
|
+
if (allowedGroups.length > 0 || allowedTypes.length > 0) {
|
|
680
|
+
const groupOk = allowedGroups.includes(ship.groupID);
|
|
681
|
+
const typeOk = allowedTypes.includes(ship.id);
|
|
682
|
+
if (!groupOk && !typeOk) {
|
|
683
|
+
return { ok: false, reason: "Ship hull/group not allowed by this module" };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (slot === "RIG") {
|
|
687
|
+
const modSize = readAttr(mod, ATTR.RIG_SIZE);
|
|
688
|
+
const shipSize = readAttr(ship, ATTR.RIG_SIZE);
|
|
689
|
+
if (modSize != null && shipSize != null && Math.round(modSize) !== Math.round(shipSize)) {
|
|
690
|
+
return { ok: false, reason: "Rig size mismatch" };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (slot === "SUBSYSTEM") {
|
|
694
|
+
const fitsTo = readAttr(mod, 1380);
|
|
695
|
+
if (fitsTo != null && fitsTo > 0 && Math.round(fitsTo) !== ship.id) {
|
|
696
|
+
return { ok: false, reason: "Subsystem locked to a different T3C hull" };
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (slot === "HI") {
|
|
700
|
+
if (isTurretWeapon(mod)) {
|
|
701
|
+
const turrets = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
702
|
+
if (turrets <= 0) {
|
|
703
|
+
return { ok: false, reason: "Ship has no turret hardpoints" };
|
|
704
|
+
}
|
|
705
|
+
} else if (isMissileLauncher(mod)) {
|
|
706
|
+
const launchers = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
707
|
+
if (launchers <= 0) {
|
|
708
|
+
return { ok: false, reason: "Ship has no launcher hardpoints" };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (fitContext) {
|
|
713
|
+
const free = freeFitGroupSlotsFor(mod, fitContext.fittedModules, fitContext.dataset);
|
|
714
|
+
if (free <= 0) {
|
|
715
|
+
const cap = maxTypeFittedFor(mod) ?? maxGroupFittedFor(mod);
|
|
716
|
+
return { ok: false, reason: `Only ${cap} of this module type can be fitted to a ship` };
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return { ok: true };
|
|
720
|
+
}
|
|
721
|
+
function chargeGroupsForModule(mod) {
|
|
722
|
+
const out = [];
|
|
723
|
+
for (const attrID of CHARGE_GROUP_ATTRS) {
|
|
724
|
+
const v = readAttr(mod, attrID);
|
|
725
|
+
if (v != null && v > 0) out.push(Math.round(v));
|
|
726
|
+
}
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
function moduleAcceptsAnyCharge(mod) {
|
|
730
|
+
return chargeGroupsForModule(mod).length > 0;
|
|
731
|
+
}
|
|
732
|
+
function moduleAcceptsChargeType(mod, charge) {
|
|
733
|
+
const allowedGroups = chargeGroupsForModule(mod);
|
|
734
|
+
if (allowedGroups.length === 0) return false;
|
|
735
|
+
if (!allowedGroups.includes(charge.groupID)) return false;
|
|
736
|
+
const modSize = readAttr(mod, ATTR.CHARGE_SIZE);
|
|
737
|
+
const chargeSize = readAttr(charge, ATTR.CHARGE_SIZE);
|
|
738
|
+
if (modSize != null && chargeSize != null && Math.round(chargeSize) > Math.round(modSize)) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
var ACTIVE_EFFECT_CATEGORIES = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
744
|
+
function isActivatableModule(mod, effects) {
|
|
745
|
+
for (const e of mod.effects) {
|
|
746
|
+
const eff = effects.get(e.id);
|
|
747
|
+
if (eff && eff.effectCategoryID !== void 0 && ACTIVE_EFFECT_CATEGORIES.has(eff.effectCategoryID)) {
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
var DEFAULT_OFFLINE_ACTIVATION_GROUPS = /* @__PURE__ */ new Set([
|
|
754
|
+
330,
|
|
755
|
+
4117
|
|
756
|
+
]);
|
|
757
|
+
function defaultStateForModule(mod, effects) {
|
|
758
|
+
if (DEFAULT_OFFLINE_ACTIVATION_GROUPS.has(mod.groupID)) return "ONLINE";
|
|
759
|
+
return isActivatableModule(mod, effects) ? "ACTIVE" : "ONLINE";
|
|
760
|
+
}
|
|
761
|
+
function freeHardpointsFor(mod, ship, fittedHiModules, dataset) {
|
|
762
|
+
const turretCap = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
763
|
+
const launcherCap = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
764
|
+
let turretUsed = 0;
|
|
765
|
+
let launcherUsed = 0;
|
|
766
|
+
for (const m of fittedHiModules) {
|
|
767
|
+
const t = dataset.getType(m.typeID);
|
|
768
|
+
if (!t) continue;
|
|
769
|
+
if (isTurretWeapon(t)) turretUsed++;
|
|
770
|
+
else if (isMissileLauncher(t)) launcherUsed++;
|
|
771
|
+
}
|
|
772
|
+
let hardpointFree;
|
|
773
|
+
if (isTurretWeapon(mod)) hardpointFree = Math.max(0, turretCap - turretUsed);
|
|
774
|
+
else if (isMissileLauncher(mod)) hardpointFree = Math.max(0, launcherCap - launcherUsed);
|
|
775
|
+
else hardpointFree = Number.POSITIVE_INFINITY;
|
|
776
|
+
const groupFree = freeFitGroupSlotsFor(mod, fittedHiModules, dataset);
|
|
777
|
+
return Math.min(hardpointFree, groupFree);
|
|
778
|
+
}
|
|
779
|
+
var TACTICAL_DESTROYER_GROUP = 1305;
|
|
780
|
+
var MODED_HULL_NAMES = /* @__PURE__ */ new Set(["Anhinga"]);
|
|
781
|
+
var SHIP_MODIFIERS_GROUP = 1306;
|
|
782
|
+
function defaultModeTypeIDFor(ship, dataset) {
|
|
783
|
+
if (ship.groupID !== TACTICAL_DESTROYER_GROUP && !MODED_HULL_NAMES.has(ship.name ?? "")) return void 0;
|
|
784
|
+
const prefix = (ship.name ?? "").toLowerCase();
|
|
785
|
+
if (!prefix) return void 0;
|
|
786
|
+
let best;
|
|
787
|
+
for (const t of dataset.typesByBucket.modules?.values() ?? []) {
|
|
788
|
+
if (t.groupID !== SHIP_MODIFIERS_GROUP) continue;
|
|
789
|
+
if (!(t.name ?? "").toLowerCase().startsWith(prefix)) continue;
|
|
790
|
+
if (best === void 0 || t.id < best.id) best = t;
|
|
791
|
+
}
|
|
792
|
+
return best?.id;
|
|
793
|
+
}
|
|
794
|
+
|
|
584
795
|
// src/fitContext.ts
|
|
796
|
+
var MISSILE_BOOST_SKILLS = [
|
|
797
|
+
3319,
|
|
798
|
+
// Missile Launcher Operation
|
|
799
|
+
3323
|
|
800
|
+
// Defender Missiles
|
|
801
|
+
];
|
|
802
|
+
var PYFA_GROUP_FILTERED_EFFECTS = /* @__PURE__ */ new Map([
|
|
803
|
+
// 3495 shipCapPropulsionJamming — the Interceptor / Interdictor role bonus
|
|
804
|
+
// to Warp Scrambler + Stasis Web capacitor need. pyfa:
|
|
805
|
+
// `mod.item.group.name in ('Stasis Web', 'Warp Scrambler')`.
|
|
806
|
+
[3495, /* @__PURE__ */ new Set([52, 65])]
|
|
807
|
+
]);
|
|
585
808
|
var FitContext = class {
|
|
586
809
|
constructor(init) {
|
|
587
810
|
/** Projected hostile sources currently in scope. Populated by the
|
|
@@ -689,14 +912,19 @@ var FitContext = class {
|
|
|
689
912
|
* - EffectStopper: handled outside this resolver (stops other effects
|
|
690
913
|
* rather than applying a modifier).
|
|
691
914
|
*/
|
|
692
|
-
targetsForModifier(modifier, source) {
|
|
915
|
+
targetsForModifier(modifier, source, effectID) {
|
|
693
916
|
const root = this.resolveDomain(modifier.domain, source);
|
|
694
917
|
switch (modifier.func) {
|
|
695
918
|
case "ItemModifier":
|
|
919
|
+
if (modifier.domain === "charID" && modifier.modifiedAttributeID === ATTR.DRONE_CONTROL_RANGE) {
|
|
920
|
+
return this.ship ? [this.ship] : [];
|
|
921
|
+
}
|
|
696
922
|
if (modifier.domain === "charID" && source.kind === "module" && modifier.modifiedAttributeID === 212) {
|
|
697
923
|
const out = [];
|
|
698
924
|
for (const m of this.modules) {
|
|
699
|
-
if (m.charge)
|
|
925
|
+
if (!m.charge) continue;
|
|
926
|
+
if (!MISSILE_BOOST_SKILLS.some((sid) => itemRequiresSkill(m.charge, sid))) continue;
|
|
927
|
+
out.push(m.charge);
|
|
700
928
|
}
|
|
701
929
|
return out;
|
|
702
930
|
}
|
|
@@ -708,7 +936,10 @@ var FitContext = class {
|
|
|
708
936
|
return this.itemsInLocation(root).filter((it) => it.groupID === modifier.groupID);
|
|
709
937
|
}
|
|
710
938
|
case "LocationRequiredSkillModifier": {
|
|
711
|
-
if (!root
|
|
939
|
+
if (!root) return [];
|
|
940
|
+
const groups = effectID === void 0 ? void 0 : PYFA_GROUP_FILTERED_EFFECTS.get(effectID);
|
|
941
|
+
if (groups) return this.itemsInLocation(root).filter((it) => groups.has(it.groupID));
|
|
942
|
+
if (modifier.skillTypeID === void 0) return [];
|
|
712
943
|
const sid = modifier.skillTypeID;
|
|
713
944
|
return this.itemsInLocation(root).filter((it) => itemRequiresSkill(it, sid));
|
|
714
945
|
}
|
|
@@ -884,15 +1115,11 @@ var ModifiedAttribute = class {
|
|
|
884
1115
|
for (const a of byOp.get("PostDiv") ?? []) {
|
|
885
1116
|
postMulEntries.push({ value: 1 / a.value, stackingGroup: a.stackingGroup });
|
|
886
1117
|
}
|
|
887
|
-
if (postMulEntries.length > 0) {
|
|
888
|
-
value *= combineMultiplicative(postMulEntries);
|
|
889
|
-
}
|
|
890
|
-
const postPercentEntries = [];
|
|
891
1118
|
for (const a of byOp.get("PostPercent") ?? []) {
|
|
892
|
-
|
|
1119
|
+
postMulEntries.push({ value: 1 + a.value, stackingGroup: a.stackingGroup });
|
|
893
1120
|
}
|
|
894
|
-
if (
|
|
895
|
-
value *= combineMultiplicative(
|
|
1121
|
+
if (postMulEntries.length > 0) {
|
|
1122
|
+
value *= combineMultiplicative(postMulEntries);
|
|
896
1123
|
}
|
|
897
1124
|
const postAssign = byOp.get("PostAssign");
|
|
898
1125
|
if (postAssign && postAssign.length > 0) {
|
|
@@ -925,6 +1152,13 @@ var VOLUME_ATTR_ID = 161;
|
|
|
925
1152
|
var CAPACITY_ATTR_ID = 38;
|
|
926
1153
|
var ItemState = class {
|
|
927
1154
|
constructor(init) {
|
|
1155
|
+
/**
|
|
1156
|
+
* Attribute schema, needed to honour the SDE's attribute CAPS
|
|
1157
|
+
* (`maxAttributeID` / `minAttributeID`). Assigned by `computeFit` once for
|
|
1158
|
+
* every item in the fit; when absent, `getFinal` simply doesn't clamp — the
|
|
1159
|
+
* behaviour before caps existed.
|
|
1160
|
+
*/
|
|
1161
|
+
this.attrSchema = null;
|
|
928
1162
|
this.attrs = /* @__PURE__ */ new Map();
|
|
929
1163
|
this.kind = init.kind;
|
|
930
1164
|
this.id = init.id;
|
|
@@ -934,6 +1168,7 @@ var ItemState = class {
|
|
|
934
1168
|
this.categoryID = init.type.categoryID;
|
|
935
1169
|
this.state = init.state ?? "ONLINE";
|
|
936
1170
|
this.charge = init.charge ?? null;
|
|
1171
|
+
this.activeSideEffects = new Set(init.activeSideEffects ?? []);
|
|
937
1172
|
for (const a of init.type.attributes) {
|
|
938
1173
|
const base = init.attributeOverrides?.[a.id] ?? a.v;
|
|
939
1174
|
this.attrs.set(a.id, new ModifiedAttribute(a.id, base));
|
|
@@ -977,7 +1212,47 @@ var ItemState = class {
|
|
|
977
1212
|
getFinal(id, fallback = 0) {
|
|
978
1213
|
const ma = this.attrs.get(id);
|
|
979
1214
|
if (!ma) return fallback;
|
|
980
|
-
return ma.compute();
|
|
1215
|
+
return this.clampToCaps(id, ma.compute());
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Clamp a computed value to the ceiling / floor the SDE declares for that
|
|
1219
|
+
* attribute, mirroring pyfa's `__calculateValue` (which applies the same
|
|
1220
|
+
* bounds to forced values AND to normally-calculated ones).
|
|
1221
|
+
*
|
|
1222
|
+
* An attribute may name another attribute as its bound — resonances are
|
|
1223
|
+
* capped by `<layer>MaxDamageResonance` (1.0), `maxVelocity` by
|
|
1224
|
+
* `speedLimit`, the `*Bonus` attributes floored by `ConstantMinusNinetyNine`
|
|
1225
|
+
* — and the bound is read off THIS item, so a hull that raises its own
|
|
1226
|
+
* ceiling is honoured.
|
|
1227
|
+
*
|
|
1228
|
+
* Without it a Polarized weapon, which force-assigns every resonance to
|
|
1229
|
+
* `resistanceKiller` = 100, read as -9900% resistance instead of the 0% the
|
|
1230
|
+
* game shows: the cap is what turns "assign 100" into "resistances removed".
|
|
1231
|
+
* Measured on real published fits — five hulls, forty stats.
|
|
1232
|
+
*/
|
|
1233
|
+
clampToCaps(id, value) {
|
|
1234
|
+
const schema = this.attrSchema?.get(id);
|
|
1235
|
+
if (!schema) return value;
|
|
1236
|
+
let out = value;
|
|
1237
|
+
if (schema.minAttributeID !== void 0) {
|
|
1238
|
+
const floor = this.capValue(schema.minAttributeID);
|
|
1239
|
+
if (floor !== void 0) out = Math.max(out, floor);
|
|
1240
|
+
}
|
|
1241
|
+
if (schema.maxAttributeID !== void 0) {
|
|
1242
|
+
const ceil = this.capValue(schema.maxAttributeID);
|
|
1243
|
+
if (ceil !== void 0) out = Math.min(out, ceil);
|
|
1244
|
+
}
|
|
1245
|
+
return out;
|
|
1246
|
+
}
|
|
1247
|
+
/** Value of a bounding attribute on this item, falling back to the SDE
|
|
1248
|
+
* default when the item doesn't carry it (pyfa's `getOriginal(key,
|
|
1249
|
+
* default)`). Bounds never declare bounds of their own, so this recurses
|
|
1250
|
+
* exactly one level. */
|
|
1251
|
+
capValue(capAttrID) {
|
|
1252
|
+
const ma = this.attrs.get(capAttrID);
|
|
1253
|
+
if (ma) return ma.compute();
|
|
1254
|
+
const def = this.attrSchema?.get(capAttrID)?.defaultValue;
|
|
1255
|
+
return def;
|
|
981
1256
|
}
|
|
982
1257
|
/** `defaultBase` is the seed base value when the target attribute isn't
|
|
983
1258
|
* in the item's typeDogma — needed for SDE attrs whose `defaultValue`
|
|
@@ -1095,7 +1370,13 @@ function makeImplantState(fi, type) {
|
|
|
1095
1370
|
return new ItemState({ kind: "implant", id: fi.id, type, state: "ONLINE" });
|
|
1096
1371
|
}
|
|
1097
1372
|
function makeBoosterState(fb, type) {
|
|
1098
|
-
return new ItemState({
|
|
1373
|
+
return new ItemState({
|
|
1374
|
+
kind: "booster",
|
|
1375
|
+
id: fb.id,
|
|
1376
|
+
type,
|
|
1377
|
+
state: "ONLINE",
|
|
1378
|
+
activeSideEffects: fb.activeSideEffects ?? []
|
|
1379
|
+
});
|
|
1099
1380
|
}
|
|
1100
1381
|
function makeSubsystemState(fs2, type) {
|
|
1101
1382
|
return new ItemState({ kind: "subsystem", id: fs2.id, type, state: "ONLINE" });
|
|
@@ -1104,312 +1385,347 @@ function makeModeState(modeTypeID, type) {
|
|
|
1104
1385
|
return new ItemState({ kind: "mode", id: `mode:${modeTypeID}`, type, state: "ACTIVE" });
|
|
1105
1386
|
}
|
|
1106
1387
|
|
|
1107
|
-
// src/
|
|
1108
|
-
var
|
|
1109
|
-
[89, "default"],
|
|
1110
|
-
[91, "default"],
|
|
1111
|
-
[92, "default"],
|
|
1112
|
-
[93, "default"],
|
|
1113
|
-
[95, "default"],
|
|
1114
|
-
[96, "default"],
|
|
1115
|
-
[394, "default"],
|
|
1116
|
-
[395, "default"],
|
|
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
|
-
[
|
|
1369
|
-
[
|
|
1370
|
-
[
|
|
1371
|
-
[
|
|
1372
|
-
[
|
|
1373
|
-
[
|
|
1374
|
-
[
|
|
1375
|
-
[
|
|
1376
|
-
[
|
|
1377
|
-
[
|
|
1378
|
-
[
|
|
1379
|
-
[
|
|
1380
|
-
[
|
|
1381
|
-
[
|
|
1382
|
-
[
|
|
1383
|
-
[
|
|
1384
|
-
[
|
|
1385
|
-
[
|
|
1386
|
-
[
|
|
1387
|
-
[
|
|
1388
|
-
[
|
|
1389
|
-
[
|
|
1390
|
-
[
|
|
1391
|
-
[
|
|
1392
|
-
[
|
|
1393
|
-
[
|
|
1394
|
-
[
|
|
1395
|
-
[
|
|
1396
|
-
[12838, "postMul"],
|
|
1397
|
-
[12839, "postMul"]
|
|
1388
|
+
// src/stackingPenalised.ts
|
|
1389
|
+
var STACKING_PENALISED = /* @__PURE__ */ new Map([
|
|
1390
|
+
[89, ["51#default"]],
|
|
1391
|
+
[91, ["64#default"]],
|
|
1392
|
+
[92, ["64#default"]],
|
|
1393
|
+
[93, ["64#default"]],
|
|
1394
|
+
[95, ["51#default"]],
|
|
1395
|
+
[96, ["51#default"]],
|
|
1396
|
+
[394, ["37#default!booster+implant+skill"]],
|
|
1397
|
+
[395, ["70#default!booster+implant+skill"]],
|
|
1398
|
+
[607, ["37#postMul"]],
|
|
1399
|
+
[657, ["70#default"]],
|
|
1400
|
+
[699, ["564#default!booster+implant+skill"]],
|
|
1401
|
+
[763, ["*#default"]],
|
|
1402
|
+
[784, ["281#default!booster+implant+skill"]],
|
|
1403
|
+
[854, ["564#cloakingScanResolutionMultiplier"]],
|
|
1404
|
+
[856, ["600#default!booster+implant+skill"]],
|
|
1405
|
+
[889, ["51#default"]],
|
|
1406
|
+
[891, ["37#default!ship"]],
|
|
1407
|
+
[892, ["37#default!ship"]],
|
|
1408
|
+
[1024, ["37#default!ship"]],
|
|
1409
|
+
[1230, ["37#default!ship"]],
|
|
1410
|
+
[1281, ["84#default!implant"]],
|
|
1411
|
+
[1445, ["54#default!booster+skill"]],
|
|
1412
|
+
[1446, ["54#default!booster+skill"]],
|
|
1413
|
+
[1448, ["54#default!booster+skill"]],
|
|
1414
|
+
[1472, ["654#default!booster+implant+skill"]],
|
|
1415
|
+
[1590, ["653#default!booster+implant+skill"]],
|
|
1416
|
+
[1617, ["70#default"]],
|
|
1417
|
+
[1720, ["68#default"]],
|
|
1418
|
+
[1764, ["37#default!booster+implant+skill"]],
|
|
1419
|
+
[1885, ["51#default!ship"]],
|
|
1420
|
+
[1886, ["51#default!ship"]],
|
|
1421
|
+
[2013, ["37#default!booster+implant"]],
|
|
1422
|
+
[2014, ["54#default!skill"]],
|
|
1423
|
+
[2019, ["68#default!implant+skill"]],
|
|
1424
|
+
[2020, ["84#default!implant+skill"]],
|
|
1425
|
+
[2041, ["*#default"]],
|
|
1426
|
+
[2052, ["*#default"]],
|
|
1427
|
+
[2152, ["37#default"]],
|
|
1428
|
+
[2232, ["*#default"]],
|
|
1429
|
+
[2302, ["*#preMul"]],
|
|
1430
|
+
[2644, ["552#default"]],
|
|
1431
|
+
[2645, ["564#default"]],
|
|
1432
|
+
[2646, ["76#default"]],
|
|
1433
|
+
[2670, ["*#default", "564#default", "76#default"]],
|
|
1434
|
+
[2693, ["158#default!booster"]],
|
|
1435
|
+
[2694, ["158#default!booster"]],
|
|
1436
|
+
[2695, ["158#default!booster"]],
|
|
1437
|
+
[2696, ["54#default!booster"]],
|
|
1438
|
+
[2697, ["54#default!booster"]],
|
|
1439
|
+
[2698, ["54#default!booster"]],
|
|
1440
|
+
[2716, ["552#default"]],
|
|
1441
|
+
[2717, ["70#default"]],
|
|
1442
|
+
[2792, ["*#default"]],
|
|
1443
|
+
[2795, ["*#default"]],
|
|
1444
|
+
[2796, ["4#default"]],
|
|
1445
|
+
[2797, ["51#default"]],
|
|
1446
|
+
[2798, ["64#default!booster"]],
|
|
1447
|
+
[2799, ["51#default"]],
|
|
1448
|
+
[2801, ["51#default"]],
|
|
1449
|
+
[2802, ["64#default!booster"]],
|
|
1450
|
+
[2803, ["64#default!booster"]],
|
|
1451
|
+
[2804, ["51#default"]],
|
|
1452
|
+
[2851, ["*#default!booster"]],
|
|
1453
|
+
[2858, ["37#default"]],
|
|
1454
|
+
[2865, ["37#default"]],
|
|
1455
|
+
[2867, ["64#default"]],
|
|
1456
|
+
[2868, ["84#default"]],
|
|
1457
|
+
[3001, ["51#postPerc"]],
|
|
1458
|
+
[3046, ["37#default"]],
|
|
1459
|
+
[3174, ["54#default"]],
|
|
1460
|
+
[3175, ["20#default"]],
|
|
1461
|
+
[3182, ["*#default"]],
|
|
1462
|
+
[3200, ["84#default"]],
|
|
1463
|
+
[3201, ["68#default"]],
|
|
1464
|
+
[3586, ["566#default!skill"]],
|
|
1465
|
+
[3655, ["54#default"]],
|
|
1466
|
+
[3656, ["160#default"]],
|
|
1467
|
+
[3657, ["564#default"]],
|
|
1468
|
+
[3659, ["76#default"]],
|
|
1469
|
+
[3674, ["37#default"]],
|
|
1470
|
+
[3726, ["70#default"]],
|
|
1471
|
+
[3727, ["37#default"]],
|
|
1472
|
+
[3993, ["76#postMul"]],
|
|
1473
|
+
[3995, ["552#postMul"]],
|
|
1474
|
+
[3996, ["267#default"]],
|
|
1475
|
+
[3997, ["268#default"]],
|
|
1476
|
+
[3998, ["269#default"]],
|
|
1477
|
+
[3999, ["270#default"]],
|
|
1478
|
+
[4002, ["37#postMul"]],
|
|
1479
|
+
[4003, ["37#postMul"]],
|
|
1480
|
+
[4016, ["64#default"]],
|
|
1481
|
+
[4017, ["118#postMul"]],
|
|
1482
|
+
[4018, ["114#postMul"]],
|
|
1483
|
+
[4019, ["116#postMul"]],
|
|
1484
|
+
[4020, ["117#postMul"]],
|
|
1485
|
+
[4021, ["64#postMul"]],
|
|
1486
|
+
[4022, ["160#postMul"]],
|
|
1487
|
+
[4023, ["653#postMul"]],
|
|
1488
|
+
[4054, ["64#default"]],
|
|
1489
|
+
[4055, ["64#default"]],
|
|
1490
|
+
[4056, ["64#default"]],
|
|
1491
|
+
[4057, ["114#postMul"]],
|
|
1492
|
+
[4058, ["116#postMul"]],
|
|
1493
|
+
[4059, ["117#postMul"]],
|
|
1494
|
+
[4060, ["118#postMul"]],
|
|
1495
|
+
[4061, ["118#postMul"]],
|
|
1496
|
+
[4062, ["114#postMul"]],
|
|
1497
|
+
[4063, ["116#postMul"]],
|
|
1498
|
+
[4086, ["84#postMul"]],
|
|
1499
|
+
[4088, ["84#postMul"]],
|
|
1500
|
+
[4089, ["68#postMul"]],
|
|
1501
|
+
[4135, ["271#default"]],
|
|
1502
|
+
[4136, ["272#default"]],
|
|
1503
|
+
[4137, ["273#default"]],
|
|
1504
|
+
[4138, ["274#default"]],
|
|
1505
|
+
[4162, ["1371#default!booster+implant+skill"]],
|
|
1506
|
+
[4280, ["70#default"]],
|
|
1507
|
+
[4358, ["54#default"]],
|
|
1508
|
+
[4464, ["51#default"]],
|
|
1509
|
+
[4489, ["37#default"]],
|
|
1510
|
+
[4490, ["37#default"]],
|
|
1511
|
+
[4491, ["37#default"]],
|
|
1512
|
+
[4492, ["37#default"]],
|
|
1513
|
+
[4527, ["158#default"]],
|
|
1514
|
+
[4559, ["*#default"]],
|
|
1515
|
+
[4575, ["2044#default", "37#default", "54#default", "564#default", "64#default", "68#default"]],
|
|
1516
|
+
[4809, ["238#default"]],
|
|
1517
|
+
[4810, ["239#default"]],
|
|
1518
|
+
[4811, ["240#default"]],
|
|
1519
|
+
[4812, ["241#default"]],
|
|
1520
|
+
[4906, ["64#postMul"]],
|
|
1521
|
+
[4928, ["*#preMul"]],
|
|
1522
|
+
[4961, ["68#postMul"]],
|
|
1523
|
+
[5081, ["76#default"]],
|
|
1524
|
+
[5188, ["160#default!booster"]],
|
|
1525
|
+
[5189, ["160#default!booster"]],
|
|
1526
|
+
[5190, ["160#default!booster"]],
|
|
1527
|
+
[5213, ["37#default!ship"]],
|
|
1528
|
+
[5214, ["37#default!ship"]],
|
|
1529
|
+
[5230, ["*#default"]],
|
|
1530
|
+
[5231, ["*#default"]],
|
|
1531
|
+
[5397, ["1372#default"]],
|
|
1532
|
+
[5399, ["1371#default"]],
|
|
1533
|
+
[5440, ["117#postMul"]],
|
|
1534
|
+
[5468, ["70#default!ship"]],
|
|
1535
|
+
[5560, ["669#default!ship"]],
|
|
1536
|
+
[5618, ["51#default!ship"]],
|
|
1537
|
+
[5757, ["*#default", "566#default"]],
|
|
1538
|
+
[5867, ["281#default!ship"]],
|
|
1539
|
+
[5911, ["868#default"]],
|
|
1540
|
+
[5912, ["90#postMul"]],
|
|
1541
|
+
[5914, ["97#postMul"]],
|
|
1542
|
+
[5915, ["90#postMul"]],
|
|
1543
|
+
[5916, ["116#postMul"]],
|
|
1544
|
+
[5917, ["117#postMul"]],
|
|
1545
|
+
[5918, ["118#postMul"]],
|
|
1546
|
+
[5919, ["114#postMul"]],
|
|
1547
|
+
[5920, ["654#postMul"]],
|
|
1548
|
+
[5921, ["554#postMul"]],
|
|
1549
|
+
[5922, ["20#postMul"]],
|
|
1550
|
+
[5923, ["97#postMul"]],
|
|
1551
|
+
[5924, ["238#postMul"]],
|
|
1552
|
+
[5925, ["239#postMul"]],
|
|
1553
|
+
[5926, ["240#postMul"]],
|
|
1554
|
+
[5927, ["241#postMul"]],
|
|
1555
|
+
[5929, ["160#postMul"]],
|
|
1556
|
+
[5951, ["600#default"]],
|
|
1557
|
+
[5998, ["70#default"]],
|
|
1558
|
+
[6001, ["70#default"]],
|
|
1559
|
+
[6010, ["76#postDiv"]],
|
|
1560
|
+
[6011, ["54#postDiv"]],
|
|
1561
|
+
[6012, ["*#postDiv"]],
|
|
1562
|
+
[6014, ["552#postDiv"]],
|
|
1563
|
+
[6015, ["*#postDiv"]],
|
|
1564
|
+
[6016, ["70#postDiv"]],
|
|
1565
|
+
[6017, ["37#postDiv"]],
|
|
1566
|
+
[6039, ["160#postDiv"]],
|
|
1567
|
+
[6040, ["554#postDiv"]],
|
|
1568
|
+
[6041, ["*#postDiv"]],
|
|
1569
|
+
[6063, ["*#default"]],
|
|
1570
|
+
[6076, ["37#postDiv"]],
|
|
1571
|
+
[6110, ["37#default"]],
|
|
1572
|
+
[6111, ["281#default"]],
|
|
1573
|
+
[6112, ["654#default"]],
|
|
1574
|
+
[6113, ["653#default"]],
|
|
1575
|
+
[6135, ["*#default"]],
|
|
1576
|
+
[6152, ["54#postDiv"]],
|
|
1577
|
+
[6154, ["20#postDiv"]],
|
|
1578
|
+
[6164, ["37#default"]],
|
|
1579
|
+
[6201, ["37#default"]],
|
|
1580
|
+
[6208, ["552#default"]],
|
|
1581
|
+
[6402, ["653#default"]],
|
|
1582
|
+
[6403, ["37#default"]],
|
|
1583
|
+
[6404, ["2044#default", "54#default"]],
|
|
1584
|
+
[6405, ["6#default"]],
|
|
1585
|
+
[6406, ["158#default", "2044#default", "54#default"]],
|
|
1586
|
+
[6409, ["564#default"]],
|
|
1587
|
+
[6410, ["654#default"]],
|
|
1588
|
+
[6411, ["37#default"]],
|
|
1589
|
+
[6412, ["99#default"]],
|
|
1590
|
+
[6422, ["564#default", "76#default"]],
|
|
1591
|
+
[6423, ["*#default"]],
|
|
1592
|
+
[6424, ["*#default"]],
|
|
1593
|
+
[6425, ["552#default"]],
|
|
1594
|
+
[6426, ["37#default"]],
|
|
1595
|
+
[6427, ["*#default"]],
|
|
1596
|
+
[6428, ["*#default"]],
|
|
1597
|
+
[6435, ["37#default"]],
|
|
1598
|
+
[6439, ["271#default", "272#default", "273#default", "274#default", "37#default", "552#default"]],
|
|
1599
|
+
[6440, ["37#default"]],
|
|
1600
|
+
[6441, ["37#default", "552#default"]],
|
|
1601
|
+
[6448, ["*#default"]],
|
|
1602
|
+
[6449, ["*#default", "51#default"]],
|
|
1603
|
+
[6472, ["37#default"]],
|
|
1604
|
+
[6473, ["37#default"]],
|
|
1605
|
+
[6474, ["37#default"]],
|
|
1606
|
+
[6476, ["37#default"]],
|
|
1607
|
+
[6478, ["552#default"]],
|
|
1608
|
+
[6479, ["*#default"]],
|
|
1609
|
+
[6481, ["564#default", "76#default"]],
|
|
1610
|
+
[6484, ["*#postMul"]],
|
|
1611
|
+
[6487, ["2045#default"]],
|
|
1612
|
+
[6555, ["37#default"]],
|
|
1613
|
+
[6556, ["2130#default", "2178#default", "2226#default", "64#default"]],
|
|
1614
|
+
[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"]],
|
|
1615
|
+
[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"]],
|
|
1616
|
+
[6566, ["2177#postMul", "2182#postMul", "2233#postMul", "37#postMul"]],
|
|
1617
|
+
[6567, ["*#default", "564#default"]],
|
|
1618
|
+
[6581, ["*#default", "2044#default", "37#default", "4#default", "54#default", "564#default", "64#default"]],
|
|
1619
|
+
[6582, ["4#postMul", "68#default", "84#default", "charge:37#default", "module:51#postPercent"]],
|
|
1620
|
+
[6658, ["158#default", "51#postPerc", "54#default", "68#default", "84#default", "charge:37#default", "ship:*#default", "ship:*#preMul"]],
|
|
1621
|
+
[6670, ["2149#default", "2175#default", "2236#default", "54#default"]],
|
|
1622
|
+
[6671, ["37#default"]],
|
|
1623
|
+
[6682, ["37#default"]],
|
|
1624
|
+
[6683, ["552#default"]],
|
|
1625
|
+
[6684, ["564#default", "76#default"]],
|
|
1626
|
+
[6686, ["*#default"]],
|
|
1627
|
+
[6690, ["37#default"]],
|
|
1628
|
+
[6692, ["552#default"]],
|
|
1629
|
+
[6693, ["564#default", "76#default"]],
|
|
1630
|
+
[6694, ["*#default"]],
|
|
1631
|
+
[6727, ["2044#default"]],
|
|
1632
|
+
[6730, ["37#postMul", "552#default"]],
|
|
1633
|
+
[6731, ["37#postMul"]],
|
|
1634
|
+
[6796, ["64#postDiv"]],
|
|
1635
|
+
[6797, ["64#postDiv"]],
|
|
1636
|
+
[6798, ["64#postDiv"]],
|
|
1637
|
+
[6799, ["*#postDiv"]],
|
|
1638
|
+
[6801, ["20#postDiv"]],
|
|
1639
|
+
[6877, ["600#default"]],
|
|
1640
|
+
[7029, ["2751#default"]],
|
|
1641
|
+
[7077, ["64#default"]],
|
|
1642
|
+
[7078, ["51#default"]],
|
|
1643
|
+
[7098, ["564#default", "76#default"]],
|
|
1644
|
+
[7111, ["64#default"]],
|
|
1645
|
+
[7142, ["20#default", "37#default", "4#default"]],
|
|
1646
|
+
[7202, ["37#default"]],
|
|
1647
|
+
[7203, ["64#default"]],
|
|
1648
|
+
[7223, ["70#default"]],
|
|
1649
|
+
[7237, ["600#default"]],
|
|
1650
|
+
[8033, ["54#default"]],
|
|
1651
|
+
[8057, ["51#default", "64#default"]],
|
|
1652
|
+
[8076, ["110#default"]],
|
|
1653
|
+
[8082, ["564#default"]],
|
|
1654
|
+
[8108, ["552#postMul"]],
|
|
1655
|
+
[8109, ["552#postMul"]],
|
|
1656
|
+
[8111, ["113#default", "1371#default", "267#default", "271#default"]],
|
|
1657
|
+
[8112, ["109#default", "269#default", "273#default", "564#default", "600#default"]],
|
|
1658
|
+
[8113, ["111#default", "268#default", "272#default", "552#postMul", "68#postMul", "84#postMul"]],
|
|
1659
|
+
[8114, ["*#postMul", "110#default", "160#postMul", "270#default", "274#default", "654#postMul", "drone:64#postMul", "fighter:64#postMul", "module:64#default"]],
|
|
1660
|
+
[8119, ["37#default", "54#default", "64#default", "68#default"]],
|
|
1661
|
+
[11445, ["*#default"]],
|
|
1662
|
+
[11691, ["37#default"]],
|
|
1663
|
+
[11946, ["64#default"]],
|
|
1664
|
+
[11947, ["654#postMul"]],
|
|
1665
|
+
[11948, ["64#postMul"]],
|
|
1666
|
+
[11953, ["653#postMul"]],
|
|
1667
|
+
[12126, ["552#default"]],
|
|
1668
|
+
[12597, ["*#default", "564#default", "76#default"]],
|
|
1669
|
+
[12761, ["3154#default"]],
|
|
1670
|
+
[12794, ["37#postDiv"]],
|
|
1671
|
+
[12795, ["37#postDiv"]],
|
|
1672
|
+
[12796, ["281#postDiv"]],
|
|
1673
|
+
[12798, ["37#postDiv"]],
|
|
1674
|
+
[12799, ["51#postDiv"]],
|
|
1675
|
+
[12838, ["1371#postMul"]],
|
|
1676
|
+
[12839, ["1372#postMul"]]
|
|
1398
1677
|
]);
|
|
1678
|
+
var CONTEXT_KIND = {
|
|
1679
|
+
skill: ["skill", "character"],
|
|
1680
|
+
ship: ["ship"],
|
|
1681
|
+
implant: ["implant"],
|
|
1682
|
+
booster: ["booster"],
|
|
1683
|
+
module: ["module"],
|
|
1684
|
+
drone: ["drone"],
|
|
1685
|
+
fighter: ["fighter"],
|
|
1686
|
+
subsystem: ["subsystem"],
|
|
1687
|
+
mode: ["mode"]
|
|
1688
|
+
};
|
|
1689
|
+
function stackPenaltyGroup(effectID, attributeID, receiver, sourceKind) {
|
|
1690
|
+
const keys = STACKING_PENALISED.get(effectID);
|
|
1691
|
+
if (!keys) return null;
|
|
1692
|
+
const id = attributeID === void 0 ? null : String(attributeID);
|
|
1693
|
+
for (const key of keys) {
|
|
1694
|
+
const bang = key.indexOf("!");
|
|
1695
|
+
const exempt = bang < 0 ? [] : key.slice(bang + 1).split("+");
|
|
1696
|
+
const head = bang < 0 ? key : key.slice(0, bang);
|
|
1697
|
+
const hash = head.lastIndexOf("#");
|
|
1698
|
+
const group = head.slice(hash + 1);
|
|
1699
|
+
const target = head.slice(0, hash);
|
|
1700
|
+
const colon = target.indexOf(":");
|
|
1701
|
+
const recv = colon < 0 ? null : target.slice(0, colon);
|
|
1702
|
+
const attr = colon < 0 ? target : target.slice(colon + 1);
|
|
1703
|
+
if (recv !== null && recv !== receiver) continue;
|
|
1704
|
+
if (attr !== "*" && (id === null || attr !== id)) continue;
|
|
1705
|
+
if (exempt.some((c) => (CONTEXT_KIND[c] ?? []).includes(sourceKind))) return null;
|
|
1706
|
+
return group;
|
|
1707
|
+
}
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1399
1710
|
|
|
1400
1711
|
// src/modifierEngine.ts
|
|
1401
1712
|
var NO_PENALTY_KINDS = /* @__PURE__ */ new Set([
|
|
1402
1713
|
"skill",
|
|
1403
|
-
"
|
|
1404
|
-
"
|
|
1405
|
-
"
|
|
1406
|
-
"character"
|
|
1714
|
+
"character",
|
|
1715
|
+
"implant",
|
|
1716
|
+
"booster"
|
|
1407
1717
|
]);
|
|
1408
1718
|
function scaleForPipeline(rawValue, _unitID, op) {
|
|
1409
1719
|
if (op === "PostPercent") return rawValue / 100;
|
|
1410
1720
|
return rawValue;
|
|
1411
1721
|
}
|
|
1412
|
-
function
|
|
1722
|
+
function isPeerModifier(source, mi, ctx, effectID) {
|
|
1723
|
+
for (const t of ctx.targetsForModifier(mi, source, effectID)) {
|
|
1724
|
+
if (t.kind === "implant" || t.kind === "booster") return true;
|
|
1725
|
+
}
|
|
1726
|
+
return false;
|
|
1727
|
+
}
|
|
1728
|
+
function applySourceItem(source, ctx, dataset, pass = "all") {
|
|
1413
1729
|
const isLocalModule = source.kind === "module";
|
|
1414
1730
|
const selfMods = [];
|
|
1415
1731
|
const outMods = [];
|
|
@@ -1417,10 +1733,15 @@ function applySourceItem(source, ctx, dataset) {
|
|
|
1417
1733
|
if (LEGACY_HANDLED_EFFECT_IDS.has(eid)) continue;
|
|
1418
1734
|
if (SEC_STATUS_SCALED_EFFECT_IDS.has(eid)) continue;
|
|
1419
1735
|
if (isLocalModule && ctx.stoppedLocalEffectIDs.has(eid)) continue;
|
|
1736
|
+
if (source.kind === "booster" && BOOSTER_SIDE_EFFECT_IDS.has(eid) && !source.activeSideEffects.has(eid)) continue;
|
|
1420
1737
|
const effect = dataset.effects.get(eid);
|
|
1421
1738
|
if (!effect) continue;
|
|
1422
1739
|
if (!source.appliesAtState(effect)) continue;
|
|
1423
1740
|
for (const mi of effect.modifierInfo) {
|
|
1741
|
+
if (pass !== "all") {
|
|
1742
|
+
const peer = isPeerModifier(source, mi, ctx, effect.id);
|
|
1743
|
+
if (pass === "peer" ? !peer : peer) continue;
|
|
1744
|
+
}
|
|
1424
1745
|
(mi.domain === "itemID" ? selfMods : outMods).push({ effect, mi });
|
|
1425
1746
|
}
|
|
1426
1747
|
}
|
|
@@ -1463,7 +1784,7 @@ function applyLegacyFighterProjection(fighter, ctx) {
|
|
|
1463
1784
|
sourceID: fighter.id,
|
|
1464
1785
|
operation: "PostPercent",
|
|
1465
1786
|
value: pen / 100,
|
|
1466
|
-
stackingGroup: `
|
|
1787
|
+
stackingGroup: `default:${ATTR_MAX_VEL}`
|
|
1467
1788
|
});
|
|
1468
1789
|
}
|
|
1469
1790
|
}
|
|
@@ -1489,9 +1810,8 @@ function applyOneModifier(source, effect, mi, ctx, dataset) {
|
|
|
1489
1810
|
if (mi.modifiedAttributeID === void 0) return;
|
|
1490
1811
|
const computed = computeModifierValue(source, mi, ctx, dataset, op);
|
|
1491
1812
|
if (computed === null) return;
|
|
1492
|
-
const targets = ctx.targetsForModifier(mi, source);
|
|
1813
|
+
const targets = ctx.targetsForModifier(mi, source, effect.id);
|
|
1493
1814
|
if (targets.length === 0) return;
|
|
1494
|
-
const stackingGroup = computeStackingGroup(source, mi, dataset, effect);
|
|
1495
1815
|
const isMul = op === "PreMul" || op === "PostMul" || op === "PreDiv" || op === "PostDiv";
|
|
1496
1816
|
const value = isMul && computed.scaled ? 1 + computed.value : computed.value;
|
|
1497
1817
|
const sdeDefault = isMul ? dataset.attributes.get(mi.modifiedAttributeID)?.defaultValue ?? 0 : 0;
|
|
@@ -1501,7 +1821,7 @@ function applyOneModifier(source, effect, mi, ctx, dataset) {
|
|
|
1501
1821
|
sourceID: source.id,
|
|
1502
1822
|
operation: op,
|
|
1503
1823
|
value,
|
|
1504
|
-
stackingGroup,
|
|
1824
|
+
stackingGroup: computeStackingGroup(source, mi, dataset, effect, target, ctx),
|
|
1505
1825
|
resistanceAttributeID: effect.resistanceAttributeID
|
|
1506
1826
|
};
|
|
1507
1827
|
target.addAffliction(mi.modifiedAttributeID, affliction, sdeDefault);
|
|
@@ -2008,8 +2328,17 @@ var SHIP_BONUS_SCALING_SKILL = /* @__PURE__ */ new Map([
|
|
|
2008
2328
|
// Notable: Exhumer/Barge shield+armor resist role bonuses (Hulk/Skiff/
|
|
2009
2329
|
// Mackinaw shield resist 4 %→20 %), Bhaalgorn drone+laser (492), industrial
|
|
2010
2330
|
// command (Orca/Rorqual), Marauder/pirate/expedition hull bonuses.
|
|
2331
|
+
// NB: attr 310 (`cpuNeedBonus`) is deliberately ABSENT. The auto-derivation
|
|
2332
|
+
// proposed it because the Weapon Upgrades SKILL scales its own attr 310 per
|
|
2333
|
+
// level (pyfa Effect3519 — a skill source, handled by the skill path), but
|
|
2334
|
+
// no SHIP-side effect does: pyfa's Effect5901 (`roleBonusBulkheadCPU`,
|
|
2335
|
+
// freighters + jump freighters) takes `ship.getModifiedItemAttr('cpuNeedBonus')`
|
|
2336
|
+
// verbatim, and every one of the 18 hulls carrying the attribute holds a
|
|
2337
|
+
// FULL-value role bonus (-99 % / -100 %), never a per-level one. Scaling it
|
|
2338
|
+
// by Electronics Upgrades V turned an Obelisk's -100 % bulkhead CPU into
|
|
2339
|
+
// -500 %, so three Reinforced Bulkheads reported -480 tf of CPU used
|
|
2340
|
+
// instead of 0. Caught on real published freighter fits.
|
|
2011
2341
|
[66, 89611],
|
|
2012
|
-
[310, 3432],
|
|
2013
2342
|
[349, 19760],
|
|
2014
2343
|
[492, 3339],
|
|
2015
2344
|
[1296, 21610],
|
|
@@ -2177,6 +2506,9 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
2177
2506
|
if (source.kind === "ship") {
|
|
2178
2507
|
return { value: baseValue, scaled: false };
|
|
2179
2508
|
}
|
|
2509
|
+
if (source.kind === "implant" || source.kind === "booster") {
|
|
2510
|
+
return { value: baseValue, scaled: false };
|
|
2511
|
+
}
|
|
2180
2512
|
if (itemRequiresSkill(source, skillID)) {
|
|
2181
2513
|
return { value: baseValue, scaled: false };
|
|
2182
2514
|
}
|
|
@@ -2184,19 +2516,42 @@ function computeModifierValue(source, mi, ctx, dataset, op) {
|
|
|
2184
2516
|
}
|
|
2185
2517
|
return { value: baseValue, scaled: false };
|
|
2186
2518
|
}
|
|
2187
|
-
function
|
|
2188
|
-
if (
|
|
2189
|
-
if (
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2519
|
+
function penaltyReceiver(target, source, ctx) {
|
|
2520
|
+
if (target === ctx.ship) return "ship";
|
|
2521
|
+
if (target === source) return "self";
|
|
2522
|
+
switch (target.kind) {
|
|
2523
|
+
case "charge":
|
|
2524
|
+
return "charge";
|
|
2525
|
+
case "drone":
|
|
2526
|
+
return "drone";
|
|
2527
|
+
case "fighter":
|
|
2528
|
+
return "fighter";
|
|
2529
|
+
case "character":
|
|
2530
|
+
return "character";
|
|
2531
|
+
case "module":
|
|
2532
|
+
case "subsystem":
|
|
2533
|
+
return "module";
|
|
2534
|
+
default:
|
|
2535
|
+
return "self";
|
|
2196
2536
|
}
|
|
2197
|
-
return `attr:${mi.modifiedAttributeID}`;
|
|
2198
2537
|
}
|
|
2199
|
-
|
|
2538
|
+
function computeStackingGroup(source, mi, dataset, effect, target, ctx) {
|
|
2539
|
+
if (NO_PENALTY_KINDS.has(source.kind)) return null;
|
|
2540
|
+
const group = stackPenaltyGroup(
|
|
2541
|
+
effect.id,
|
|
2542
|
+
mi.modifiedAttributeID,
|
|
2543
|
+
penaltyReceiver(target, source, ctx),
|
|
2544
|
+
source.kind
|
|
2545
|
+
);
|
|
2546
|
+
if (group === null) return null;
|
|
2547
|
+
if (!HONOURED_PENALTY_GROUPS.has(group)) return `default:${mi.modifiedAttributeID}`;
|
|
2548
|
+
return `${group}:${mi.modifiedAttributeID}`;
|
|
2549
|
+
}
|
|
2550
|
+
var HONOURED_PENALTY_GROUPS = /* @__PURE__ */ new Set([
|
|
2551
|
+
"postMul",
|
|
2552
|
+
"postDiv",
|
|
2553
|
+
"postPerc",
|
|
2554
|
+
"postPercent",
|
|
2200
2555
|
"cloakingScanResolutionMultiplier"
|
|
2201
2556
|
]);
|
|
2202
2557
|
function mapSourceKind(kind) {
|
|
@@ -2260,7 +2615,7 @@ function applySkills(ctx, dataset) {
|
|
|
2260
2615
|
const sourceAttrMeta = dataset.attributes.get(mi.modifyingAttributeID);
|
|
2261
2616
|
const baseVal = scaleForPipeline(rawBase, sourceAttrMeta?.unitID, op);
|
|
2262
2617
|
const value = isMul ? 1 + baseVal * level : baseVal * level;
|
|
2263
|
-
const stackingGroup = NO_PENALTY_KINDS.has("character") ? null : `
|
|
2618
|
+
const stackingGroup = NO_PENALTY_KINDS.has("character") ? null : `default:${mi.modifiedAttributeID}`;
|
|
2264
2619
|
const targets = resolveSkillTargets(mi, ctx);
|
|
2265
2620
|
if (targets.length === 0) continue;
|
|
2266
2621
|
const affliction = {
|
|
@@ -2472,7 +2827,7 @@ function applyLegacyPropMods(ctx) {
|
|
|
2472
2827
|
sourceID: mod.id,
|
|
2473
2828
|
operation: "PostPercent",
|
|
2474
2829
|
value: sigBonus / 100,
|
|
2475
|
-
stackingGroup: `
|
|
2830
|
+
stackingGroup: `default:${ATTR_SIGNATURE_RADIUS}`
|
|
2476
2831
|
});
|
|
2477
2832
|
}
|
|
2478
2833
|
}
|
|
@@ -2480,13 +2835,13 @@ function applyLegacyPropMods(ctx) {
|
|
|
2480
2835
|
var ENTOSIS_LINK_EFFECT_ID = 6063;
|
|
2481
2836
|
function applyLegacyEntosisLink(ctx) {
|
|
2482
2837
|
for (const mod of ctx.modules) {
|
|
2483
|
-
if (mod.state
|
|
2838
|
+
if (mod.state !== "ACTIVE" && mod.state !== "OVERLOAD") continue;
|
|
2484
2839
|
if (!mod.effectIDs.has(ENTOSIS_LINK_EFFECT_ID)) continue;
|
|
2485
2840
|
const sensorPairs = [
|
|
2486
|
-
[1027,
|
|
2841
|
+
[1027, ATTR_SCAN_GRAVIMETRIC],
|
|
2487
2842
|
[1028, ATTR_SCAN_LADAR],
|
|
2488
2843
|
[1029, ATTR_SCAN_MAGNETOMETRIC],
|
|
2489
|
-
[1030,
|
|
2844
|
+
[1030, ATTR_SCAN_RADAR]
|
|
2490
2845
|
];
|
|
2491
2846
|
for (const [srcAttr, tgtAttr] of sensorPairs) {
|
|
2492
2847
|
const bonusPct = mod.getBase(srcAttr) ?? 0;
|
|
@@ -2520,7 +2875,7 @@ function applyLegacyCapitalEhe(ctx) {
|
|
|
2520
2875
|
sourceID: mod.id,
|
|
2521
2876
|
operation: "PostMul",
|
|
2522
2877
|
value: factor,
|
|
2523
|
-
stackingGroup: `
|
|
2878
|
+
stackingGroup: `default:${tgtAttr}`
|
|
2524
2879
|
});
|
|
2525
2880
|
}
|
|
2526
2881
|
}
|
|
@@ -2599,13 +2954,13 @@ function applyLegacyMjdSigBloom(ctx) {
|
|
|
2599
2954
|
if (!isMjd) continue;
|
|
2600
2955
|
const bonusPct = mod.getFinal(ATTR_SIGNATURE_RADIUS_BONUS_PERCENT, 0);
|
|
2601
2956
|
if (bonusPct === 0) continue;
|
|
2957
|
+
const penalised = [...LEGACY_MJD_EFFECT_IDS].some((eid) => mod.effectIDs.has(eid) && stackPenaltyGroup(eid, ATTR_SIGNATURE_RADIUS, "ship", "module") !== null);
|
|
2602
2958
|
ctx.ship.addAffliction(ATTR_SIGNATURE_RADIUS, {
|
|
2603
2959
|
sourceKind: "module",
|
|
2604
2960
|
sourceID: mod.id,
|
|
2605
2961
|
operation: "PostPercent",
|
|
2606
2962
|
value: bonusPct / 100,
|
|
2607
|
-
stackingGroup: `
|
|
2608
|
-
// stack-penalised with MWD bloom
|
|
2963
|
+
stackingGroup: penalised ? `default:${ATTR_SIGNATURE_RADIUS}` : null
|
|
2609
2964
|
});
|
|
2610
2965
|
}
|
|
2611
2966
|
}
|
|
@@ -3008,7 +3363,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3008
3363
|
sourceID,
|
|
3009
3364
|
operation: "PreMul",
|
|
3010
3365
|
value: factor,
|
|
3011
|
-
stackingGroup: HULL_RES_ATTRS.has(tgt) ? null : `
|
|
3366
|
+
stackingGroup: HULL_RES_ATTRS.has(tgt) ? null : `default:${tgt}`
|
|
3012
3367
|
});
|
|
3013
3368
|
}
|
|
3014
3369
|
const maxRangeBonus = mod.getFinal(ATTR_MAX_RANGE_BONUS, 0);
|
|
@@ -3023,14 +3378,14 @@ function applyLegacyBastion(ctx) {
|
|
|
3023
3378
|
sourceID,
|
|
3024
3379
|
operation: "PostPercent",
|
|
3025
3380
|
value: maxRangeBonus / 100,
|
|
3026
|
-
stackingGroup: `
|
|
3381
|
+
stackingGroup: `default:${ATTR_MAX_RANGE}`
|
|
3027
3382
|
});
|
|
3028
3383
|
if (turretRofBonus !== 0) target.addAffliction(ATTR_SPEED, {
|
|
3029
3384
|
sourceKind: "module",
|
|
3030
3385
|
sourceID,
|
|
3031
3386
|
operation: "PostPercent",
|
|
3032
3387
|
value: turretRofBonus / 100,
|
|
3033
|
-
stackingGroup: `
|
|
3388
|
+
stackingGroup: `default:${ATTR_SPEED}`
|
|
3034
3389
|
});
|
|
3035
3390
|
}
|
|
3036
3391
|
if (matchTurretFalloff(target) && falloffBonusV !== 0) {
|
|
@@ -3039,7 +3394,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3039
3394
|
sourceID,
|
|
3040
3395
|
operation: "PostPercent",
|
|
3041
3396
|
value: falloffBonusV / 100,
|
|
3042
|
-
stackingGroup: `
|
|
3397
|
+
stackingGroup: `default:${ATTR_FALLOFF}`
|
|
3043
3398
|
});
|
|
3044
3399
|
}
|
|
3045
3400
|
}
|
|
@@ -3054,7 +3409,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3054
3409
|
sourceID,
|
|
3055
3410
|
operation: "PostPercent",
|
|
3056
3411
|
value: missileVelocityBonus / 100,
|
|
3057
|
-
stackingGroup: `
|
|
3412
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3058
3413
|
});
|
|
3059
3414
|
}
|
|
3060
3415
|
if (matchMissileLauncherROF(target) && missileRofBonus !== 0) {
|
|
@@ -3063,7 +3418,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3063
3418
|
sourceID,
|
|
3064
3419
|
operation: "PostPercent",
|
|
3065
3420
|
value: missileRofBonus / 100,
|
|
3066
|
-
stackingGroup: `
|
|
3421
|
+
stackingGroup: `default:${ATTR_SPEED}`
|
|
3067
3422
|
});
|
|
3068
3423
|
}
|
|
3069
3424
|
}
|
|
@@ -3079,7 +3434,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3079
3434
|
sourceID,
|
|
3080
3435
|
operation: "PostPercent",
|
|
3081
3436
|
value: armorRepBonus / 100,
|
|
3082
|
-
stackingGroup: `
|
|
3437
|
+
stackingGroup: `default:${ATTR_ARMOR_REP_AMOUNT}`
|
|
3083
3438
|
});
|
|
3084
3439
|
}
|
|
3085
3440
|
if (isShield && shieldBoostBonus !== 0) {
|
|
@@ -3088,7 +3443,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3088
3443
|
sourceID,
|
|
3089
3444
|
operation: "PostPercent",
|
|
3090
3445
|
value: shieldBoostBonus / 100,
|
|
3091
|
-
stackingGroup: `
|
|
3446
|
+
stackingGroup: `default:${ATTR_SHIELD_REP_AMOUNT}`
|
|
3092
3447
|
});
|
|
3093
3448
|
}
|
|
3094
3449
|
if ((isArmor || isShield) && repairDurationBonus !== 0) {
|
|
@@ -3132,7 +3487,7 @@ function applyLegacyBastion(ctx) {
|
|
|
3132
3487
|
sourceID,
|
|
3133
3488
|
operation: "PostPercent",
|
|
3134
3489
|
value: v / 100,
|
|
3135
|
-
stackingGroup: `
|
|
3490
|
+
stackingGroup: `default:${tgt}`
|
|
3136
3491
|
});
|
|
3137
3492
|
}
|
|
3138
3493
|
const resistPairs = [
|
|
@@ -3231,7 +3586,7 @@ function applyLegacyDoomsdaySelfEffects(ctx) {
|
|
|
3231
3586
|
sourceID: mod.id,
|
|
3232
3587
|
operation: "PostPercent",
|
|
3233
3588
|
value: speedFactor / 100,
|
|
3234
|
-
stackingGroup: `
|
|
3589
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3235
3590
|
});
|
|
3236
3591
|
}
|
|
3237
3592
|
const siegeStatus = mod.getFinal(ATTR_SIEGE_WARP_STATUS, 0);
|
|
@@ -3302,7 +3657,7 @@ function applyCommandBuff(buffID, value, sourceMod, ctx) {
|
|
|
3302
3657
|
sourceID: sourceMod.id,
|
|
3303
3658
|
operation,
|
|
3304
3659
|
value: scaledValue,
|
|
3305
|
-
stackingGroup: `
|
|
3660
|
+
stackingGroup: `default:${attrID}`
|
|
3306
3661
|
});
|
|
3307
3662
|
for (const mi of dbuff.itemModifiers) {
|
|
3308
3663
|
ctx.ship.addAffliction(mi.dogmaAttributeID, affliction(op, mi.dogmaAttributeID));
|
|
@@ -3648,7 +4003,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3648
4003
|
sourceID: fighter.id,
|
|
3649
4004
|
operation: "PostPercent",
|
|
3650
4005
|
value: speedPct / 100,
|
|
3651
|
-
stackingGroup: `
|
|
4006
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3652
4007
|
});
|
|
3653
4008
|
}
|
|
3654
4009
|
}
|
|
@@ -3660,7 +4015,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3660
4015
|
sourceID: fighter.id,
|
|
3661
4016
|
operation: "PostPercent",
|
|
3662
4017
|
value: speedPct / 100,
|
|
3663
|
-
stackingGroup: `
|
|
4018
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3664
4019
|
});
|
|
3665
4020
|
}
|
|
3666
4021
|
const sigPct = fighter.getFinal(ATTR_FIGHTER_MWD_SIG_BONUS, 0);
|
|
@@ -3670,7 +4025,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3670
4025
|
sourceID: fighter.id,
|
|
3671
4026
|
operation: "PostPercent",
|
|
3672
4027
|
value: sigPct / 100,
|
|
3673
|
-
stackingGroup: `
|
|
4028
|
+
stackingGroup: `default:${ATTR_SIGNATURE_RADIUS}`
|
|
3674
4029
|
});
|
|
3675
4030
|
}
|
|
3676
4031
|
}
|
|
@@ -3682,7 +4037,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3682
4037
|
sourceID: fighter.id,
|
|
3683
4038
|
operation: "PostPercent",
|
|
3684
4039
|
value: speedPct / 100,
|
|
3685
|
-
stackingGroup: `
|
|
4040
|
+
stackingGroup: `default:${ATTR_MAX_VELOCITY}`
|
|
3686
4041
|
});
|
|
3687
4042
|
}
|
|
3688
4043
|
const sigPct = fighter.getFinal(ATTR_FIGHTER_EVASIVE_SIG_BONUS, 0);
|
|
@@ -3692,7 +4047,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3692
4047
|
sourceID: fighter.id,
|
|
3693
4048
|
operation: "PostPercent",
|
|
3694
4049
|
value: sigPct / 100,
|
|
3695
|
-
stackingGroup: `
|
|
4050
|
+
stackingGroup: `default:${ATTR_SIGNATURE_RADIUS}`
|
|
3696
4051
|
});
|
|
3697
4052
|
}
|
|
3698
4053
|
const resonances = [
|
|
@@ -3709,7 +4064,7 @@ function applyLegacyFighterAbilities(ctx) {
|
|
|
3709
4064
|
sourceID: fighter.id,
|
|
3710
4065
|
operation: "PostMul",
|
|
3711
4066
|
value: mul,
|
|
3712
|
-
stackingGroup: `
|
|
4067
|
+
stackingGroup: `default:${tgtAttr}`
|
|
3713
4068
|
});
|
|
3714
4069
|
}
|
|
3715
4070
|
}
|
|
@@ -3829,7 +4184,7 @@ function applyLegacyRAH(ctx, damageProfile) {
|
|
|
3829
4184
|
sourceID: mod.id,
|
|
3830
4185
|
operation: "PreMul",
|
|
3831
4186
|
value: factor,
|
|
3832
|
-
stackingGroup: `
|
|
4187
|
+
stackingGroup: `default:${tgtAttr}`
|
|
3833
4188
|
});
|
|
3834
4189
|
}
|
|
3835
4190
|
}
|
|
@@ -3993,176 +4348,6 @@ function computeTotalEhp(shield, armor, hull, useProfile) {
|
|
|
3993
4348
|
return Number.isFinite(total) ? total : Number.MAX_SAFE_INTEGER;
|
|
3994
4349
|
}
|
|
3995
4350
|
|
|
3996
|
-
// src/fitChecks.ts
|
|
3997
|
-
function readAttr(t, id) {
|
|
3998
|
-
return t.attributes.find((a) => a.id === id)?.v;
|
|
3999
|
-
}
|
|
4000
|
-
function typeFitsSlotType(t, slot) {
|
|
4001
|
-
for (const e of t.effects) {
|
|
4002
|
-
const mapped = SLOT_EFFECT_TO_SLOT_TYPE[e.id];
|
|
4003
|
-
if (mapped === slot) return true;
|
|
4004
|
-
}
|
|
4005
|
-
return false;
|
|
4006
|
-
}
|
|
4007
|
-
function isTurretWeapon(t) {
|
|
4008
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "TURRET");
|
|
4009
|
-
}
|
|
4010
|
-
function isMissileLauncher(t) {
|
|
4011
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "MISSILE");
|
|
4012
|
-
}
|
|
4013
|
-
function isSmartBomb(t) {
|
|
4014
|
-
return t.effects.some((e) => WEAPON_EFFECT_KIND[e.id] === "SMARTBOMB");
|
|
4015
|
-
}
|
|
4016
|
-
function shipGroupRestrictions(t) {
|
|
4017
|
-
const out = [];
|
|
4018
|
-
for (const id of CAN_FIT_SHIP_GROUP_ATTRS) {
|
|
4019
|
-
const v = readAttr(t, id);
|
|
4020
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
4021
|
-
}
|
|
4022
|
-
return out;
|
|
4023
|
-
}
|
|
4024
|
-
function shipTypeRestrictions(t) {
|
|
4025
|
-
const out = [];
|
|
4026
|
-
for (const id of CAN_FIT_SHIP_TYPE_ATTRS) {
|
|
4027
|
-
const v = readAttr(t, id);
|
|
4028
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
4029
|
-
}
|
|
4030
|
-
return out;
|
|
4031
|
-
}
|
|
4032
|
-
function maxGroupFittedFor(mod) {
|
|
4033
|
-
const v = readAttr(mod, ATTR.MAX_GROUP_FITTED);
|
|
4034
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
4035
|
-
}
|
|
4036
|
-
function maxTypeFittedFor(mod) {
|
|
4037
|
-
const v = readAttr(mod, ATTR.MAX_TYPE_FITTED);
|
|
4038
|
-
return v != null && v > 0 ? Math.round(v) : void 0;
|
|
4039
|
-
}
|
|
4040
|
-
function freeFitGroupSlotsFor(mod, fittedModules, dataset) {
|
|
4041
|
-
const groupCap = maxGroupFittedFor(mod);
|
|
4042
|
-
const typeCap = maxTypeFittedFor(mod);
|
|
4043
|
-
if (groupCap === void 0 && typeCap === void 0) return Number.POSITIVE_INFINITY;
|
|
4044
|
-
let groupCount = 0;
|
|
4045
|
-
let typeCount = 0;
|
|
4046
|
-
for (const fm of fittedModules) {
|
|
4047
|
-
if (typeCap !== void 0 && fm.typeID === mod.id) typeCount++;
|
|
4048
|
-
if (groupCap !== void 0) {
|
|
4049
|
-
const t = dataset.getType(fm.typeID);
|
|
4050
|
-
if (t && t.groupID === mod.groupID) groupCount++;
|
|
4051
|
-
}
|
|
4052
|
-
}
|
|
4053
|
-
let free = Number.POSITIVE_INFINITY;
|
|
4054
|
-
if (groupCap !== void 0) free = Math.min(free, groupCap - groupCount);
|
|
4055
|
-
if (typeCap !== void 0) free = Math.min(free, typeCap - typeCount);
|
|
4056
|
-
return Math.max(0, free);
|
|
4057
|
-
}
|
|
4058
|
-
function canFitModuleOnShip(mod, ship, slot, fitContext) {
|
|
4059
|
-
if (!typeFitsSlotType(mod, slot)) {
|
|
4060
|
-
return { ok: false, reason: `Module does not declare a ${slot} slot effect` };
|
|
4061
|
-
}
|
|
4062
|
-
if (!ship) return { ok: true };
|
|
4063
|
-
const allowedGroups = shipGroupRestrictions(mod);
|
|
4064
|
-
const allowedTypes = shipTypeRestrictions(mod);
|
|
4065
|
-
if (allowedGroups.length > 0 || allowedTypes.length > 0) {
|
|
4066
|
-
const groupOk = allowedGroups.includes(ship.groupID);
|
|
4067
|
-
const typeOk = allowedTypes.includes(ship.id);
|
|
4068
|
-
if (!groupOk && !typeOk) {
|
|
4069
|
-
return { ok: false, reason: "Ship hull/group not allowed by this module" };
|
|
4070
|
-
}
|
|
4071
|
-
}
|
|
4072
|
-
if (slot === "RIG") {
|
|
4073
|
-
const modSize = readAttr(mod, ATTR.RIG_SIZE);
|
|
4074
|
-
const shipSize = readAttr(ship, ATTR.RIG_SIZE);
|
|
4075
|
-
if (modSize != null && shipSize != null && Math.round(modSize) !== Math.round(shipSize)) {
|
|
4076
|
-
return { ok: false, reason: "Rig size mismatch" };
|
|
4077
|
-
}
|
|
4078
|
-
}
|
|
4079
|
-
if (slot === "SUBSYSTEM") {
|
|
4080
|
-
const fitsTo = readAttr(mod, 1380);
|
|
4081
|
-
if (fitsTo != null && fitsTo > 0 && Math.round(fitsTo) !== ship.id) {
|
|
4082
|
-
return { ok: false, reason: "Subsystem locked to a different T3C hull" };
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
if (slot === "HI") {
|
|
4086
|
-
if (isTurretWeapon(mod)) {
|
|
4087
|
-
const turrets = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
4088
|
-
if (turrets <= 0) {
|
|
4089
|
-
return { ok: false, reason: "Ship has no turret hardpoints" };
|
|
4090
|
-
}
|
|
4091
|
-
} else if (isMissileLauncher(mod)) {
|
|
4092
|
-
const launchers = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
4093
|
-
if (launchers <= 0) {
|
|
4094
|
-
return { ok: false, reason: "Ship has no launcher hardpoints" };
|
|
4095
|
-
}
|
|
4096
|
-
}
|
|
4097
|
-
}
|
|
4098
|
-
if (fitContext) {
|
|
4099
|
-
const free = freeFitGroupSlotsFor(mod, fitContext.fittedModules, fitContext.dataset);
|
|
4100
|
-
if (free <= 0) {
|
|
4101
|
-
const cap = maxTypeFittedFor(mod) ?? maxGroupFittedFor(mod);
|
|
4102
|
-
return { ok: false, reason: `Only ${cap} of this module type can be fitted to a ship` };
|
|
4103
|
-
}
|
|
4104
|
-
}
|
|
4105
|
-
return { ok: true };
|
|
4106
|
-
}
|
|
4107
|
-
function chargeGroupsForModule(mod) {
|
|
4108
|
-
const out = [];
|
|
4109
|
-
for (const attrID of CHARGE_GROUP_ATTRS) {
|
|
4110
|
-
const v = readAttr(mod, attrID);
|
|
4111
|
-
if (v != null && v > 0) out.push(Math.round(v));
|
|
4112
|
-
}
|
|
4113
|
-
return out;
|
|
4114
|
-
}
|
|
4115
|
-
function moduleAcceptsAnyCharge(mod) {
|
|
4116
|
-
return chargeGroupsForModule(mod).length > 0;
|
|
4117
|
-
}
|
|
4118
|
-
function moduleAcceptsChargeType(mod, charge) {
|
|
4119
|
-
const allowedGroups = chargeGroupsForModule(mod);
|
|
4120
|
-
if (allowedGroups.length === 0) return false;
|
|
4121
|
-
if (!allowedGroups.includes(charge.groupID)) return false;
|
|
4122
|
-
const modSize = readAttr(mod, ATTR.CHARGE_SIZE);
|
|
4123
|
-
const chargeSize = readAttr(charge, ATTR.CHARGE_SIZE);
|
|
4124
|
-
if (modSize != null && chargeSize != null && Math.round(chargeSize) > Math.round(modSize)) {
|
|
4125
|
-
return false;
|
|
4126
|
-
}
|
|
4127
|
-
return true;
|
|
4128
|
-
}
|
|
4129
|
-
var ACTIVE_EFFECT_CATEGORIES = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
4130
|
-
function isActivatableModule(mod, effects) {
|
|
4131
|
-
for (const e of mod.effects) {
|
|
4132
|
-
const eff = effects.get(e.id);
|
|
4133
|
-
if (eff && eff.effectCategoryID !== void 0 && ACTIVE_EFFECT_CATEGORIES.has(eff.effectCategoryID)) {
|
|
4134
|
-
return true;
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
return false;
|
|
4138
|
-
}
|
|
4139
|
-
var DEFAULT_OFFLINE_ACTIVATION_GROUPS = /* @__PURE__ */ new Set([
|
|
4140
|
-
330,
|
|
4141
|
-
4117
|
|
4142
|
-
]);
|
|
4143
|
-
function defaultStateForModule(mod, effects) {
|
|
4144
|
-
if (DEFAULT_OFFLINE_ACTIVATION_GROUPS.has(mod.groupID)) return "ONLINE";
|
|
4145
|
-
return isActivatableModule(mod, effects) ? "ACTIVE" : "ONLINE";
|
|
4146
|
-
}
|
|
4147
|
-
function freeHardpointsFor(mod, ship, fittedHiModules, dataset) {
|
|
4148
|
-
const turretCap = readAttr(ship, ATTR.TURRET_HARDPOINTS) ?? 0;
|
|
4149
|
-
const launcherCap = readAttr(ship, ATTR.LAUNCHER_HARDPOINTS) ?? 0;
|
|
4150
|
-
let turretUsed = 0;
|
|
4151
|
-
let launcherUsed = 0;
|
|
4152
|
-
for (const m of fittedHiModules) {
|
|
4153
|
-
const t = dataset.getType(m.typeID);
|
|
4154
|
-
if (!t) continue;
|
|
4155
|
-
if (isTurretWeapon(t)) turretUsed++;
|
|
4156
|
-
else if (isMissileLauncher(t)) launcherUsed++;
|
|
4157
|
-
}
|
|
4158
|
-
let hardpointFree;
|
|
4159
|
-
if (isTurretWeapon(mod)) hardpointFree = Math.max(0, turretCap - turretUsed);
|
|
4160
|
-
else if (isMissileLauncher(mod)) hardpointFree = Math.max(0, launcherCap - launcherUsed);
|
|
4161
|
-
else hardpointFree = Number.POSITIVE_INFINITY;
|
|
4162
|
-
const groupFree = freeFitGroupSlotsFor(mod, fittedHiModules, dataset);
|
|
4163
|
-
return Math.min(hardpointFree, groupFree);
|
|
4164
|
-
}
|
|
4165
|
-
|
|
4166
4351
|
// src/derived/capacitor.ts
|
|
4167
4352
|
function computeCapacitor(ctx, dataset) {
|
|
4168
4353
|
const ship = ctx.ship;
|
|
@@ -5199,22 +5384,46 @@ function computeFit(fit, dataset, opts) {
|
|
|
5199
5384
|
const t = dataset.getType(ff.typeID);
|
|
5200
5385
|
return t ? [makeFighterState(ff, t)] : [];
|
|
5201
5386
|
});
|
|
5202
|
-
const implants =
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5387
|
+
const implants = dropSlotCollisions(
|
|
5388
|
+
fit.implants.flatMap((fi) => {
|
|
5389
|
+
const t = dataset.getType(fi.typeID);
|
|
5390
|
+
return t ? [{ slot: t.attributes.find((a) => a.id === ATTR_IMPLANTNESS)?.v ?? fi.slot, item: makeImplantState(fi, t) }] : [];
|
|
5391
|
+
})
|
|
5392
|
+
);
|
|
5393
|
+
const boosters = dropSlotCollisions(
|
|
5394
|
+
fit.boosters.flatMap((fb) => {
|
|
5395
|
+
const t = dataset.getType(fb.typeID);
|
|
5396
|
+
return t ? [{ slot: t.attributes.find((a) => a.id === ATTR_BOOSTERNESS)?.v ?? fb.slot, item: makeBoosterState(fb, t) }] : [];
|
|
5397
|
+
})
|
|
5398
|
+
);
|
|
5210
5399
|
const subsystems = fit.subsystems.flatMap((fs2) => {
|
|
5211
5400
|
const t = dataset.getType(fs2.typeID);
|
|
5212
5401
|
return t ? [makeSubsystemState(fs2, t)] : [];
|
|
5213
5402
|
});
|
|
5214
5403
|
let mode;
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5404
|
+
const modeTypeID = fit.modeTypeID ?? defaultModeTypeIDFor(shipType, dataset);
|
|
5405
|
+
if (modeTypeID !== void 0) {
|
|
5406
|
+
const t = dataset.getType(modeTypeID);
|
|
5407
|
+
if (t) mode = makeModeState(modeTypeID, t);
|
|
5408
|
+
}
|
|
5409
|
+
ship.attr(
|
|
5410
|
+
ATTR.DRONE_CONTROL_RANGE,
|
|
5411
|
+
dataset.attributes.get(ATTR.DRONE_CONTROL_RANGE)?.defaultValue ?? 2e4
|
|
5412
|
+
);
|
|
5413
|
+
for (const it of [
|
|
5414
|
+
ship,
|
|
5415
|
+
character,
|
|
5416
|
+
mode,
|
|
5417
|
+
...modules,
|
|
5418
|
+
...drones,
|
|
5419
|
+
...fighters,
|
|
5420
|
+
...implants,
|
|
5421
|
+
...boosters,
|
|
5422
|
+
...subsystems
|
|
5423
|
+
]) {
|
|
5424
|
+
if (!it) continue;
|
|
5425
|
+
it.attrSchema = dataset.attributes;
|
|
5426
|
+
if (it.charge) it.charge.attrSchema = dataset.attributes;
|
|
5218
5427
|
}
|
|
5219
5428
|
const skillLevels = /* @__PURE__ */ new Map();
|
|
5220
5429
|
for (const [k, v] of Object.entries(opts.skillProfile.skills)) {
|
|
@@ -5260,8 +5469,9 @@ function computeFit(fit, dataset, opts) {
|
|
|
5260
5469
|
for (const m of modules) applySourceItem(m, ctx, dataset);
|
|
5261
5470
|
for (const d of drones) applySourceItem(d, ctx, dataset);
|
|
5262
5471
|
for (const f of fighters) applySourceItem(f, ctx, dataset);
|
|
5263
|
-
|
|
5264
|
-
for (const
|
|
5472
|
+
const charSide = [...implants, ...boosters];
|
|
5473
|
+
for (const it of charSide) applySourceItem(it, ctx, dataset, "peer");
|
|
5474
|
+
for (const it of charSide) applySourceItem(it, ctx, dataset, "outgoing");
|
|
5265
5475
|
if (mode) applySourceItem(mode, ctx, dataset);
|
|
5266
5476
|
applyLegacyOverload(ctx);
|
|
5267
5477
|
applyLegacyPropMods(ctx);
|
|
@@ -5468,6 +5678,7 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
5468
5678
|
const cap = computeCapacitor(ctx, dataset);
|
|
5469
5679
|
const tank = computeTank(ctx);
|
|
5470
5680
|
const offense = computeOffense(ctx, dataset, fit);
|
|
5681
|
+
const sensor = pickSensorStrength(ship);
|
|
5471
5682
|
return {
|
|
5472
5683
|
fitting: {
|
|
5473
5684
|
cpuUsed,
|
|
@@ -5556,8 +5767,8 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
5556
5767
|
)),
|
|
5557
5768
|
signatureRadius: ship.getFinal(ATTR.SIGNATURE_RADIUS, 0),
|
|
5558
5769
|
scanResolution: ship.getFinal(ATTR.SCAN_RESOLUTION, 0),
|
|
5559
|
-
sensorStrength:
|
|
5560
|
-
sensorType:
|
|
5770
|
+
sensorStrength: sensor.value,
|
|
5771
|
+
sensorType: sensor.type
|
|
5561
5772
|
},
|
|
5562
5773
|
drones: {
|
|
5563
5774
|
bayUsed: droneBayUsed,
|
|
@@ -5568,13 +5779,12 @@ function deriveStats(ctx, dataset, damageProfile, fit, projectionReports) {
|
|
|
5568
5779
|
// ItemState per FitDrone stack, so filtering by state counted five
|
|
5569
5780
|
// active Ogre IIs as 1 — and this is the number bandwidth gates.
|
|
5570
5781
|
active: fit.drones.reduce((n, d) => n + d.countActive, 0),
|
|
5571
|
-
//
|
|
5572
|
-
//
|
|
5573
|
-
//
|
|
5574
|
-
//
|
|
5575
|
-
//
|
|
5576
|
-
|
|
5577
|
-
controlRange: ship.getFinal(ATTR.DRONE_CONTROL_RANGE, 2e4) + ctx.character.getFinal(ATTR.DRONE_CONTROL_RANGE, 0)
|
|
5782
|
+
// ONE accumulator, on the ship (attr 458, SDE default 20 km) — the
|
|
5783
|
+
// charID-domain modifiers that carry the skills and the hull bonus
|
|
5784
|
+
// are re-routed there, see FitContext.targetsForModifier. Summing a
|
|
5785
|
+
// ship half and a character half instead let a percentage bonus
|
|
5786
|
+
// apply to only one of them.
|
|
5787
|
+
controlRange: ship.getFinal(ATTR.DRONE_CONTROL_RANGE, 2e4)
|
|
5578
5788
|
},
|
|
5579
5789
|
projected: projectionReports,
|
|
5580
5790
|
structure: computeStructureMeta(ctx),
|
|
@@ -5601,18 +5811,36 @@ function computeAlignTime(ship) {
|
|
|
5601
5811
|
if (!mass || !agility) return 0;
|
|
5602
5812
|
return Math.log(4) * mass * agility / 1e6;
|
|
5603
5813
|
}
|
|
5814
|
+
var ATTR_IMPLANTNESS = 331;
|
|
5815
|
+
var ATTR_BOOSTERNESS = 1087;
|
|
5816
|
+
function dropSlotCollisions(entries) {
|
|
5817
|
+
const taken = /* @__PURE__ */ new Set();
|
|
5818
|
+
const out = [];
|
|
5819
|
+
for (const e of entries) {
|
|
5820
|
+
if (taken.has(e.slot)) continue;
|
|
5821
|
+
taken.add(e.slot);
|
|
5822
|
+
out.push(e.item);
|
|
5823
|
+
}
|
|
5824
|
+
return out;
|
|
5825
|
+
}
|
|
5604
5826
|
function pickSensorStrength(ship) {
|
|
5605
5827
|
const candidates = [
|
|
5606
|
-
{ attr: ATTR.SCAN_RADAR_STRENGTH, type: "radar" },
|
|
5607
|
-
{ attr: ATTR.SCAN_LADAR_STRENGTH, type: "ladar" },
|
|
5608
5828
|
{ attr: ATTR.SCAN_MAGNETOMETRIC_STRENGTH, type: "magnetometric" },
|
|
5829
|
+
{ attr: ATTR.SCAN_LADAR_STRENGTH, type: "ladar" },
|
|
5830
|
+
{ attr: ATTR.SCAN_RADAR_STRENGTH, type: "radar" },
|
|
5609
5831
|
{ attr: ATTR.SCAN_GRAVIMETRIC_STRENGTH, type: "gravimetric" }
|
|
5610
5832
|
];
|
|
5833
|
+
let best = -1;
|
|
5834
|
+
let type = "unknown";
|
|
5611
5835
|
for (const c of candidates) {
|
|
5612
5836
|
const v = ship.getFinal(c.attr, 0);
|
|
5613
|
-
if (v >
|
|
5837
|
+
if (v > best) {
|
|
5838
|
+
best = v;
|
|
5839
|
+
type = c.type;
|
|
5840
|
+
} else if (v === best) type = "multispectral";
|
|
5614
5841
|
}
|
|
5615
|
-
return { value: 0, type: "unknown" };
|
|
5842
|
+
if (best <= 0) return { value: Math.max(best, 0), type: best === 0 ? type : "unknown" };
|
|
5843
|
+
return { value: best, type };
|
|
5616
5844
|
}
|
|
5617
5845
|
|
|
5618
5846
|
// src/eft/parser.ts
|
|
@@ -5715,6 +5943,20 @@ function parseEft(text, dataset) {
|
|
|
5715
5943
|
return { fit, warnings };
|
|
5716
5944
|
}
|
|
5717
5945
|
function classifySection(entries, idx, _dataset) {
|
|
5946
|
+
let sawImplant = false;
|
|
5947
|
+
let allImplant = true;
|
|
5948
|
+
for (const e of entries) {
|
|
5949
|
+
const { name, quantity } = parseEntryName(e.text);
|
|
5950
|
+
const typeID = idx.map.get(name.toLowerCase());
|
|
5951
|
+
if (typeID === void 0) {
|
|
5952
|
+
allImplant = false;
|
|
5953
|
+
continue;
|
|
5954
|
+
}
|
|
5955
|
+
const t = idx.types.get(typeID);
|
|
5956
|
+
if (quantity === null && isFittableImplant(t)) sawImplant = true;
|
|
5957
|
+
else allImplant = false;
|
|
5958
|
+
}
|
|
5959
|
+
if (sawImplant && allImplant) return "implants";
|
|
5718
5960
|
for (const e of entries) {
|
|
5719
5961
|
const { name, quantity } = parseEntryName(e.text);
|
|
5720
5962
|
const typeID = idx.map.get(name.toLowerCase());
|
|
@@ -5731,6 +5973,13 @@ function classifySection(entries, idx, _dataset) {
|
|
|
5731
5973
|
}
|
|
5732
5974
|
return "cargo";
|
|
5733
5975
|
}
|
|
5976
|
+
var ATTR_IMPLANTNESS2 = 331;
|
|
5977
|
+
var ATTR_BOOSTERNESS2 = 1087;
|
|
5978
|
+
var attrValue = (t, id) => t.attributes?.find((a) => a.id === id)?.v;
|
|
5979
|
+
function isFittableImplant(t) {
|
|
5980
|
+
if (t.categoryID !== CATEGORY.IMPLANT) return false;
|
|
5981
|
+
return attrValue(t, ATTR_IMPLANTNESS2) !== void 0 || attrValue(t, ATTR_BOOSTERNESS2) !== void 0;
|
|
5982
|
+
}
|
|
5734
5983
|
function parseEntryName(text) {
|
|
5735
5984
|
const m = QUANTITY_RE.exec(text);
|
|
5736
5985
|
if (m) {
|
|
@@ -5767,6 +6016,23 @@ function consumeEntry(entry, sectionKind, fit, slotCursor, idx, dataset, warning
|
|
|
5767
6016
|
fit.drones.push(drone);
|
|
5768
6017
|
return;
|
|
5769
6018
|
}
|
|
6019
|
+
if (sectionKind === "implants" && quantity === null && isFittableImplant(type)) {
|
|
6020
|
+
const implantSlot = attrValue(type, ATTR_IMPLANTNESS2);
|
|
6021
|
+
if (implantSlot !== void 0) {
|
|
6022
|
+
fit.implants.push({ id: tempId(), typeID, slot: implantSlot });
|
|
6023
|
+
} else {
|
|
6024
|
+
fit.boosters.push({
|
|
6025
|
+
id: tempId(),
|
|
6026
|
+
typeID,
|
|
6027
|
+
slot: attrValue(type, ATTR_BOOSTERNESS2) ?? 1,
|
|
6028
|
+
// Side effects are opt-in and an EFT cannot express them, so an
|
|
6029
|
+
// imported booster arrives with its drawbacks off — the same
|
|
6030
|
+
// default pyfa gives it.
|
|
6031
|
+
activeSideEffects: []
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
return;
|
|
6035
|
+
}
|
|
5770
6036
|
if (sectionKind === "cargo" || quantity !== null) {
|
|
5771
6037
|
const cargo = { id: tempId(), typeID, count: quantity ?? 1 };
|
|
5772
6038
|
fit.cargo.push(cargo);
|
|
@@ -6132,7 +6398,7 @@ var SUBSYSTEM_RANK_BY_TYPE_ID = /* @__PURE__ */ new Map([
|
|
|
6132
6398
|
[45621, 3]
|
|
6133
6399
|
// Loki Propulsion - Wake Limiter
|
|
6134
6400
|
]);
|
|
6135
|
-
function
|
|
6401
|
+
function attrValue2(t, id) {
|
|
6136
6402
|
for (const a of t.attributes) if (a.id === id) return a.v;
|
|
6137
6403
|
return void 0;
|
|
6138
6404
|
}
|
|
@@ -6146,7 +6412,7 @@ function computeT3CVariantCode(shipTypeID, subsystems, dataset) {
|
|
|
6146
6412
|
for (const fs2 of subsystems) {
|
|
6147
6413
|
const t = subsBucket.get(fs2.typeID);
|
|
6148
6414
|
if (!t) return null;
|
|
6149
|
-
if (
|
|
6415
|
+
if (attrValue2(t, PARENT_SHIP_ATTR) !== shipTypeID) return null;
|
|
6150
6416
|
fittedByGroup.set(t.groupID, fs2.typeID);
|
|
6151
6417
|
}
|
|
6152
6418
|
let code = "";
|
|
@@ -6380,6 +6646,7 @@ async function computeFromEft(eft, options = {}) {
|
|
|
6380
6646
|
|
|
6381
6647
|
exports.ACTIVATION_EFFECT_ID = ACTIVATION_EFFECT_ID;
|
|
6382
6648
|
exports.ATTR = ATTR;
|
|
6649
|
+
exports.BOOSTER_SIDE_EFFECT_IDS = BOOSTER_SIDE_EFFECT_IDS;
|
|
6383
6650
|
exports.BUNDLED_DATA_DIR = BUNDLED_DATA_DIR;
|
|
6384
6651
|
exports.CATEGORY = CATEGORY;
|
|
6385
6652
|
exports.DAMAGE_PROFILE_PRESETS = DAMAGE_PROFILE_PRESETS;
|
|
@@ -6420,6 +6687,7 @@ exports.computeStructureMeta = computeStructureMeta;
|
|
|
6420
6687
|
exports.computeT3CVariantCode = computeT3CVariantCode;
|
|
6421
6688
|
exports.computeTank = computeTank;
|
|
6422
6689
|
exports.computeTotalEhp = computeTotalEhp;
|
|
6690
|
+
exports.defaultModeTypeIDFor = defaultModeTypeIDFor;
|
|
6423
6691
|
exports.defaultStateForModule = defaultStateForModule;
|
|
6424
6692
|
exports.disintegratorCyclesToFullSpool = disintegratorCyclesToFullSpool;
|
|
6425
6693
|
exports.disintegratorSpoolBonus = disintegratorSpoolBonus;
|