diff --git a/BepInEx/plugins/skwizzy.LootingBots.dll b/BepInEx/plugins/skwizzy.LootingBots.dll new file mode 100644 index 0000000..2a419a8 Binary files /dev/null and b/BepInEx/plugins/skwizzy.LootingBots.dll differ diff --git a/user/mods/Skwizzy-LootingBots-ServerMod/README.md b/user/mods/Skwizzy-LootingBots-ServerMod/README.md new file mode 100644 index 0000000..2aee9e1 --- /dev/null +++ b/user/mods/Skwizzy-LootingBots-ServerMod/README.md @@ -0,0 +1,16 @@ +## Description ## +A server side mod that serves two main purposes for LootingBots. + +1. Marks all items with DiscardLimits as InsuranceDisabled. It then disables the DiscardLimit settings for the server via the EnableDiscardLimits option in Server/database/globals.json. SPT PMC bots by default spawn with loot already in their backpacks, this loot is not marked Found In Raid and thus is subject to BSG's RMT protection logic. With discard limits enabled, when a bot drops their backback to swap to a new one any loot with discard limits in their bag will be deleted immediately when the bag is dropped. To avoid this we set the EnableDiscardLimits to false, and also make sure to flag all items with a DiscardLimit >= 0 as InsuranceDisabled to prevent items suchs as keys and cases to be insured. + +2. Provide the option to clear out the loot that PMC/Scav bots start with in their backpacks. This does not include meds, ammo, grenades ect. These options can be found in the `LootingBots-ServerMod/config/config.json`. + - `pmcSpawnWithLoot` - When set to `true`, PMCs will spawn with loot in their bags/pockets (default SPT behavior) + - `scavSpawnWithLoot` - When set to `true`, Scavs will spawn with loot in the bags/pockets (default SPT behavior) + + Default config: + ``` + { + "pmcSpawnWithLoot": false, + "scavSpawnWithLoot": true + } + ``` \ No newline at end of file diff --git a/user/mods/Skwizzy-LootingBots-ServerMod/config/config.json b/user/mods/Skwizzy-LootingBots-ServerMod/config/config.json new file mode 100644 index 0000000..7080069 --- /dev/null +++ b/user/mods/Skwizzy-LootingBots-ServerMod/config/config.json @@ -0,0 +1,4 @@ +{ + "pmcSpawnWithLoot": true, + "scavSpawnWithLoot": true +} \ No newline at end of file diff --git a/user/mods/Skwizzy-LootingBots-ServerMod/package.json b/user/mods/Skwizzy-LootingBots-ServerMod/package.json new file mode 100644 index 0000000..1868737 --- /dev/null +++ b/user/mods/Skwizzy-LootingBots-ServerMod/package.json @@ -0,0 +1,23 @@ +{ + "name": "LootingBots-ServerMod", + "version": "1.5.2", + "main": "src/mod.js", + "license": "MIT", + "author": "Skwizzy", + "sptVersion": "~3.11", + "scripts": { + "setup": "npm i", + "build": "node ./packageBuild.ts" + }, + "devDependencies": { + "@types/node": "^16.18.18", + "@typescript-eslint/eslint-plugin": "5.46.1", + "@typescript-eslint/parser": "5.46.1", + "bestzip": "2.2.1", + "eslint": "8.30.0", + "fs-extra": "11.1.0", + "glob": "8.0.3", + "tsyringe": "4.7.0", + "typescript": "4.9.4" + } +} \ No newline at end of file diff --git a/user/mods/Skwizzy-LootingBots-ServerMod/src/enums.ts b/user/mods/Skwizzy-LootingBots-ServerMod/src/enums.ts new file mode 100644 index 0000000..1670f4e --- /dev/null +++ b/user/mods/Skwizzy-LootingBots-ServerMod/src/enums.ts @@ -0,0 +1,122 @@ +export const enum ParentClasses { + WEAPON = "5422acb9af1c889c16000029", + ARMORVEST = "5448e54d4bdc2dcc718b4568", + ARMOREDEQUIPMENT = "57bef4c42459772e8d35a53b", + HEADWEAR = "5a341c4086f77401f2541505", + FACECOVER = "5a341c4686f77469e155819e", + CHESTRIG = "5448e5284bdc2dcb718b4567", + BACKPACK = "5448e53e4bdc2d60728b4567", + VISORS = "5448e5724bdc2ddf718b4568", + FOOD = "5448e8d04bdc2ddf718b4569", + DRINK = "5448e8d64bdc2dce718b4568", + BARTER_ITEM = "5448eb774bdc2d0a728b4567", + INFO = "5448ecbe4bdc2d60728b4568", + MEDKIT = "5448f39d4bdc2d0a728b4568", + DRUGS = "5448f3a14bdc2d27728b4569", + STIMULATOR = "5448f3a64bdc2d60728b456a", + MEDICAL = "5448f3ac4bdc2dce718b4569", + MEDICAL_SUPPLIES = "57864c8c245977548867e7f1", + MOD = "5448fe124bdc2da5018b4567", + FUNCTIONAL_MOD = "550aa4154bdc2dd8348b456b", + FUEL = "5d650c3e815116009f6201d2", + GEAR_MOD = "55802f3e4bdc2de7118b4584", + STOCK = "55818a594bdc2db9688b456a", + FOREGRIP = "55818af64bdc2d5b648b4570", + MASTER_MOD = "55802f4a4bdc2ddb688b4569", + MOUNT = "55818b224bdc2dde698b456f", + MUZZLE = "5448fe394bdc2d0d028b456c", + SIGHTS = "5448fe7a4bdc2d6f028b456b", + MEDS = "543be5664bdc2dd4348b4569", + MONEY = "543be5dd4bdc2deb348b4569", + NIGHTVISION = "5a2c3a9486f774688b05e574", + THEMALVISION = "5d21f59b6dbe99052b54ef83", + KEY = "543be5e94bdc2df1348b4568", + KEY_MECHANICAL = "5c99f98d86f7745c314214b3", + KEYCARD = "5c164d2286f774194c5e69fa", + EQUIPMENT = "543be5f84bdc2dd4348b456a", + THROW_WEAPON = "543be6564bdc2df4348b4568", + FOOD_DRINK = "543be6674bdc2df1348b4569", + PISTOL = "5447b5cf4bdc2d65278b4567", + SMG = "5447b5e04bdc2d62278b4567", + ASSAULT_RIFLE = "5447b5f14bdc2d61278b4567", + ASSAULT_CARBINE = "5447b5fc4bdc2d87278b4567", + SHOTGUN = "5447b6094bdc2dc3278b4567", + MARKSMAN_RIFLE = "5447b6194bdc2d67278b4567", + SNIPER_RIFLE = "5447b6254bdc2dc3278b4568", + MACHINE_GUN = "5447bed64bdc2d97278b4568", + GRENADE_LAUNCHER = "5447bedf4bdc2d87278b4568", + SPECIAL_WEAPON = "5447bee84bdc2dc3278b4569", + SPEC_ITEM = "5447e0e74bdc2d3c308b4567", + KNIFE = "5447e1d04bdc2dff2f8b4567", + AMMO = "5485a8684bdc2da71d8b4567", + AMMO_BOX = "543be5cb4bdc2deb348b4568", + LOOT_CONTAINER = "566965d44bdc2d814c8b4571", + MOD_CONTAINER = "5448bf274bdc2dfc2f8b456a", + SEARCHABLE_ITEM = "566168634bdc2d144c8b456c", + STASH = "566abbb64bdc2d144c8b457d", + SORTING_TABLE = "6050cac987d3f925bf016837", + LOCKABLE_CONTAINER = "5671435f4bdc2d96058b4569", + SIMPLE_CONTAINER = "5795f317245977243854e041", + INVENTORY = "55d720f24bdc2d88028b456d", + STATIONARY_CONTAINER = "567583764bdc2d98058b456e", + POCKETS = "557596e64bdc2dc2118b4571", + ARMBAND = "5b3f15d486f77432d0509248", + DOG_TAG_USEC = "59f32c3b86f77472a31742f0", + DOG_TAG_BEAR = "59f32bb586f774757e1e8442", + JEWELRY = "57864a3d24597754843f8721", + ELECTRONICS = "57864a66245977548f04a81f", + BUILDING_MATERIAL = "57864ada245977548638de91", + TOOL = "57864bb7245977548b3b66c2", + HOUSEHOLD_GOODS = "57864c322459775490116fbf", + LUBRICANT = "57864e4c24597754843f8723", + BATTERY = "57864ee62459775490116fc1", + ASSAULT_SCOPE = "55818add4bdc2d5b648b456f", + TACTICAL_COMBO = "55818b164bdc2ddc698b456c", + FLASHLIGHT = "55818b084bdc2d5b648b4571", + MAGAZINE = "5448bc234bdc2d3c308b4569", + LIGHT_LASER = "55818b0e4bdc2dde698b456e", + FLASH_HIDER = "550aa4bf4bdc2dd6348b456b", + COLLIMATOR = "55818ad54bdc2ddc698b4569", + COMPACT_COLLIMATOR = "55818acf4bdc2dde698b456b", + COMPENSATOR = "550aa4af4bdc2dd4348b456e", + OPTIC_SCOPE = "55818ae44bdc2dde698b456c", + SPECIAL_SCOPE = "55818aeb4bdc2ddc698b456a", + OTHER = "590c745b86f7743cc433c5f2", + SILENCER = "550aa4cd4bdc2dd8348b456c", + PORTABLE_RANGE_FINDER = "61605ddea09d851a0a0c1bbc", + ITEM = "54009119af1c881c07000029", + CYLINDER_MAGAZINE = "610720f290b75a49ff2e5e25", + MAP = "567849dd4bdc2d150f8b456e", + REPAIRKITS = "616eb7aea207f41933308f46", + COMPASS = "5f4fbaaca5573a5ac31db429", + HEADSET = "5645bcb74bdc2ded0b8b4578", + GASBLOCK = "56ea9461d2720b67698b456f" +} + +export const enum Calibers { + _9x19mm = "Caliber9x19PARA", + _9x18mm = "Caliber9x18PM", + _9x21mm = "Caliber9x21", + _9x39mm = "Caliber9x39", + _45ACP = "Caliber1143x23ACP", + _46x30mm = "Caliber46x30", + _57x28mm = "Caliber57x28", + _762x25mm = "Caliber762x25TT", + _366TKM = "Caliber366TKM", + _762x39mm = "Caliber762x39", + _762x51mm = "Caliber762x51", + _762x54rmm = "Caliber762x54R", + _300BLK = "Caliber762x35", + _556x45mm = "Caliber556x45NATO", + _545x39mm = "Caliber545x39", + _127x55mm = "Caliber127x55", + _338mag = "Caliber86x70", + _357mag = "Caliber9x33R", + _127x108mm = "Caliber127x108", + _40x46mm = "Caliber40x46", + _40x53mm = "Caliber40mmRU", + _26x75mm = "Caliber26x75", + _12ga = "Caliber12g", + _20ga = "Caliber20g", + _23x75mm = "Caliber23x75" +} diff --git a/user/mods/Skwizzy-LootingBots-ServerMod/src/mod.ts b/user/mods/Skwizzy-LootingBots-ServerMod/src/mod.ts new file mode 100644 index 0000000..f1e25e1 --- /dev/null +++ b/user/mods/Skwizzy-LootingBots-ServerMod/src/mod.ts @@ -0,0 +1,95 @@ +import { DependencyContainer } from "tsyringe"; + +import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod"; +import { DatabaseServer } from "@spt/servers/DatabaseServer"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import { ConfigServer } from "@spt/servers/ConfigServer"; +import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; +import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig"; +import { IBotConfig } from "@spt/models/spt/config/IBotConfig"; + + +import config from "../config/config.json"; + +class DisableDiscardLimits implements IPostDBLoadMod { + public postDBLoad(container: DependencyContainer): void { + const databaseServer = container.resolve("DatabaseServer"); + const configServer = container.resolve("ConfigServer"); + const pmcConfig = configServer.getConfig(ConfigTypes.PMC); + const botConfig = configServer.getConfig(ConfigTypes.BOT); + + const { logInfo } = useLogger(container); + + const tables = databaseServer.getTables(); + + /** + * Set the item generation weights for backpackLoot, vestLoot, and pocketLoot to zero to prevent extra loot items from spawning on the specified bot type + * @param botTypes + */ + const emptyInventory = (botTypes: string[]) => { + botTypes.forEach((type) => { + logInfo(`Removing loot from ${type}`); + const backpackWeights = tables.bots.types[type].generation.items.backpackLoot.weights; + const vestWeights = tables.bots.types[type].generation.items.vestLoot.weights; + const pocketWeights = tables.bots.types[type].generation.items.pocketLoot.weights; + + clearWeights(backpackWeights); + clearWeights(vestWeights); + clearWeights(pocketWeights); + }); + }; + + if (!config.pmcSpawnWithLoot) { + emptyInventory(["usec", "bear"]); + // Do not allow weapons to spawn in PMC bags + pmcConfig.looseWeaponInBackpackLootMinMax.max = 0; + + // Clear weights in pmc randomisation + botConfig.equipment?.pmc?.randomisation?.forEach(details => { + const generation = details?.generation; + clearWeights(generation?.backpackLoot?.weights); + clearWeights(generation?.pocketLoot?.weights); + clearWeights(generation?.vestLoot?.weights); + }); + } + + if (!config.scavSpawnWithLoot) { + emptyInventory(["assault"]); + } + + logInfo("Marking items with DiscardLimits as InsuranceDisabled"); + for (let itemId in tables.templates.items) { + const template = tables.templates.items[itemId]; + /** + * When we set DiscardLimitsEnabled to false further down, this will cause some items to be able to be insured when they normally should not be. + * The DiscardLimit property is used by BSG for RMT protections and their code internally treats things with discard limits as not insurable. + * For items that have a DiscardLimit >= 0, we need to manually flag them as InsuranceDisabled to make sure they still cannot be insured by the player. + * Do not disable insurance if the item is marked as always available for insurance. + */ + if ( + template._props.DiscardLimit >= 0 && + !template._props.IsAlwaysAvailableForInsurance + ) { + template._props.InsuranceDisabled = true; + } + } + + tables.globals.config.DiscardLimitsEnabled = false; + logInfo("Global config DiscardLimitsEnabled set to false"); + } +} + +function clearWeights(weights: Record = {}) { + Object.keys(weights).forEach(weight => weights[weight] = 0); +} + +function useLogger(container: DependencyContainer) { + const logger = container.resolve("WinstonLogger"); + return { + logInfo: (message: string) => { + logger.info(`[LootingBots-ServerMod] ${message}`); + }, + }; +} + +module.exports = { mod: new DisableDiscardLimits() };