spt_modpack/user/mods/AlgorithmicLevelProgression/src/LoadoutChanges/OnGameStartUtils.ts
2025-07-18 15:52:20 -05:00

373 lines
12 KiB
TypeScript

import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
import {
EquipmentFilters,
IRandomisationDetails,
IWeightingAdjustmentDetails,
} from "@spt/models/spt/config/IBotConfig";
import advancedConfig from "../../config/advancedConfig.json";
import { IBotType, IMods } from "@spt/models/eft/common/tables/IBotType";
import {
SightType,
checkParentRecursive,
cloneDeep,
mountParent,
muzzleParent,
sightParent,
weaponParent,
weaponTypeNameToId,
} from "./utils";
import InternalBlacklist from "./InternalBlacklist";
import { globalValues } from "./GlobalValues";
export const makeRandomisationAdjustments = (
isNight: boolean,
originalWeight: EquipmentFilters,
randomisation: IRandomisationDetails[],
location: keyof typeof advancedConfig.locations
) => {
const noNvgNeeded = ["factory4_day", "factory4_night", "laboratory"].includes(
location
);
// levelRange: MinMax;
// generation?: Record<string, GenerationData>;
// /** Mod slots that should be fully randomised -ignores mods from bottype.json and instaed creates a pool using items.json */
// randomisedWeaponModSlots?: string[];
// /** Armor slots that should be randomised e.g. 'Headwear, Armband' */
// randomisedArmorSlots?: string[];
// /** Equipment chances */
// equipment?: Record<string, number>;
// /** Weapon mod chances */
// weaponMods?: Record<string, number>;
// /** Equipment mod chances */
// equipmentMods?: Record<string, number>;
originalWeight.randomisation.forEach((_, index) => {
// NVG's
if (
!noNvgNeeded &&
randomisation?.[index]?.equipmentMods?.mod_nvg !== undefined
) {
randomisation[index].equipmentMods.mod_nvg = isNight
? (index + 1) * 18
: 0;
if (randomisation[index].equipmentMods.mod_nvg > 100)
randomisation[index].equipmentMods.mod_nvg = 100;
}
// Silencers??
if (randomisation?.[index]?.weaponMods?.mod_muzzle !== undefined) {
randomisation[index].weaponMods.mod_muzzle += isNight ? 18 : 0;
if (randomisation[index].weaponMods.mod_muzzle > 100)
randomisation[index].weaponMods.mod_muzzle = 100;
}
// Flashlights
if (
location === "laboratory" ||
randomisation?.[index]?.weaponMods?.mod_flashlight !== undefined
) {
randomisation[index].weaponMods.mod_flashlight += isNight ? 45 : 0;
if (randomisation[index].weaponMods.mod_flashlight > 100)
randomisation[index].weaponMods.mod_flashlight = 100;
}
if (location === "laboratory") {
[
"mod_equipment",
"mod_equipment_000",
"mod_equipment_001",
"mod_equipment_002", //TODO: check if this is still needed
"mod_pistol_grip_akms",
"mod_tactical",
"mod_tactical_2",
"mod_tactical001",
"mod_tactical002",
"mod_tactical_000",
"mod_tactical_001",
"mod_tactical_002",
"mod_tactical_003",
].forEach((modName) => {
if (randomisation?.[index]?.weaponMods?.[modName] !== undefined) {
randomisation[index].weaponMods[modName] += 30;
if (randomisation[index].weaponMods[modName] > 100)
randomisation[index].weaponMods[modName] = 100;
}
});
}
});
};
export const makeMapSpecificWeaponWeightings = (
location: keyof typeof advancedConfig.locations,
items: Record<string, ITemplateItem>,
originalWeight: EquipmentFilters,
pmcWeighting: IWeightingAdjustmentDetails[]
) => {
const firstPrimaryWeaponTypes =
advancedConfig.locations[location].weightingAdjustments.FirstPrimaryWeapon;
originalWeight.weightingAdjustmentsByBotLevel.forEach((weightTier, index) => {
const firstPrimary = weightTier.equipment.edit.FirstPrimaryWeapon;
const firstPrimaryKeys = Object.keys(firstPrimary);
firstPrimaryKeys?.forEach((weaponId) => {
const parentId = items[weaponId]?._parent;
const parent = items?.[parentId]?._name;
if (parent && firstPrimaryWeaponTypes[parent]) {
const multiplier = firstPrimaryWeaponTypes[parent];
pmcWeighting[index].equipment.edit.FirstPrimaryWeapon[weaponId] =
Math.round(multiplier * firstPrimary[weaponId]);
// console.log(firstPrimary[weaponId], " to ", pmcWeighting[index].equipment.edit.FirstPrimaryWeapon[weaponId], parent, items[weaponId]._name)
} else {
console.log(
`Algorthimic LevelProgression: Unable to set map settings for ${items[weaponId]._name} - ${weaponId} `
);
}
});
});
};
export const cullModItems = (
mods: IMods,
isNight: boolean,
items: Record<string, ITemplateItem>,
location: keyof typeof advancedConfig.locations
) => {
const cullList: Set<string> = new Set([
...(isNight ? nightTimeCullList : dayTimeCullList),
...InternalBlacklist,
]);
if (location === "laboratory") {
cullList.delete("5a1ead28fcdbcb001912fa9f");
cullList.delete("5c11046cd174af02a012e42b");
cullList.delete("5a1eaa87fcdbcb001865f75e");
cullList.delete("5d1b5e94d7ad1a2b865a96b0");
cullList.delete("5ea058e01dbce517f324b3e2");
}
for (let key in mods) {
if (
cullList.has(key) ||
!checkDaytimeSilencer(key, isNight, items, cullList)
) {
delete mods[key];
} else {
for (const modType in mods[key]) {
if (mods?.[key]?.[modType].length) {
mods[key][modType] = mods[key][modType].filter(
(id) =>
!cullList.has(id) &&
checkDaytimeSilencer(id, isNight, items, cullList)
);
if (
mods[key][modType].length === 0 &&
Object.keys(mods[key]).length === 1
) {
delete mods[key];
}
}
}
}
}
};
const checkDaytimeSilencer = (
id: string,
isNight: boolean,
items: Record<string, ITemplateItem>,
cullList: Set<string>
) => {
const item = items[id];
if (!item?._props) return false;
switch (true) {
case !isNight &&
checkParentRecursive(id, items, [muzzleParent]) &&
item._props.Loudness < globalValues.advancedConfig.daytimeSilencerCutoff:
// console.log(item._name);
cullList.add(id);
return false;
default:
break;
}
return true;
};
const nightTimeCullList = [
"5cc9c20cd7f00c001336c65d", // tactical_all_ncstar_tactical_blue_laser
"560d657b4bdc2da74d8b4572", // tactical_all_zenit_2p_kleh_vis_laser
];
const dayTimeCullList = [
"5b3b6dc75acfc47a8773fb1e",
"644a3df63b0b6f03e101e065", // tactical_all_bemeyers_mawl_c1_plus
"5b3a337e5acfc4704b4a19a0", // tactical_all_zenit_2u_kleh
"626becf9582c3e319310b837", // tactical_all_insight_wmx200
"57fd23e32459772d0805bcf1", // tactical_all_holosun_ls321
"544909bb4bdc2d6f028b4577", // tactical_all_insight_anpeq15
];
const smgUpperRails = new Set([
"5926dad986f7741f82604363",
"5a966ec8a2750c00171b3f36",
"602e63fb6335467b0c5ac94d",
"5894a5b586f77426d2590767",
"5de8e67c4a9f347bc92edbd7",
]);
const marksmanUpperRails = new Set([
"5df8e4080b92095fd441e594",
"5dfcd0e547101c39625f66f9",
]);
export const updateScopes = (
mods: IMods,
isNight: boolean,
items: Record<string, ITemplateItem>,
location: keyof typeof advancedConfig.locations
) => {
const weaponTypeMapper = buildOutWeaponTypeMapper(location, isNight);
for (let key in mods) {
if (
smgUpperRails.has(key) ||
marksmanUpperRails.has(key) ||
checkParentRecursive(key, items, [weaponParent])
) {
const parent = items[key]._parent;
let scopeTypes = weaponTypeMapper[parent];
if (smgUpperRails.has(key)) {
scopeTypes = weaponTypeMapper[weaponTypeNameToId.Smg];
}
if (marksmanUpperRails.has(key)) {
scopeTypes = weaponTypeMapper[weaponTypeNameToId.MarksmanRifle];
}
if (!scopeTypes) {
// console.log("UNABLE TO FIND PARENT FOR", key, items[key]._name)
break;
}
if (!!mods[key]?.mod_scope?.length) {
const result = mods[key].mod_scope.filter(
(id) =>
scopeTypes.has(items[id]?._parent) ||
checkIfChildHasScopes(id, items, scopeTypes, mods)
);
if (result.length) mods[key].mod_scope = result;
}
if (!!mods[key]?.mod_mount) {
const mountResult = mods[key].mod_mount.filter(
(id) =>
scopeTypes.has(items[id]?._parent) ||
checkIfChildHasScopes(id, items, scopeTypes, mods, true)
);
// console.log(key, items[key]._name, mods[key].mod_mount.length, mountResult.length)
if (mountResult.length) mods[key].mod_mount = mountResult;
mods[key]?.mod_mount;
}
[
"mod_mount_001",
"mod_mount_002",
"mod_mount_003",
"mod_mount_004",
].forEach((mountType) => {
if (!!mods[key]?.[mountType]) {
const mountResult = mods[key][mountType].filter(
(id) =>
!checkParentRecursive(id, items, [mountParent, sightParent]) ||
(items[id]?._parent === mountParent && !mods[id]?.mod_scope) ||
scopeTypes.has(items[id]?._parent) ||
checkIfChildHasScopes(id, items, scopeTypes, mods, true)
);
// console.log(mountType, key, items[key]._name, mods[key][mountType].length, mountResult.length)
if (mountResult.length) mods[key][mountType] = mountResult;
mods[key]?.[mountType];
}
});
if (!!mods[key]?.mod_reciever) {
const receiverScopetypes = checkAssaultScopeTypes(
items,
key,
scopeTypes,
weaponTypeMapper
);
const receiverResult = mods[key].mod_reciever.filter(
(id) =>
scopeTypes.has(items[id]?._parent) ||
checkIfChildHasScopes(id, items, receiverScopetypes, mods, true)
);
// console.log(key, items[key]._name, mods[key].mod_reciever.length, receiverResult.length)
if (receiverResult?.length) mods[key].mod_reciever = receiverResult;
mods[key]?.mod_reciever;
}
}
}
};
const akType = "reciever_ak";
const checkAssaultScopeTypes = (
items: Record<string, ITemplateItem>,
id: string,
originalScopeType: Set<string>,
weaponTypeMapper: Record<string, Set<string>>
) => {
if (items[id]?._name?.includes(akType))
return weaponTypeMapper["5447b5f14bdc2d61278b4567"]; //assault rifle type
return originalScopeType;
};
const checkIfChildHasScopes = (
id: string,
items: Record<string, ITemplateItem>,
scopeTypes: Set<string>,
mods: IMods,
clean?: boolean
) => {
const result = !!mods[id]?.mod_scope?.find((scopeId) =>
scopeTypes.has(items[scopeId]?._parent)
);
if (result && clean) {
const filtered = mods[id]?.mod_scope.filter(
(id) =>
scopeTypes.has(items[id]?._parent) ||
checkIfChildHasScopes(id, items, scopeTypes, mods)
);
if (filtered?.length) mods[id].mod_scope = filtered;
}
return result;
};
const buildOutWeaponTypeMapper = (
location: keyof typeof advancedConfig.locations,
isNight: boolean
) => {
const mapper: Record<string, Set<string>> = {};
const sightConfiguration = cloneDeep(
advancedConfig.locations[location].sightConfiguration
);
if (isNight) {
["SniperRifle", "MarksmanRifle", "AssaultCarbine", "AssaultRifle"].forEach(
(type) => {
sightConfiguration[type].push("NightVision");
}
);
}
for (const weaponType in sightConfiguration) {
const weaponTypeUUID = weaponTypeNameToId[weaponType];
mapper[weaponTypeUUID] = new Set(
sightConfiguration[weaponType].map((name) => SightType[name])
);
}
return mapper;
};
// check if item is scope, if so ignore (allow for child scopes)
// check if item is weapon, if so, filter mod_scope
// if scope, check
// if "55818b224bdc2dde698b456f" Mount, check if any mod_scope within contain correct scopes, if not remove