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