From 1f76ce5d10fe885012bdcc7a7d57894183144791 Mon Sep 17 00:00:00 2001 From: Dev Date: Sat, 6 Jul 2024 13:39:56 +0100 Subject: [PATCH] Refactored airdrop loot code --- project/src/controllers/LocationController.ts | 76 +------- project/src/controllers/ProfileController.ts | 2 +- project/src/di/Container.ts | 4 + project/src/generators/LootGenerator.ts | 71 ++------ project/src/models/enums/AirdropType.ts | 4 +- .../src/models/spt/services/LootRequest.ts | 2 - .../routers/static/LocationStaticRouter.ts | 8 +- project/src/services/AirdropService.ts | 164 ++++++++++++++++++ 8 files changed, 193 insertions(+), 138 deletions(-) create mode 100644 project/src/services/AirdropService.ts diff --git a/project/src/controllers/LocationController.ts b/project/src/controllers/LocationController.ts index a443e298..3abea48d 100644 --- a/project/src/controllers/LocationController.ts +++ b/project/src/controllers/LocationController.ts @@ -2,55 +2,42 @@ import { inject, injectable } from "tsyringe"; import { ApplicationContext } from "@spt/context/ApplicationContext"; import { ContextVariableType } from "@spt/context/ContextVariableType"; import { LocationGenerator } from "@spt/generators/LocationGenerator"; -import { LootGenerator } from "@spt/generators/LootGenerator"; -import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper"; import { ILocationBase } from "@spt/models/eft/common/ILocationBase"; import { ILocationsGenerateAllResponse } from "@spt/models/eft/common/ILocationsSourceDestinationBase"; import { ILooseLoot, SpawnpointTemplate } from "@spt/models/eft/common/ILooseLoot"; import { IGetAirdropLootResponse } from "@spt/models/eft/location/IGetAirdropLootResponse"; import { IGetLocationRequestData } from "@spt/models/eft/location/IGetLocationRequestData"; -import { AirdropTypeEnum } from "@spt/models/enums/AirdropType"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; -import { AirdropLoot, IAirdropConfig } from "@spt/models/spt/config/IAirdropConfig"; import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig"; import { IRaidChanges } from "@spt/models/spt/location/IRaidChanges"; import { ILocations } from "@spt/models/spt/server/ILocations"; -import { LootRequest } from "@spt/models/spt/services/LootRequest"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { ConfigServer } from "@spt/servers/ConfigServer"; +import { AirdropService } from "@spt/services/AirdropService"; import { DatabaseService } from "@spt/services/DatabaseService"; -import { ItemFilterService } from "@spt/services/ItemFilterService"; import { LocalisationService } from "@spt/services/LocalisationService"; import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService"; import { ICloner } from "@spt/utils/cloners/ICloner"; -import { HashUtil } from "@spt/utils/HashUtil"; -import { RandomUtil } from "@spt/utils/RandomUtil"; import { TimeUtil } from "@spt/utils/TimeUtil"; @injectable() export class LocationController { - protected airdropConfig: IAirdropConfig; protected locationConfig: ILocationConfig; constructor( - @inject("HashUtil") protected hashUtil: HashUtil, - @inject("RandomUtil") protected randomUtil: RandomUtil, - @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("PrimaryLogger") protected logger: ILogger, @inject("LocationGenerator") protected locationGenerator: LocationGenerator, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService, - @inject("ItemFilterService") protected itemFilterService: ItemFilterService, - @inject("LootGenerator") protected lootGenerator: LootGenerator, @inject("DatabaseService") protected databaseService: DatabaseService, + @inject("AirdropService") protected airdropService: AirdropService, @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("ConfigServer") protected configServer: ConfigServer, @inject("ApplicationContext") protected applicationContext: ApplicationContext, @inject("PrimaryCloner") protected cloner: ICloner, ) { - this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP); this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION); } @@ -165,65 +152,8 @@ export class LocationController return { locations: locations, paths: locationsFromDb.base.paths }; } - /** - * Handle client/location/getAirdropLoot - * Get loot for an airdrop container - * Generates it randomly based on config/airdrop.json values - * @returns Array of LootItem objects - */ public getAirdropLoot(): IGetAirdropLootResponse { - const airdropType = this.chooseAirdropType(); - this.logger.debug(`Chose ${airdropType} for airdrop loot`); - - const airdropConfig = this.getAirdropLootConfigByType(airdropType); - - return { icon: airdropType, container: this.lootGenerator.createRandomLoot(airdropConfig) }; - } - - /** - * Randomly pick a type of airdrop loot using weighted values from config - * @returns airdrop type value - */ - protected chooseAirdropType(): AirdropTypeEnum - { - const possibleAirdropTypes = this.airdropConfig.airdropTypeWeightings; - - return this.weightedRandomHelper.getWeightedValue(possibleAirdropTypes); - } - - /** - * Get the configuration for a specific type of airdrop - * @param airdropType Type of airdrop to get settings for - * @returns LootRequest - */ - protected getAirdropLootConfigByType(airdropType: AirdropTypeEnum): LootRequest - { - let lootSettingsByType: AirdropLoot = this.airdropConfig.loot[airdropType]; - if (!lootSettingsByType) - { - this.logger.error( - this.localisationService.getText("location-unable_to_find_airdrop_drop_config_of_type", airdropType), - ); - lootSettingsByType = this.airdropConfig.loot[AirdropTypeEnum.COMMON]; - } - - return { - airdropLoot: airdropType, - weaponPresetCount: lootSettingsByType.weaponPresetCount, - armorPresetCount: lootSettingsByType.armorPresetCount, - itemCount: lootSettingsByType.itemCount, - weaponCrateCount: lootSettingsByType.weaponCrateCount, - itemBlacklist: [ - ...lootSettingsByType.itemBlacklist, - ...this.itemFilterService.getItemRewardBlacklist(), - ...this.itemFilterService.getBossItems(), - ], - itemTypeWhitelist: lootSettingsByType.itemTypeWhitelist, - itemLimits: lootSettingsByType.itemLimits, - itemStackLimits: lootSettingsByType.itemStackLimits, - armorLevelWhitelist: lootSettingsByType.armorLevelWhitelist, - allowBossItems: lootSettingsByType.allowBossItems, - }; + return this.airdropService.generateAirdropLoot(); } } diff --git a/project/src/controllers/ProfileController.ts b/project/src/controllers/ProfileController.ts index 6d227b91..2d21a98a 100644 --- a/project/src/controllers/ProfileController.ts +++ b/project/src/controllers/ProfileController.ts @@ -87,7 +87,7 @@ export class ProfileController if (!pmc?.Info?.Level) { return { - username: profile.info.username, + username: profile.info?.username ?? "", nickname: "unknown", side: "unknown", currlvl: 0, diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index 7cf48ebb..35823d78 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -197,6 +197,7 @@ import { IWebSocketConnectionHandler } from "@spt/servers/ws/IWebSocketConnectio import { DefaultSptWebSocketMessageHandler } from "@spt/servers/ws/message/DefaultSptWebSocketMessageHandler"; import { ISptWebSocketMessageHandler } from "@spt/servers/ws/message/ISptWebSocketMessageHandler"; import { SptWebSocketConnectionHandler } from "@spt/servers/ws/SptWebSocketConnectionHandler"; +import { AirdropService } from "@spt/services/AirdropService"; import { BotEquipmentFilterService } from "@spt/services/BotEquipmentFilterService"; import { BotEquipmentModPoolService } from "@spt/services/BotEquipmentModPoolService"; import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService"; @@ -795,6 +796,9 @@ export class Container depContainer.register("ProfileActivityService", ProfileActivityService, { lifecycle: Lifecycle.Singleton, }); + depContainer.register("AirdropService", AirdropService, { + lifecycle: Lifecycle.Singleton, + }); } private static registerServers(depContainer: DependencyContainer): void diff --git a/project/src/generators/LootGenerator.ts b/project/src/generators/LootGenerator.ts index d368d90b..02a77bc1 100644 --- a/project/src/generators/LootGenerator.ts +++ b/project/src/generators/LootGenerator.ts @@ -8,7 +8,6 @@ import { Item } from "@spt/models/eft/common/tables/IItem"; import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem"; import { BaseClasses } from "@spt/models/enums/BaseClasses"; import { ISealedAirdropContainerSettings, RewardDetails } from "@spt/models/spt/config/IInventoryConfig"; -import { LootItem } from "@spt/models/spt/services/LootItem"; import { LootRequest } from "@spt/models/spt/services/LootRequest"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { DatabaseService } from "@spt/services/DatabaseService"; @@ -17,8 +16,6 @@ import { LocalisationService } from "@spt/services/LocalisationService"; import { RagfairLinkedItemService } from "@spt/services/RagfairLinkedItemService"; import { HashUtil } from "@spt/utils/HashUtil"; import { RandomUtil } from "@spt/utils/RandomUtil"; -import { AirdropTypeEnum } from "@spt/models/enums/AirdropType"; -import { ItemTpl } from "@spt/models/enums/ItemTpl"; type ItemLimit = { current: number, max: number }; @@ -48,39 +45,6 @@ export class LootGenerator public createRandomLoot(options: LootRequest): Item[] { const result: Item[] = []; - let airdropContainerParentID = ""; - - if (options.airdropLoot) - { - airdropContainerParentID = this.hashUtil.generate(); - let airdropContainer = { - _id: airdropContainerParentID, - _tpl: "", - upd: { - SpawnedInSession: true, - StackObjectsCount: 1 - } - } - - switch (options.airdropLoot) { - case AirdropTypeEnum.MEDICAL: - airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_MEDICAL_CRATE - break; - case AirdropTypeEnum.SUPPLY: - airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_SUPPLY_CRATE - break; - case AirdropTypeEnum.WEAPON: - airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_WEAPON_CRATE - break; - case AirdropTypeEnum.COMMON: - default: - airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_COMMON_SUPPLY_CRATE - break; - } - - result.push(airdropContainer); - } - const itemTypeCounts = this.initItemLimitCounter(options.itemLimits); const itemsDb = this.databaseService.getItems(); @@ -104,18 +68,18 @@ export class LootGenerator } // Handle sealed weapon containers - const desiredWeaponCrateCount = this.randomUtil.getInt( + const sealedWeaponCrateCount = this.randomUtil.getInt( options.weaponCrateCount.min, options.weaponCrateCount.max, ); - if (desiredWeaponCrateCount > 0) + if (sealedWeaponCrateCount > 0) { - // Get list of all sealed containers from db + // Get list of all sealed containers from db - they're all the same, just for flavor const sealedWeaponContainerPool = Object.values(itemsDb).filter((item) => item._name.includes("event_container_airdrop"), ); - for (let index = 0; index < desiredWeaponCrateCount; index++) + for (let index = 0; index < sealedWeaponCrateCount; index++) { // Choose one at random + add to results array const chosenSealedContainer = this.randomUtil.getArrayValue(sealedWeaponContainerPool); @@ -124,8 +88,8 @@ export class LootGenerator _tpl: chosenSealedContainer._id, upd: { StackObjectsCount: 1, - SpawnedInSession: true - } + SpawnedInSession: true, + }, }); } } @@ -139,6 +103,7 @@ export class LootGenerator && options.itemTypeWhitelist.includes(item[1]._parent), ); + // Pool has items we could add as loot, proceed if (items.length > 0) { const randomisedItemCount = this.randomUtil.getInt(options.itemCount.min, options.itemCount.max); @@ -221,19 +186,6 @@ export class LootGenerator } } - for (const item of result) { - if (item._id == airdropContainerParentID) - { - continue; - } - - if (!item.parentId) - { - item.parentId = airdropContainerParentID; - item.slotId = "main" - } - } - return result; } @@ -313,8 +265,8 @@ export class LootGenerator _tpl: randomItem._id, upd: { StackObjectsCount: 1, - SpawnedInSession: true - } + SpawnedInSession: true, + }, }; // Special case - handle items that need a stackcount > 1 @@ -421,8 +373,9 @@ export class LootGenerator const presetAndMods: Item[] = this.itemHelper.replaceIDs(chosenPreset._items); this.itemHelper.remapRootItemId(presetAndMods); // Add chosen preset tpl to result array - presetAndMods.forEach(item => { - result.push(item) + presetAndMods.forEach((item) => + { + result.push(item); }); if (itemLimitCount) diff --git a/project/src/models/enums/AirdropType.ts b/project/src/models/enums/AirdropType.ts index 916a3001..6a2c3dc0 100644 --- a/project/src/models/enums/AirdropType.ts +++ b/project/src/models/enums/AirdropType.ts @@ -3,5 +3,5 @@ export enum AirdropTypeEnum COMMON = "common", SUPPLY = "supply", MEDICAL = "medical", - WEAPON = "weapon" -} \ No newline at end of file + WEAPON = "weapon", +} diff --git a/project/src/models/spt/services/LootRequest.ts b/project/src/models/spt/services/LootRequest.ts index 720d45aa..29a610f9 100644 --- a/project/src/models/spt/services/LootRequest.ts +++ b/project/src/models/spt/services/LootRequest.ts @@ -1,9 +1,7 @@ import { MinMax } from "@spt/models/common/MinMax"; -import { AirdropTypeEnum } from "@spt/models/enums/AirdropType"; export interface LootRequest { - airdropLoot?: AirdropTypeEnum weaponPresetCount: MinMax armorPresetCount: MinMax itemCount: MinMax diff --git a/project/src/routers/static/LocationStaticRouter.ts b/project/src/routers/static/LocationStaticRouter.ts index 88f962b8..a81b5310 100644 --- a/project/src/routers/static/LocationStaticRouter.ts +++ b/project/src/routers/static/LocationStaticRouter.ts @@ -3,6 +3,7 @@ import { LocationCallbacks } from "@spt/callbacks/LocationCallbacks"; import { RouteAction, StaticRouter } from "@spt/di/Router"; import { ILocationsGenerateAllResponse } from "@spt/models/eft/common/ILocationsSourceDestinationBase"; import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData"; +import { IGetAirdropLootResponse } from "@spt/models/eft/location/IGetAirdropLootResponse"; @injectable() export class LocationStaticRouter extends StaticRouter @@ -24,7 +25,12 @@ export class LocationStaticRouter extends StaticRouter ), new RouteAction( "/client/airdrop/loot", - async (url: string, info: any, sessionID: string, _output: string): Promise => + async ( + url: string, + info: any, + sessionID: string, + output: string, + ): Promise> => { return this.locationCallbacks.getAirdropLoot(url, info, sessionID); }, diff --git a/project/src/services/AirdropService.ts b/project/src/services/AirdropService.ts new file mode 100644 index 00000000..1b455e8f --- /dev/null +++ b/project/src/services/AirdropService.ts @@ -0,0 +1,164 @@ +import { inject, injectable } from "tsyringe"; +import { LootGenerator } from "@spt/generators/LootGenerator"; +import { ItemHelper } from "@spt/helpers/ItemHelper"; +import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper"; +import { Item } from "@spt/models/eft/common/tables/IItem"; +import { IGetAirdropLootResponse } from "@spt/models/eft/location/IGetAirdropLootResponse"; +import { AirdropTypeEnum } from "@spt/models/enums/AirdropType"; +import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; +import { ItemTpl } from "@spt/models/enums/ItemTpl"; +import { AirdropLoot, IAirdropConfig } from "@spt/models/spt/config/IAirdropConfig"; +import { LootRequest } from "@spt/models/spt/services/LootRequest"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import { ConfigServer } from "@spt/servers/ConfigServer"; +import { DatabaseService } from "@spt/services/DatabaseService"; +import { ItemFilterService } from "@spt/services/ItemFilterService"; +import { LocalisationService } from "@spt/services/LocalisationService"; +import { ICloner } from "@spt/utils/cloners/ICloner"; +import { HashUtil } from "@spt/utils/HashUtil"; + +@injectable() +export class AirdropService +{ + protected airdropConfig: IAirdropConfig; + + constructor( + @inject("PrimaryLogger") protected logger: ILogger, + @inject("HashUtil") protected hashUtil: HashUtil, + @inject("ItemHelper") protected itemHelper: ItemHelper, + @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, + @inject("LocalisationService") protected localisationService: LocalisationService, + @inject("ItemFilterService") protected itemFilterService: ItemFilterService, + @inject("LootGenerator") protected lootGenerator: LootGenerator, + @inject("DatabaseService") protected databaseService: DatabaseService, + @inject("ConfigServer") protected configServer: ConfigServer, + @inject("PrimaryCloner") protected cloner: ICloner, + ) + { + this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP); + } + + /** + * Handle client/location/getAirdropLoot + * Get loot for an airdrop container + * Generates it randomly based on config/airdrop.json values + * @returns Array of LootItem objects + */ + public generateAirdropLoot(): IGetAirdropLootResponse + { + const airdropType = this.chooseAirdropType(); + this.logger.debug(`Chose ${airdropType} for airdrop loot`); + + // Common/weapon/etc + const airdropConfig = this.getAirdropLootConfigByType(airdropType); + + // generate loot to put into airdrop crate + const crateLoot = this.lootGenerator.createRandomLoot(airdropConfig); + + // Create airdrop crate and add to result in first spot + const airdropCrateItem = this.getAirdropCrateItem(airdropType); + + // Add crate to front of array + crateLoot.unshift(airdropCrateItem); + + // Reparent loot items to create we added above + for (const item of crateLoot) + { + if (item._id == airdropCrateItem._id) + { + // Crate itself, don't alter + continue; + } + + // no parentId = root item, make item have create as parent + if (!item.parentId) + { + item.parentId = airdropCrateItem._id; + item.slotId = "main"; + } + } + + return { icon: airdropType, container: crateLoot }; + } + + /** + * Create a container create item based on passed in airdrop type + * @param airdropType What tpye of container: weapon/common etc + * @returns Item + */ + protected getAirdropCrateItem(airdropType: AirdropTypeEnum): Item + { + const airdropContainer = { + _id: this.hashUtil.generate(), + _tpl: "", // picked later + upd: { + SpawnedInSession: true, + StackObjectsCount: 1, + }, + }; + + switch (airdropType) + { + case AirdropTypeEnum.MEDICAL: + airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_MEDICAL_CRATE; + break; + case AirdropTypeEnum.SUPPLY: + airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_SUPPLY_CRATE; + break; + case AirdropTypeEnum.WEAPON: + airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_WEAPON_CRATE; + break; + case AirdropTypeEnum.COMMON: + default: + airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_COMMON_SUPPLY_CRATE; + break; + } + + return airdropContainer; + } + + /** + * Randomly pick a type of airdrop loot using weighted values from config + * @returns airdrop type value + */ + protected chooseAirdropType(): AirdropTypeEnum + { + const possibleAirdropTypes = this.airdropConfig.airdropTypeWeightings; + + return this.weightedRandomHelper.getWeightedValue(possibleAirdropTypes); + } + + /** + * Get the configuration for a specific type of airdrop + * @param airdropType Type of airdrop to get settings for + * @returns LootRequest + */ + protected getAirdropLootConfigByType(airdropType: AirdropTypeEnum): LootRequest + { + let lootSettingsByType: AirdropLoot = this.airdropConfig.loot[airdropType]; + if (!lootSettingsByType) + { + this.logger.error( + this.localisationService.getText("location-unable_to_find_airdrop_drop_config_of_type", airdropType), + ); + lootSettingsByType = this.airdropConfig.loot[AirdropTypeEnum.COMMON]; + } + + return { + weaponPresetCount: lootSettingsByType.weaponPresetCount, + armorPresetCount: lootSettingsByType.armorPresetCount, + itemCount: lootSettingsByType.itemCount, + weaponCrateCount: lootSettingsByType.weaponCrateCount, + itemBlacklist: [ + ...lootSettingsByType.itemBlacklist, + ...this.itemFilterService.getItemRewardBlacklist(), + ...this.itemFilterService.getBossItems(), + ], + itemTypeWhitelist: lootSettingsByType.itemTypeWhitelist, + itemLimits: lootSettingsByType.itemLimits, + itemStackLimits: lootSettingsByType.itemStackLimits, + armorLevelWhitelist: lootSettingsByType.armorLevelWhitelist, + allowBossItems: lootSettingsByType.allowBossItems, + }; + } +}