diff --git a/project/assets/configs/airdrop.json b/project/assets/configs/airdrop.json index 7615dd6d..b9544929 100644 --- a/project/assets/configs/airdrop.json +++ b/project/assets/configs/airdrop.json @@ -3,10 +3,12 @@ "mixed": 5, "weaponArmor": 2, "foodMedical": 1, - "barter": 1 + "barter": 1, + "radar": 0 }, "loot": { "mixed": { + "icon": "Common", "weaponPresetCount": { "min": 3, "max": 5 @@ -108,6 +110,7 @@ "allowBossItems": false }, "weaponArmor": { + "icon": "Weapon", "weaponPresetCount": { "min": 6, "max": 8 @@ -180,6 +183,7 @@ "allowBossItems": false }, "foodMedical": { + "icon": "Medical", "weaponPresetCount": { "min": 0, "max": 0 @@ -260,6 +264,7 @@ "allowBossItems": false }, "barter": { + "icon": "Supply", "weaponPresetCount": { "min": 0, "max": 0 @@ -339,6 +344,39 @@ }, "armorLevelWhitelist": [0], "allowBossItems": false - } - } + }, + "radar": { + "icon": "Supply", + "weaponPresetCount": { + "min": 0, + "max": 0 + }, + "armorPresetCount": { + "min": 0, + "max": 0 + }, + "itemCount": { + "min": 0, + "max": 0 + }, + "weaponCrateCount": { + "min": 0, + "max": 0 + }, + "itemBlacklist": [], + "itemTypeWhitelist": [], + "itemLimits": {}, + "itemStackLimits": {}, + "armorLevelWhitelist": [], + "allowBossItems": false, + "useForcedLoot": true, + "forcedLoot": { + "66d9f7256916142b3b02276e": {"min": 2, "max": 4 } + } + } + }, + "customAirdropMapping": { + "66da1b49099cf6adcc07a36b": "radar", + "66da1b546916142b3b022777": "radar" + } } diff --git a/project/src/callbacks/LocationCallbacks.ts b/project/src/callbacks/LocationCallbacks.ts index 140702c4..ecb3c1e5 100644 --- a/project/src/callbacks/LocationCallbacks.ts +++ b/project/src/callbacks/LocationCallbacks.ts @@ -2,6 +2,7 @@ import { LocationController } from "@spt/controllers/LocationController"; import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData"; import { ILocationsGenerateAllResponse } from "@spt/models/eft/common/ILocationsSourceDestinationBase"; import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData"; +import { IGetAirdropLootRequest } from "@spt/models/eft/location/IGetAirdropLootRequest"; import { IGetAirdropLootResponse } from "@spt/models/eft/location/IGetAirdropLootResponse"; import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil"; import { inject, injectable } from "tsyringe"; @@ -25,9 +26,9 @@ export class LocationCallbacks { /** Handle client/airdrop/loot */ public getAirdropLoot( url: string, - info: IEmptyRequestData, + info: IGetAirdropLootRequest, sessionID: string, ): IGetBodyResponseData { - return this.httpResponse.getBody(this.locationController.getAirdropLoot()); + return this.httpResponse.getBody(this.locationController.getAirdropLoot(info)); } } diff --git a/project/src/controllers/LocationController.ts b/project/src/controllers/LocationController.ts index a439dfb2..14bcbc38 100644 --- a/project/src/controllers/LocationController.ts +++ b/project/src/controllers/LocationController.ts @@ -1,4 +1,5 @@ import { ILocationsGenerateAllResponse } from "@spt/models/eft/common/ILocationsSourceDestinationBase"; +import { IGetAirdropLootRequest } from "@spt/models/eft/location/IGetAirdropLootRequest"; import { IGetAirdropLootResponse } from "@spt/models/eft/location/IGetAirdropLootResponse"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig"; @@ -50,7 +51,11 @@ export class LocationController { } /** Handle client/airdrop/loot */ - public getAirdropLoot(): IGetAirdropLootResponse { + public getAirdropLoot(request: IGetAirdropLootRequest): IGetAirdropLootResponse { + if (request.containerId) { + return this.airdropService.generateCustomAirdropLoot(request); + } + return this.airdropService.generateAirdropLoot(); } } diff --git a/project/src/generators/LootGenerator.ts b/project/src/generators/LootGenerator.ts index 6d7db900..e92392d2 100644 --- a/project/src/generators/LootGenerator.ts +++ b/project/src/generators/LootGenerator.ts @@ -153,6 +153,32 @@ export class LootGenerator { return result; } + public createForcedLoot(airdropConfig: LootRequest) { + const result: IItem[] = []; + + const forcedItems = Object.keys(airdropConfig.forcedLoot); + + for (const tpl of forcedItems) { + const details = airdropConfig.forcedLoot[tpl]; + const randomisedItemCount = this.randomUtil.getInt(details.min, details.max); + + // Add forced loot item to result + const newLootItem: IItem = { + _id: this.hashUtil.generate(), + _tpl: tpl, + upd: { + StackObjectsCount: randomisedItemCount, + SpawnedInSession: true, + }, + }; + + const splitResults = this.itemHelper.splitStack(newLootItem); + result.push(...splitResults); + } + + return result; + } + protected getItemRewardPool( itemTplBlacklist: string[], itemTypeWhitelist: string[], diff --git a/project/src/models/enums/AirdropType.ts b/project/src/models/enums/AirdropType.ts index a6963807..41ed6390 100644 --- a/project/src/models/enums/AirdropType.ts +++ b/project/src/models/enums/AirdropType.ts @@ -3,4 +3,5 @@ export enum AirdropTypeEnum { SUPPLY = "barter", FOOD_MEDICAL = "foodMedical", WEAPON_ARMOR = "weaponArmor", + RADAR = "radar", } diff --git a/project/src/models/spt/config/IAirdropConfig.ts b/project/src/models/spt/config/IAirdropConfig.ts index 94773cfe..a01eb2b1 100644 --- a/project/src/models/spt/config/IAirdropConfig.ts +++ b/project/src/models/spt/config/IAirdropConfig.ts @@ -7,6 +7,7 @@ export interface IAirdropConfig extends IBaseConfig { airdropTypeWeightings: Record; /** What rewards will the loot crate contain, keyed by drop type e.g. mixed/weaponArmor/foodMedical/barter */ loot: Record; + customAirdropMapping: Record; } /** Chance map will have an airdrop occur out of 100 - locations not included count as 0% */ @@ -23,6 +24,7 @@ export interface IAirdropChancePercent { /** Loot inside crate */ export interface IAirdropLoot { + icon: AirdropTypeEnum; /** Min/max of weapons inside crate */ weaponPresetCount?: MinMax; /** Min/max of armors (head/chest/rig) inside crate */ @@ -43,4 +45,6 @@ export interface IAirdropLoot { armorLevelWhitelist?: number[]; /** Should boss items be added to airdrop crate */ allowBossItems: boolean; + useForcedLoot?: boolean; + forcedLoot?: Record; } diff --git a/project/src/models/spt/services/LootRequest.ts b/project/src/models/spt/services/LootRequest.ts index 652b2d5e..f0d62ee7 100644 --- a/project/src/models/spt/services/LootRequest.ts +++ b/project/src/models/spt/services/LootRequest.ts @@ -1,4 +1,5 @@ import { MinMax } from "@spt/models/common/MinMax"; +import { AirdropTypeEnum } from "@spt/models/enums/AirdropType"; export interface LootRequest { weaponPresetCount: MinMax; @@ -13,4 +14,10 @@ export interface LootRequest { armorLevelWhitelist: number[]; allowBossItems: boolean; useRewarditemBlacklist?: boolean; + useForcedLoot?: boolean; + forcedLoot?: Record; +} + +export interface IAirdropLootRequest extends LootRequest { + icon?: AirdropTypeEnum; } diff --git a/project/src/services/AirdropService.ts b/project/src/services/AirdropService.ts index aa7ad2ea..cf7a1d3c 100644 --- a/project/src/services/AirdropService.ts +++ b/project/src/services/AirdropService.ts @@ -2,12 +2,13 @@ import { LootGenerator } from "@spt/generators/LootGenerator"; import { ItemHelper } from "@spt/helpers/ItemHelper"; import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper"; import { IItem } from "@spt/models/eft/common/tables/IItem"; +import { IGetAirdropLootRequest } from "@spt/models/eft/location/IGetAirdropLootRequest"; 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 { IAirdropConfig, IAirdropLoot } from "@spt/models/spt/config/IAirdropConfig"; -import { LootRequest } from "@spt/models/spt/services/LootRequest"; +import { IAirdropLootRequest, 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"; @@ -36,21 +37,36 @@ export class AirdropService { this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP); } + public generateCustomAirdropLoot(request: IGetAirdropLootRequest): IGetAirdropLootResponse { + const customAirdropInformation = this.airdropConfig.customAirdropMapping[request.containerId]; + if (!customAirdropInformation) { + this.logger.warning( + `Unable to find data for custom airdrop ${request.containerId}, returning random airdrop instead`, + ); + + return this.generateAirdropLoot(); + } + + return this.generateAirdropLoot(customAirdropInformation); + } + /** * 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(); + public generateAirdropLoot(forcedAirdropType = null): IGetAirdropLootResponse { + const airdropType = forcedAirdropType ? forcedAirdropType : 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); + const crateLoot = airdropConfig.useForcedLoot + ? this.lootGenerator.createForcedLoot(airdropConfig) + : this.lootGenerator.createRandomLoot(airdropConfig); // Create airdrop crate and add to result in first spot const airdropCrateItem = this.getAirdropCrateItem(airdropType); @@ -58,7 +74,7 @@ export class AirdropService { // Add crate to front of array crateLoot.unshift(airdropCrateItem); - // Reparent loot items to create we added above + // Reparent loot items to crate we added above for (const item of crateLoot) { if (item._id === airdropCrateItem._id) { // Crate itself, don't alter @@ -72,7 +88,7 @@ export class AirdropService { } } - return { icon: airdropType, container: crateLoot }; + return { icon: airdropConfig.icon, container: crateLoot }; } /** @@ -103,6 +119,9 @@ export class AirdropService { case AirdropTypeEnum.COMMON: airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_COMMON_SUPPLY_CRATE; break; + case AirdropTypeEnum.RADAR: + airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_TECHNICAL_SUPPLY_CRATE_EVENT_1; + break; default: airdropContainer._tpl = ItemTpl.LOOTCONTAINER_AIRDROP_COMMON_SUPPLY_CRATE; break; @@ -126,7 +145,7 @@ export class AirdropService { * @param airdropType Type of airdrop to get settings for * @returns LootRequest */ - protected getAirdropLootConfigByType(airdropType: AirdropTypeEnum): LootRequest { + protected getAirdropLootConfigByType(airdropType: AirdropTypeEnum): IAirdropLootRequest { let lootSettingsByType: IAirdropLoot = this.airdropConfig.loot[airdropType]; if (!lootSettingsByType) { this.logger.error( @@ -136,6 +155,7 @@ export class AirdropService { } return { + icon: lootSettingsByType.icon, weaponPresetCount: lootSettingsByType.weaponPresetCount, armorPresetCount: lootSettingsByType.armorPresetCount, itemCount: lootSettingsByType.itemCount, @@ -150,6 +170,8 @@ export class AirdropService { itemStackLimits: lootSettingsByType.itemStackLimits, armorLevelWhitelist: lootSettingsByType.armorLevelWhitelist, allowBossItems: lootSettingsByType.allowBossItems, + useForcedLoot: lootSettingsByType.useForcedLoot, + forcedLoot: lootSettingsByType.forcedLoot, }; } }