diff --git a/project/src/controllers/InventoryController.ts b/project/src/controllers/InventoryController.ts index f5f7c9a0..d207d25f 100644 --- a/project/src/controllers/InventoryController.ts +++ b/project/src/controllers/InventoryController.ts @@ -10,6 +10,7 @@ import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { QuestHelper } from "@spt-aki/helpers/QuestHelper"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; +import { IAddItemDirectRequest } from "@spt-aki/models/eft/inventory/IAddItemDirectRequest"; import { IAddItemRequestData } from "@spt-aki/models/eft/inventory/IAddItemRequestData"; import { IInventoryBindRequestData } from "@spt-aki/models/eft/inventory/IInventoryBindRequestData"; import { IInventoryCreateMarkerRequestData } from "@spt-aki/models/eft/inventory/IInventoryCreateMarkerRequestData"; @@ -914,36 +915,50 @@ export class InventoryController sessionID: string, ): IItemEventRouterResponse { + const output = this.eventOutputHolder.getOutput(sessionID); + + /** Container player opened in their inventory */ const openedItem = pmcData.Inventory.items.find((x) => x._id === body.item); - const containerDetails = this.itemHelper.getItem(openedItem._tpl); - const isSealedWeaponBox = containerDetails[1]._name.includes("event_container_airdrop"); + const containerDetailsDb = this.itemHelper.getItem(openedItem._tpl); + const isSealedWeaponBox = containerDetailsDb[1]._name.includes("event_container_airdrop"); - const newItemRequest: IAddItemRequestData = { tid: "RandomLootContainer", items: [] }; - - let foundInRaid = false; + let foundInRaid = openedItem.upd?.SpawnedInSession; + const rewards: Item[][] = []; if (isSealedWeaponBox) { const containerSettings = this.inventoryHelper.getInventoryConfig().sealedAirdropContainer; - newItemRequest.items.push(...this.lootGenerator.getSealedWeaponCaseLoot(containerSettings)); + rewards.push(...this.lootGenerator.getSealedWeaponCaseLoot(containerSettings)); - foundInRaid = containerSettings.foundInRaid; + if (containerSettings.foundInRaid) + { + foundInRaid = containerSettings.foundInRaid + } } else { - // Get summary of loot from config const rewardContainerDetails = this.inventoryHelper.getRandomLootContainerRewardDetails(openedItem._tpl); - newItemRequest.items.push(...this.lootGenerator.getRandomLootContainerLoot(rewardContainerDetails)); + rewards.push(...this.lootGenerator.getRandomLootContainerLoot(rewardContainerDetails)); - foundInRaid = rewardContainerDetails.foundInRaid; + if (rewardContainerDetails.foundInRaid) + { + foundInRaid = rewardContainerDetails.foundInRaid; + } } - const output = this.eventOutputHolder.getOutput(sessionID); - // Find and delete opened item from player inventory this.inventoryHelper.removeItem(pmcData, body.item, sessionID, output); // Add reward items to player inventory - this.inventoryHelper.addItem(pmcData, newItemRequest, output, sessionID, null, foundInRaid, null, true); + for (const itemWithChildrenToAdd of rewards) + { + const request: IAddItemDirectRequest = { + itemWithModsToAdd: itemWithChildrenToAdd, + foundInRaid: foundInRaid, + callback: null, + useSortingTable: true + }; + this.inventoryHelper.addItemToStash(sessionID, request, pmcData, output); + } return output; } diff --git a/project/src/generators/LootGenerator.ts b/project/src/generators/LootGenerator.ts index 204e48fa..20862cce 100644 --- a/project/src/generators/LootGenerator.ts +++ b/project/src/generators/LootGenerator.ts @@ -5,6 +5,7 @@ import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { PresetHelper } from "@spt-aki/helpers/PresetHelper"; import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; import { IPreset } from "@spt-aki/models/eft/common/IGlobals"; +import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { AddItem } from "@spt-aki/models/eft/inventory/IAddItemRequestData"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; @@ -17,6 +18,7 @@ import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { RagfairLinkedItemService } from "@spt-aki/services/RagfairLinkedItemService"; import { HashUtil } from "@spt-aki/utils/HashUtil"; +import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; type ItemLimit = { current: number; max: number; }; @@ -29,6 +31,7 @@ export class LootGenerator @inject("HashUtil") protected hashUtil: HashUtil, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("RandomUtil") protected randomUtil: RandomUtil, + @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper, @@ -335,11 +338,11 @@ export class LootGenerator /** * Sealed weapon containers have a weapon + associated mods inside them + assortment of other things (food/meds) * @param containerSettings sealed weapon container settings - * @returns Array of items to add to player inventory + * @returns Array of item with children arrays */ - public getSealedWeaponCaseLoot(containerSettings: ISealedAirdropContainerSettings): AddItem[] + public getSealedWeaponCaseLoot(containerSettings: ISealedAirdropContainerSettings): Item[][] { - const itemsToReturn: AddItem[] = []; + const itemsToReturn: Item[][] = []; // choose a weapon to give to the player (weighted) const chosenWeaponTpl = this.weightedRandomHelper.getWeightedValue( @@ -356,7 +359,7 @@ export class LootGenerator } // Get weapon preset - default or choose a random one from all possible - let chosenWeaponPreset = containerSettings.defaultPresetsOnly + let chosenWeaponPreset = (containerSettings.defaultPresetsOnly) ? this.presetHelper.getDefaultPreset(chosenWeaponTpl) : this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl)); @@ -366,8 +369,14 @@ export class LootGenerator chosenWeaponPreset = this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl)); } + const presetAndMods: Item[] = this.itemHelper.replaceIDs( + null, + this.jsonUtil.clone(chosenWeaponPreset._items), + ); + this.itemHelper.remapRootItemId(presetAndMods); + // Add preset to return object - itemsToReturn.push({ count: 1, item_id: chosenWeaponPreset._id, sptIsPreset: true }); + itemsToReturn.push(presetAndMods); // Get items related to chosen weapon const linkedItemsToWeapon = this.ragfairLinkedItemService.getLinkedDbItems(chosenWeaponTpl); @@ -385,14 +394,14 @@ export class LootGenerator * Get non-weapon mod rewards for a sealed container * @param containerSettings Sealed weapon container settings * @param weaponDetailsDb Details for the weapon to reward player - * @returns AddItem array + * @returns Array of item with children arrays */ protected getSealedContainerNonWeaponModRewards( containerSettings: ISealedAirdropContainerSettings, weaponDetailsDb: ITemplateItem, - ): AddItem[] + ): Item[][] { - const rewards: AddItem[] = []; + const rewards: Item[][] = []; for (const rewardTypeId in containerSettings.rewardTypeLimits) { @@ -408,9 +417,9 @@ export class LootGenerator if (rewardTypeId === BaseClasses.AMMO_BOX) { // Get ammoboxes from db - const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map((x) => + const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map((tpl) => { - const itemDetails = this.itemHelper.getItem(x); + const itemDetails = this.itemHelper.getItem(tpl); return itemDetails[1]; }); @@ -424,9 +433,18 @@ export class LootGenerator continue; } - // No need to add ammo to box, inventoryHelper.addItem() will handle it - const chosenAmmoBox = this.randomUtil.getArrayValue(ammoBoxesMatchingCaliber); - rewards.push({ count: rewardCount, item_id: chosenAmmoBox._id, sptIsPreset: false }); + for (let index = 0; index < rewardCount; index++) + { + const chosenAmmoBox = this.randomUtil.getArrayValue(ammoBoxesMatchingCaliber); + const ammoBoxItem: Item[] = [ + { + _id: this.hashUtil.generate(), + _tpl: chosenAmmoBox._id + } + ] + this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, chosenAmmoBox); + rewards.push(ammoBoxItem); + } continue; } @@ -449,9 +467,16 @@ export class LootGenerator for (let index = 0; index < rewardCount; index++) { - // choose a random item from pool + // Choose a random item from pool const chosenRewardItem = this.randomUtil.getArrayValue(rewardItemPool); - this.addOrIncrementItemToArray(chosenRewardItem._id, rewards); + const rewardItem: Item[] = [ + { + _id: this.hashUtil.generate(), + _tpl: chosenRewardItem._id + } + ] + + rewards.push(rewardItem) } } @@ -463,15 +488,15 @@ export class LootGenerator * @param containerSettings Sealed weapon container settings * @param linkedItemsToWeapon All items that can be attached/inserted into weapon * @param chosenWeaponPreset The weapon preset given to player as reward - * @returns AddItem array + * @returns Array of item with children arrays */ protected getSealedContainerWeaponModRewards( containerSettings: ISealedAirdropContainerSettings, linkedItemsToWeapon: ITemplateItem[], chosenWeaponPreset: IPreset, - ): AddItem[] + ): Item[][] { - const modRewards: AddItem[] = []; + const modRewards: Item[][] = []; for (const rewardTypeId in containerSettings.weaponModRewardLimits) { const settings = containerSettings.weaponModRewardLimits[rewardTypeId]; @@ -499,7 +524,12 @@ export class LootGenerator for (let index = 0; index < rewardCount; index++) { const chosenItem = this.randomUtil.drawRandomFromList(relatedItems); - this.addOrIncrementItemToArray(chosenItem[0]._id, modRewards); + const item: Item[] = [{ + _id: this.hashUtil.generate(), + _tpl: chosenItem[0]._id + }]; + + modRewards.push(item) } } @@ -509,11 +539,11 @@ export class LootGenerator /** * Handle event-related loot containers - currently just the halloween jack-o-lanterns that give food rewards * @param rewardContainerDetails - * @returns AddItem array + * @returns Array of item with children arrays */ - public getRandomLootContainerLoot(rewardContainerDetails: RewardDetails): AddItem[] + public getRandomLootContainerLoot(rewardContainerDetails: RewardDetails): Item[][] { - const itemsToReturn: AddItem[] = []; + const itemsToReturn: Item[][] = []; // Get random items and add to newItemRequest for (let index = 0; index < rewardContainerDetails.rewardCount; index++) @@ -522,29 +552,15 @@ export class LootGenerator const chosenRewardItemTpl = this.weightedRandomHelper.getWeightedValue( rewardContainerDetails.rewardTplPool, ); - this.addOrIncrementItemToArray(chosenRewardItemTpl, itemsToReturn); + const rewardItem: Item[] = [ + { + _id: this.hashUtil.generate(), + _tpl: chosenRewardItemTpl + } + ] + itemsToReturn.push(rewardItem) } return itemsToReturn; } - - /** - * A bug in inventoryHelper.addItem() means you cannot add the same item to the array twice with a count of 1, it causes duplication - * Default adds 1, or increments count - * @param itemTplToAdd items tpl we want to add to array - * @param resultsArray Array to add item tpl to - */ - protected addOrIncrementItemToArray(itemTplToAdd: string, resultsArray: AddItem[]): void - { - const existingItemIndex = resultsArray.findIndex((x) => x.item_id === itemTplToAdd); - if (existingItemIndex > -1) - { - // Exists in array already, increment count - resultsArray[existingItemIndex].count++; - } - else - { - resultsArray.push({ item_id: itemTplToAdd, count: 1, sptIsPreset: false }); - } - } }