This commit is contained in:
GetParanoid 2025-07-18 16:05:06 -05:00
parent 25820f46bc
commit 02e1c5c0b9
91 changed files with 8393 additions and 2 deletions

View file

@ -39,7 +39,7 @@
"excluded_bosses": [ "sptBear", "sptUsec" ]
},
"only_make_changes_just_after_spawning": {
"enabled": false,
"enabled": true,
"time_limit": 5,
"affected_systems" : {
"loot_destruction": true,

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,116 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommonUtils = void 0;
const config_json_1 = __importDefault(require("../config/config.json"));
class CommonUtils {
logger;
databaseTables;
localeService;
static fenceID = "579dc571d53a0658a154fbec";
debugMessagePrefix = "[Late to the Party] ";
translations;
constructor(logger, databaseTables, localeService) {
this.logger = logger;
this.databaseTables = databaseTables;
this.localeService = localeService;
// Get all translations for the current locale
this.translations = this.localeService.getLocaleDb();
}
logInfo(message, alwaysShow = false) {
if (config_json_1.default.debug.enabled || alwaysShow)
this.logger.info(this.debugMessagePrefix + message);
}
logWarning(message) {
this.logger.warning(this.debugMessagePrefix + message);
}
logError(message) {
this.logger.error(this.debugMessagePrefix + message);
}
getItemName(itemID) {
const translationKey = `${itemID} Name`;
if (translationKey in this.translations)
return this.translations[translationKey];
// If a key can't be found in the translations dictionary, fall back to the template data if possible
if (!(itemID in this.databaseTables.templates.items)) {
return undefined;
}
const item = this.databaseTables.templates.items[itemID];
return item._name;
}
getMaxItemPrice(itemID) {
// Get the handbook.json price, if any exists
const matchingHandbookItems = this.databaseTables.templates.handbook.Items.filter((i) => i.Id === itemID);
let handbookPrice = 0;
if (matchingHandbookItems.length === 1) {
handbookPrice = matchingHandbookItems[0].Price;
// Some mods add a record with a junk value
if ((handbookPrice == null) || Number.isNaN(handbookPrice)) {
this.logWarning(`Invalid handbook price (${handbookPrice}) for ${this.getItemName(itemID)} (${itemID}). Defaulting to 0.`);
handbookPrice = 0;
}
}
// Get the prices.json price, if any exists
let price = 0;
if (itemID in this.databaseTables.templates.prices) {
price = this.databaseTables.templates.prices[itemID];
// Some mods add a record with a junk value
if ((price == null) || Number.isNaN(price)) {
// Only show a warning if the method will return 0
if (handbookPrice === 0) {
this.logWarning(`Invalid price (${price}) for ${this.getItemName(itemID)} (${itemID}). Defaulting to 0.`);
}
price = 0;
}
}
return Math.max(handbookPrice, price);
}
/**
* Check if @param item is a child of the item with ID @param parentID
*/
static hasParent(item, parentID, databaseTables) {
const allParents = CommonUtils.getAllParents(item, databaseTables);
return allParents.includes(parentID);
}
static getAllParents(item, databaseTables) {
if ((item._parent === null) || (item._parent === undefined) || (item._parent === ""))
return [];
const allParents = CommonUtils.getAllParents(databaseTables.templates.items[item._parent], databaseTables);
allParents.push(item._parent);
return allParents;
}
static canItemDegrade(item, databaseTables) {
if (item.upd === undefined) {
return false;
}
if ((item.upd.MedKit === undefined) && (item.upd.Repairable === undefined) && (item.upd.Resource === undefined)) {
return false;
}
const itemTpl = databaseTables.templates.items[item._tpl];
if ((itemTpl._props.armorClass !== undefined) && (itemTpl._props.armorClass.toString() === "0")) {
return false;
}
return true;
}
static interpolateForFirstCol(array, value) {
if (array.length === 1) {
return array[array.length - 1][1];
}
if (value <= array[0][0]) {
return array[0][1];
}
for (let i = 1; i < array.length; i++) {
if (array[i][0] >= value) {
if (array[i][0] - array[i - 1][0] === 0) {
return array[i][1];
}
return array[i - 1][1] + (value - array[i - 1][0]) * (array[i][1] - array[i - 1][1]) / (array[i][0] - array[i - 1][0]);
}
}
return array[array.length - 1][1];
}
}
exports.CommonUtils = CommonUtils;
//# sourceMappingURL=CommonUtils.js.map

View file

@ -0,0 +1,10 @@
{
"version": 3,
"file": "CommonUtils.js",
"sourceRoot": "",
"sources": [
"CommonUtils.ts"
],
"names": [],
"mappings": "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"
}

View file

@ -0,0 +1,422 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LootRankingGenerator = void 0;
const config_json_1 = __importDefault(require("../config/config.json"));
const CommonUtils_1 = require("./CommonUtils");
const verboseLogging = false;
const lootFilePath = `${__dirname}/../db/lootRanking.json`;
class LootRankingGenerator {
commonUtils;
databaseTables;
fileSystem;
botWeaponGenerator;
hashUtil;
constructor(commonUtils, databaseTables, fileSystem, botWeaponGenerator, hashUtil) {
this.commonUtils = commonUtils;
this.databaseTables = databaseTables;
this.fileSystem = fileSystem;
this.botWeaponGenerator = botWeaponGenerator;
this.hashUtil = hashUtil;
}
getLootRankingDataFromFile() {
if (!this.fileSystem.exists(lootFilePath)) {
this.commonUtils.logWarning("Loot ranking data not found. Creating empty loot ranking file...");
// Generate empty file
const rankingData = {
costPerSlot: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.cost_per_slot,
weight: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.weight,
size: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.size,
gridSize: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.gridSize,
maxDim: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.max_dim,
armorClass: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.armor_class,
parentWeighting: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.parents,
items: {}
};
const rankingDataStr = JSON.stringify(rankingData);
this.fileSystem.write(lootFilePath, rankingDataStr);
}
const rankingDataStr = this.fileSystem.read(lootFilePath);
return JSON.parse(rankingDataStr);
}
generateLootRankingData(sessionId) {
if (!config_json_1.default.destroy_loot_during_raid.loot_ranking.enabled) {
this.commonUtils.logInfo("Loot ranking is disabled in config.json.");
return;
}
if (this.validLootRankingDataExists()) {
this.commonUtils.logInfo("Using existing loot ranking data.");
return;
}
this.commonUtils.logInfo("Creating loot ranking data...", true);
// Create ranking data for each item found in the server database
const items = {};
for (const itemID in this.databaseTables.templates.items) {
if (this.databaseTables.templates.items[itemID]._type === "Node") {
continue;
}
if (this.databaseTables.templates.items[itemID]._props.QuestItem) {
continue;
}
items[this.databaseTables.templates.items[itemID]._id] = this.generateLookRankingForItem(this.databaseTables.templates.items[itemID], sessionId);
}
// Generate the file contents
const rankingData = {
costPerSlot: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.cost_per_slot,
weight: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.weight,
size: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.size,
gridSize: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.gridSize,
maxDim: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.max_dim,
armorClass: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.armor_class,
parentWeighting: config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.parents,
items: items
};
const rankingDataStr = JSON.stringify(rankingData);
this.fileSystem.write(lootFilePath, rankingDataStr);
this.commonUtils.logInfo("Creating loot ranking data...done.", true);
}
generateLookRankingForItem(item, sessionId) {
// Get required item properties from the server database
const cost = this.commonUtils.getMaxItemPrice(item._id);
let weight = item._props.Weight;
let size = item._props.Width * item._props.Height;
let maxDim = Math.max(item._props.Width, item._props.Height);
// If the item is a weapon, find a suitable assembled version of it
if (item._props.weapClass !== undefined) {
// First try to find the most desirable weapon from the traders
let bestWeaponMatch = this.findBestWeaponMatchfromTraders(item);
// If the weapon isn't offered by any traders, find the most desirable version in the presets
if (bestWeaponMatch.length === 0) {
if (verboseLogging)
this.commonUtils.logInfo(`Could not find ${this.commonUtils.getItemName(item._id)} in trader assorts.`);
bestWeaponMatch = this.findBestWeaponInPresets(item);
}
// Ensure a weapon has been generated
if (bestWeaponMatch.length === 0) {
this.commonUtils.logError(`Could not generate a weapon for ${this.commonUtils.getItemName(item._id)}`);
}
else {
const [weaponWidth, weaponHeight, weaponWeight] = this.getWeaponProperties(item, bestWeaponMatch);
if (verboseLogging)
this.commonUtils.logInfo(`Found weapon ${this.commonUtils.getItemName(item._id)}: Width=${weaponWidth},Height=${weaponHeight},Weight=${weaponWeight}`);
weight = weaponWeight;
size = weaponWidth * weaponHeight;
maxDim = Math.max(weaponWidth, weaponHeight);
}
}
// Check if the item has a grid in which other items can be placed (i.e. a backpack)
let gridSize = 0;
if (item._props.Grids !== undefined) {
for (const grid in item._props.Grids) {
gridSize += item._props.Grids[grid]._props.cellsH * item._props.Grids[grid]._props.cellsV;
}
}
// Get the armor class for the item if applicable
let armorClass = 0;
if (item._props.armorClass !== undefined) {
armorClass = Number(item._props.armorClass);
}
// Calculate the cost per slot
// If the item can be equipped (backpacks, weapons, etc.), use the inventory slot size (1) instead of the item's total size
let costPerSlot = cost;
if (!this.canEquipItem(item)) {
costPerSlot /= size;
}
// Generate the loot-ranking value based on the item properties and weighting in config.json
let value = costPerSlot * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.cost_per_slot;
value += weight * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.weight;
value += size * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.size;
value += gridSize * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.gridSize;
value += maxDim * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.max_dim;
value += armorClass * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.armor_class;
// Determine how much additional weighting to apply if the item is a parent of any defined in config.json
let parentWeighting = 0;
for (const parentID in config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.parents) {
if (CommonUtils_1.CommonUtils.hasParent(item, parentID, this.databaseTables)) {
parentWeighting += config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.parents[parentID].value;
}
}
value += parentWeighting;
// Create the object to store in lootRanking.json
const data = {
id: item._id,
name: this.commonUtils.getItemName(item._id),
value: value,
costPerSlot: costPerSlot,
weight: weight,
size: size,
gridSize: gridSize,
maxDim: maxDim,
armorClass: armorClass,
parentWeighting: parentWeighting
};
return data;
}
findBestWeaponInPresets(item) {
let weapon = [];
for (const presetID in this.databaseTables.globals.ItemPresets) {
const preset = this.databaseTables.globals.ItemPresets[presetID];
if (preset._items[0]._tpl === item._id) {
// Store the initial weapon selection
if (weapon.length === 0) {
weapon = preset._items;
continue;
}
// Determine if the weapon is better than the previous one found
if (this.weaponBaseValue(item, preset._items) > this.weaponBaseValue(item, weapon)) {
weapon = preset._items;
}
}
}
// If there are no presets for the weapon, create one
if (weapon.length === 0) {
return this.generateWeaponPreset(item)._items;
}
return weapon;
}
generateWeaponPreset(item) {
const baseWeapon = {
_id: this.hashUtil.generate(),
_tpl: item._id
};
const weapon = this.fillItemSlots(baseWeapon, []);
if (verboseLogging)
this.commonUtils.logInfo(`Creating preset for ${this.commonUtils.getItemName(item._id)}...`);
for (const weaponPart in weapon) {
if (verboseLogging)
this.commonUtils.logInfo(`Creating preset for ${this.commonUtils.getItemName(item._id)}...found ${this.commonUtils.getItemName(weapon[weaponPart]._tpl)}`);
}
const preset = {
_id: this.hashUtil.generate(),
_type: "Preset",
_changeWeaponName: false,
_name: `${item._name}_autoGen`,
_parent: weapon[0]._id,
_items: weapon
};
return preset;
}
/**
* Iterate through all possible slots in the object and add an item for all that are required
* @param item the base item containing slots
* @returns an array of Item objects containing the base item and all required attachments generated for it
*/
fillItemSlots(item, initialBannedParts) {
if (item._tpl === undefined) {
this.commonUtils.logError("Found an item with an undefined template ID. Cannot fill item slots.");
return [];
}
const itemTemplate = this.databaseTables.templates.items[item._tpl];
if (itemTemplate === undefined) {
this.commonUtils.logError(`Could not find item with template ${item._tpl}. Cannot fill item slots.`);
return [];
}
let isValid = false;
let filledItem;
const bannedParts = [].concat(initialBannedParts);
while (!isValid) {
// Create the initial candidate for the array that will be returned
filledItem = [];
filledItem.push(item);
for (const slot in itemTemplate._props.Slots) {
if ((itemTemplate._props.Slots[slot]._required !== undefined) && !itemTemplate._props.Slots[slot]._required) {
continue;
}
// Sort the array of items that can be attached to the slot in order of ascending price
const filters = itemTemplate._props.Slots[slot]._props.filters[0].Filter;
const validFilters = filters.filter((f) => this.databaseTables.templates.items[f] !== undefined && this.databaseTables.templates.items[f]._id !== undefined);
const filtersSorted = validFilters.sort((f1, f2) => {
const f1Price = this.databaseTables.templates.items[f1]._id;
const f2Price = this.databaseTables.templates.items[f2]._id;
if (f1Price > f2Price)
return -1;
if (f1Price < f2Price)
return 1;
return 0;
});
// Add the first valid item to the slot along with all of the items attached to its (child) slots
let itemPart;
for (const filter in filtersSorted) {
if (!bannedParts.includes(filters[filter])) {
itemPart = {
_id: this.hashUtil.generate(),
_tpl: filters[filter],
parentId: item._id,
slotId: itemTemplate._props.Slots[slot]._name
};
filledItem = filledItem.concat(this.fillItemSlots(itemPart, bannedParts));
break;
}
}
if (itemPart === undefined) {
this.commonUtils.logError(`Could not find valid part to put in ${itemTemplate._props.Slots[slot]._name} for ${this.commonUtils.getItemName(item._tpl)}`);
}
}
isValid = true;
for (const itemPart in filledItem) {
// Check if any conflicting parts exist in the Item array. If so, prevent the conflicting item from being used in the next candidate
const conflictingItems = this.databaseTables.templates.items[filledItem[itemPart]._tpl]._props.ConflictingItems;
for (const conflictingItem in conflictingItems) {
if (filledItem.map(p => p._tpl).includes(conflictingItems[conflictingItem])) {
if (!bannedParts.includes(conflictingItems[conflictingItem])) {
bannedParts.push(conflictingItems[conflictingItem]);
}
isValid = false;
if (verboseLogging)
this.commonUtils.logInfo(`Finding parts for ${this.commonUtils.getItemName(item._tpl)}...${this.commonUtils.getItemName(conflictingItems[conflictingItem])} has a conflict with another part`);
break;
}
}
if (!isValid) {
break;
}
//this.commonUtils.logInfo(`Finding parts for ${this.commonUtils.getItemName(item._tpl)}...found ${this.commonUtils.getItemName(filledItem[itemPart]._tpl)}`);
}
}
return filledItem;
}
findBestWeaponMatchfromTraders(item) {
let weapon = [];
// Search all traders to see if they sell the weapon
for (const traderID in this.databaseTables.traders) {
const assort = this.databaseTables.traders[traderID].assort;
// Ignore traders who don't sell anything (i.e. Lightkeeper)
if ((assort === null) || (assort === undefined))
continue;
//if (verboseLogging) this.commonUtils.logInfo(`Searching ${this.databaseTables.traders[traderID].base.nickname}...`);
for (const assortID in assort.items) {
const weaponCandidate = [];
if (assort.items[assortID]._tpl === item._id) {
// Get all parts attached to the weapon
const matchingSlots = this.findChildSlotIndexesInTraderAssort(assort, assortID);
for (const matchingSlot in matchingSlots) {
weaponCandidate.push(assort.items[matchingSlots[matchingSlot]]);
}
// Store the initial weapon selection
if (weapon.length === 0) {
weapon = weaponCandidate;
continue;
}
// Determine if the weapon is better than the previous one found
if (this.weaponBaseValue(item, weaponCandidate) > this.weaponBaseValue(item, weapon)) {
weapon = weaponCandidate;
}
}
}
}
return weapon;
}
weaponBaseValue(baseWeaponItem, weaponParts) {
const [width, height, weight] = this.getWeaponProperties(baseWeaponItem, weaponParts);
let value = weight * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.weight;
value += width * height * config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.size;
return value;
}
/**
* Gets relevant weapon properties
* @param baseWeaponItem The base weapon template (namely the receiver)
* @param weaponParts All parts attached to the weapon (which may include the base weapon item itself)
* @returns [length, width, weight] of the assembled weapon
*/
getWeaponProperties(baseWeaponItem, weaponParts) {
if (baseWeaponItem._props === undefined) {
this.commonUtils.logError(`The properties of ${baseWeaponItem._id} (${this.commonUtils.getItemName(baseWeaponItem._id)}) are undefined. Cannot create loot value.`);
}
let width = baseWeaponItem._props.Width;
let height = baseWeaponItem._props.Height;
let weight = baseWeaponItem._props.Weight;
//if (verboseLogging) this.commonUtils.logInfo(`Getting properties for ${this.commonUtils.getItemName(baseWeaponItem._id)}... Base: Width=${width},Height=${height},Weight=${weight}`);
for (const weaponPart in weaponParts) {
const templateID = weaponParts[weaponPart]._tpl;
const slotID = weaponParts[weaponPart].slotId;
if (baseWeaponItem._id === templateID) {
continue;
}
weight += this.databaseTables.templates.items[templateID]._props.Weight ?? 0;
// Fold the weapon if possible
if (baseWeaponItem._props.FoldedSlot !== undefined) {
if (baseWeaponItem._props.FoldedSlot === slotID) {
//if (verboseLogging) this.commonUtils.logInfo(`Getting properties for ${this.commonUtils.getItemName(baseWeaponItem._id)}...folds with ${this.commonUtils.getItemName(templateID)} => Width=${width},Height=${height},Weight=${weight}`);
continue;
}
}
width += this.databaseTables.templates.items[templateID]._props.ExtraSizeLeft ?? 0;
width += this.databaseTables.templates.items[templateID]._props.ExtraSizeRight ?? 0;
height += this.databaseTables.templates.items[templateID]._props.ExtraSizeUp ?? 0;
height += this.databaseTables.templates.items[templateID]._props.ExtraSizeDown ?? 0;
//if (verboseLogging) this.commonUtils.logInfo(`Getting properties for ${this.commonUtils.getItemName(baseWeaponItem._id)}...found ${this.commonUtils.getItemName(templateID)} => Width=${width},Height=${height},Weight=${weight}`);
}
if (verboseLogging)
this.commonUtils.logInfo(`Getting properties for ${this.commonUtils.getItemName(baseWeaponItem._id)}... Final: Width=${width},Height=${height},Weight=${weight}`);
return [width, height, weight];
}
findChildSlotIndexesInTraderAssort(assort, parentIndex) {
let matchingSlots = [];
const parentID = assort.items[parentIndex]._id;
for (const assortID in assort.items) {
if (assort.items[assortID].parentId === parentID) {
matchingSlots.push(assortID);
matchingSlots = matchingSlots.concat(this.findChildSlotIndexesInTraderAssort(assort, assortID));
}
}
return matchingSlots;
}
validLootRankingDataExists() {
if (!this.fileSystem.exists(lootFilePath)) {
this.commonUtils.logInfo("Loot ranking data not found.");
return false;
}
if (config_json_1.default.destroy_loot_during_raid.loot_ranking.alwaysRegenerate) {
this.commonUtils.logInfo("Loot ranking data forced to regenerate.");
this.fileSystem.remove(lootFilePath);
return false;
}
// Get the current file data
const rankingData = this.getLootRankingDataFromFile();
// Check if the parent weighting in config.json matches the file data
let parentParametersMatch = true;
for (const parentID in config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.parents) {
if (!(parentID in rankingData.parentWeighting)) {
parentParametersMatch = false;
break;
}
if (rankingData.parentWeighting[parentID].value !== config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.parents[parentID].value) {
parentParametersMatch = false;
break;
}
}
// Check if the general weighting parameters in config.json match the file data
if (rankingData.costPerSlot !== config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.cost_per_slot ||
rankingData.maxDim !== config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.max_dim ||
rankingData.size !== config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.size ||
rankingData.gridSize !== config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.gridSize ||
rankingData.weight !== config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.weight ||
rankingData.armorClass !== config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.armor_class ||
!parentParametersMatch) {
this.commonUtils.logInfo("Loot ranking parameters have changed; deleting cached data.");
this.fileSystem.remove(lootFilePath);
return false;
}
return true;
}
canEquipItem(item) {
const defaultInventory = this.databaseTables.templates.items[config_json_1.default.destroy_loot_during_raid.loot_ranking.weighting.default_inventory_id];
if (defaultInventory === undefined) {
return false;
}
for (const slot in defaultInventory._props.Slots) {
const filters = defaultInventory._props.Slots[slot]._props.filters[0].Filter;
for (const filter in filters) {
if (CommonUtils_1.CommonUtils.hasParent(item, filters[filter], this.databaseTables)) {
//this.commonUtils.logInfo(`${this.commonUtils.getItemName(item._id)} can be equipped in ${defaultInventory._props.Slots[slot]._name}`);
return true;
}
}
}
return false;
}
}
exports.LootRankingGenerator = LootRankingGenerator;
//# sourceMappingURL=LootRankingGenerator.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,211 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/naming-convention */
const config_json_1 = __importDefault(require("../config/config.json"));
const CommonUtils_1 = require("./CommonUtils");
const LootRankingGenerator_1 = require("./LootRankingGenerator");
const ConfigTypes_1 = require("C:/snapshot/project/obj/models/enums/ConfigTypes");
const modName = "LateToTheParty";
class LateToTheParty {
commonUtils;
lootRankingGenerator;
logger;
locationConfig;
inRaidConfig;
configServer;
databaseServer;
databaseTables;
fileSystem;
localeService;
botWeaponGenerator;
hashUtil;
originalLooseLootMultipliers;
originalStaticLootMultipliers;
preSptLoad(container) {
const staticRouterModService = container.resolve("StaticRouterModService");
const dynamicRouterModService = container.resolve("DynamicRouterModService");
this.logger = container.resolve("WinstonLogger");
// Get config.json settings for the bepinex plugin
staticRouterModService.registerStaticRouter(`StaticGetConfig${modName}`, [{
url: "/LateToTheParty/GetConfig",
action: async () => {
return JSON.stringify(config_json_1.default);
}
}], "GetConfig");
if (!config_json_1.default.enabled) {
return;
}
// Game start
// Needed to initialize loot ranking generator after any other mods have potentially changed config settings
staticRouterModService.registerStaticRouter(`StaticAkiGameStart${modName}`, [{
url: "/client/game/start",
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
action: async (url, info, sessionId, output) => {
this.generateLootRankingData(sessionId);
return output;
}
}], "aki");
// Get lootRanking.json for loot ranking
staticRouterModService.registerStaticRouter(`StaticGetLootRankingData${modName}`, [{
url: "/LateToTheParty/GetLootRankingData",
action: async () => {
return JSON.stringify(this.lootRankingGenerator.getLootRankingDataFromFile());
}
}], "GetLootRankingData");
// Get an array of all car extract names
staticRouterModService.registerStaticRouter(`StaticGetCarExtractNames${modName}`, [{
url: "/LateToTheParty/GetCarExtractNames",
action: async () => {
return JSON.stringify(this.inRaidConfig.carExtracts);
}
}], "GetCarExtractNames");
// Adjust the static and loose loot multipliers
dynamicRouterModService.registerDynamicRouter(`DynamicSetLootMultipliers${modName}`, [{
url: "/LateToTheParty/SetLootMultiplier/",
action: async (url) => {
const urlParts = url.split("/");
const factor = Number(urlParts[urlParts.length - 1]);
this.setLootMultipliers(factor);
return JSON.stringify({ resp: "OK" });
}
}], "SetLootMultiplier");
}
postDBLoad(container) {
this.configServer = container.resolve("ConfigServer");
this.databaseServer = container.resolve("DatabaseServer");
this.fileSystem = container.resolve("FileSystemSync");
this.localeService = container.resolve("LocaleService");
this.botWeaponGenerator = container.resolve("BotWeaponGenerator");
this.hashUtil = container.resolve("HashUtil");
this.locationConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.LOCATION);
this.inRaidConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.IN_RAID);
this.databaseTables = this.databaseServer.getTables();
this.commonUtils = new CommonUtils_1.CommonUtils(this.logger, this.databaseTables, this.localeService);
if (!config_json_1.default.enabled) {
this.commonUtils.logInfo("Mod disabled in config.json.");
return;
}
if (!this.doesFileIntegrityCheckPass()) {
config_json_1.default.enabled = false;
return;
}
this.adjustSPTScavRaidChanges();
}
postSptLoad() {
if (!config_json_1.default.enabled) {
return;
}
// Store the original static and loose loot multipliers
this.getLootMultipliers();
}
adjustSPTScavRaidChanges() {
this.commonUtils.logInfo("Adjusting SPT Scav-raid changes...");
for (const map in this.locationConfig.scavRaidTimeSettings.maps) {
if (config_json_1.default.scav_raid_adjustments.always_spawn_late) {
this.locationConfig.scavRaidTimeSettings.maps[map].reducedChancePercent = 100;
}
if (config_json_1.default.destroy_loot_during_raid.enabled) {
this.locationConfig.scavRaidTimeSettings.maps[map].reduceLootByPercent = false;
}
}
}
getLootMultipliers() {
this.originalLooseLootMultipliers =
{
bigmap: this.locationConfig.looseLootMultiplier.bigmap,
develop: this.locationConfig.looseLootMultiplier.develop,
factory4_day: this.locationConfig.looseLootMultiplier.factory4_day,
factory4_night: this.locationConfig.looseLootMultiplier.factory4_night,
hideout: this.locationConfig.looseLootMultiplier.hideout,
interchange: this.locationConfig.looseLootMultiplier.interchange,
laboratory: this.locationConfig.looseLootMultiplier.laboratory,
lighthouse: this.locationConfig.looseLootMultiplier.lighthouse,
privatearea: this.locationConfig.looseLootMultiplier.privatearea,
rezervbase: this.locationConfig.looseLootMultiplier.rezervbase,
shoreline: this.locationConfig.looseLootMultiplier.shoreline,
suburbs: this.locationConfig.looseLootMultiplier.suburbs,
tarkovstreets: this.locationConfig.looseLootMultiplier.tarkovstreets,
terminal: this.locationConfig.looseLootMultiplier.terminal,
town: this.locationConfig.looseLootMultiplier.town,
woods: this.locationConfig.looseLootMultiplier.woods,
sandbox: this.locationConfig.looseLootMultiplier.sandbox
};
this.originalStaticLootMultipliers =
{
bigmap: this.locationConfig.staticLootMultiplier.bigmap,
develop: this.locationConfig.staticLootMultiplier.develop,
factory4_day: this.locationConfig.staticLootMultiplier.factory4_day,
factory4_night: this.locationConfig.staticLootMultiplier.factory4_night,
hideout: this.locationConfig.staticLootMultiplier.hideout,
interchange: this.locationConfig.staticLootMultiplier.interchange,
laboratory: this.locationConfig.staticLootMultiplier.laboratory,
lighthouse: this.locationConfig.staticLootMultiplier.lighthouse,
privatearea: this.locationConfig.staticLootMultiplier.privatearea,
rezervbase: this.locationConfig.staticLootMultiplier.rezervbase,
shoreline: this.locationConfig.staticLootMultiplier.shoreline,
suburbs: this.locationConfig.staticLootMultiplier.suburbs,
tarkovstreets: this.locationConfig.staticLootMultiplier.tarkovstreets,
terminal: this.locationConfig.staticLootMultiplier.terminal,
town: this.locationConfig.staticLootMultiplier.town,
woods: this.locationConfig.staticLootMultiplier.woods,
sandbox: this.locationConfig.staticLootMultiplier.sandbox
};
}
setLootMultipliers(factor) {
this.commonUtils.logInfo(`Adjusting loot multipliers by a factor of ${factor}...`);
this.locationConfig.looseLootMultiplier.bigmap = this.originalLooseLootMultipliers.bigmap * factor;
this.locationConfig.looseLootMultiplier.develop = this.originalLooseLootMultipliers.develop * factor;
this.locationConfig.looseLootMultiplier.factory4_day = this.originalLooseLootMultipliers.factory4_day * factor;
this.locationConfig.looseLootMultiplier.factory4_night = this.originalLooseLootMultipliers.factory4_night * factor;
this.locationConfig.looseLootMultiplier.hideout = this.originalLooseLootMultipliers.hideout * factor;
this.locationConfig.looseLootMultiplier.interchange = this.originalLooseLootMultipliers.interchange * factor;
this.locationConfig.looseLootMultiplier.laboratory = this.originalLooseLootMultipliers.laboratory * factor;
this.locationConfig.looseLootMultiplier.lighthouse = this.originalLooseLootMultipliers.lighthouse * factor;
this.locationConfig.looseLootMultiplier.privatearea = this.originalLooseLootMultipliers.privatearea * factor;
this.locationConfig.looseLootMultiplier.rezervbase = this.originalLooseLootMultipliers.rezervbase * factor;
this.locationConfig.looseLootMultiplier.shoreline = this.originalLooseLootMultipliers.shoreline * factor;
this.locationConfig.looseLootMultiplier.suburbs = this.originalLooseLootMultipliers.suburbs * factor;
this.locationConfig.looseLootMultiplier.tarkovstreets = this.originalLooseLootMultipliers.tarkovstreets * factor;
this.locationConfig.looseLootMultiplier.terminal = this.originalLooseLootMultipliers.terminal * factor;
this.locationConfig.looseLootMultiplier.town = this.originalLooseLootMultipliers.town * factor;
this.locationConfig.looseLootMultiplier.woods = this.originalLooseLootMultipliers.woods * factor;
this.locationConfig.looseLootMultiplier.sandbox = this.originalLooseLootMultipliers.sandbox * factor;
this.locationConfig.staticLootMultiplier.bigmap = this.originalStaticLootMultipliers.bigmap * factor;
this.locationConfig.staticLootMultiplier.develop = this.originalStaticLootMultipliers.develop * factor;
this.locationConfig.staticLootMultiplier.factory4_day = this.originalStaticLootMultipliers.factory4_day * factor;
this.locationConfig.staticLootMultiplier.factory4_night = this.originalStaticLootMultipliers.factory4_night * factor;
this.locationConfig.staticLootMultiplier.hideout = this.originalStaticLootMultipliers.hideout * factor;
this.locationConfig.staticLootMultiplier.interchange = this.originalStaticLootMultipliers.interchange * factor;
this.locationConfig.staticLootMultiplier.laboratory = this.originalStaticLootMultipliers.laboratory * factor;
this.locationConfig.staticLootMultiplier.lighthouse = this.originalStaticLootMultipliers.lighthouse * factor;
this.locationConfig.staticLootMultiplier.privatearea = this.originalStaticLootMultipliers.privatearea * factor;
this.locationConfig.staticLootMultiplier.rezervbase = this.originalStaticLootMultipliers.rezervbase * factor;
this.locationConfig.staticLootMultiplier.shoreline = this.originalStaticLootMultipliers.shoreline * factor;
this.locationConfig.staticLootMultiplier.suburbs = this.originalStaticLootMultipliers.suburbs * factor;
this.locationConfig.staticLootMultiplier.tarkovstreets = this.originalStaticLootMultipliers.tarkovstreets * factor;
this.locationConfig.staticLootMultiplier.terminal = this.originalStaticLootMultipliers.terminal * factor;
this.locationConfig.staticLootMultiplier.town = this.originalStaticLootMultipliers.town * factor;
this.locationConfig.staticLootMultiplier.woods = this.originalStaticLootMultipliers.woods * factor;
this.locationConfig.staticLootMultiplier.sandbox = this.originalStaticLootMultipliers.sandbox * factor;
}
generateLootRankingData(sessionId) {
this.lootRankingGenerator = new LootRankingGenerator_1.LootRankingGenerator(this.commonUtils, this.databaseTables, this.fileSystem, this.botWeaponGenerator, this.hashUtil);
this.lootRankingGenerator.generateLootRankingData(sessionId);
}
doesFileIntegrityCheckPass() {
const path = `${__dirname}/..`;
if (this.fileSystem.exists(`${path}/log/`)) {
this.commonUtils.logWarning("Found obsolete log folder 'user\\mods\\DanW-LateToTheParty\\log'. Logs are now saved in 'BepInEx\\plugins\\DanW-LateToTheParty\\log'.");
}
if (this.fileSystem.exists(`${path}/../../../BepInEx/plugins/LateToTheParty.dll`)) {
this.commonUtils.logError("Please remove BepInEx/plugins/LateToTheParty.dll from the previous version of this mod and restart the server, or it will NOT work correctly.");
return false;
}
return true;
}
}
module.exports = { mod: new LateToTheParty() };
//# sourceMappingURL=mod.js.map

File diff suppressed because one or more lines are too long