using System.Text.Json.Serialization; using Core.Annotations; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Inventory; using Core.Models.Eft.ItemEvent; using Core.Models.Spt.Config; using Core.Models.Spt.Inventory; using Core.Models.Utils; using Core.Services; namespace Core.Helpers; [Injectable] public class InventoryHelper { protected ISptLogger _logger; protected ProfileHelper _profileHelper; protected DialogueHelper _dialogueHelper; protected LocalisationService _localisationService; public InventoryHelper( ISptLogger logger, ProfileHelper profileHelper, DialogueHelper dialogueHelper, LocalisationService localisationService ) { _logger = logger; _profileHelper = profileHelper; _dialogueHelper = dialogueHelper; _localisationService = localisationService; } /// /// Add multiple items to player stash (assuming they all fit) /// /// Session id /// AddItemsDirectRequest request /// Player profile /// Client response object public void AddItemsToStash( string sessionId, AddItemsDirectRequest request, PmcData pmcData, ItemEventRouterResponse output) { throw new NotImplementedException(); } /// /// Add whatever is passed in request.itemWithModsToAdd into player inventory (if it fits) /// /// Session id /// AddItemDirect request /// Player profile /// Client response object public void AddItemToStash( string sessionId, AddItemDirectRequest request, PmcData pmcData, ItemEventRouterResponse output) { throw new NotImplementedException(); } /// /// Set FiR status for an item + its children /// /// An item /// Item was found in raid protected void SetFindInRaidStatusForItem(List itemWithChildren, bool foundInRaid) { throw new NotImplementedException(); } /// /// Remove properties from a Upd object used by a trader/ragfair that are unnecessary to a player /// /// Object to update protected void RemoveTraderRagfairRelatedUpdProperties(Upd upd) { throw new NotImplementedException(); } /// /// Can all provided items be added into player inventory /// /// Player id /// Array of items with children to try and fit /// True all items fit public bool CanPlaceItemsInInventory(string sessionId, List> itemsWithChildren) { throw new NotImplementedException(); } /// /// Do the provided items all fit into the grid /// /// Container grid to fit items into /// Items to try and fit into grid /// True all fit public bool CanPlaceItemsInContainer(List>? containerFS2D, List> itemsWithChildren) { throw new NotImplementedException(); } /// /// Does an item fit into a container grid /// /// Container grid /// Item to check fits /// True it fits public bool CanPlaceItemInContainer(List>? containerFS2D, List itemWithChildren) { throw new NotImplementedException(); } /// /// Find a free location inside a container to fit the item /// /// Container grid to add item to /// Item to add to grid /// Id of the container we're fitting item into /// Slot id value to use, default is "hideout" public void PlaceItemInContainer( List> containerFS2D, List itemWithChildren, string containerId, string desiredSlotId = "hideout") { throw new NotImplementedException(); } /// /// Find a location to place an item into inventory and place it /// /// 2-dimensional representation of the container slots /// 2-dimensional representation of the sorting table slots /// Item to place with children /// Players inventory /// Should sorting table to be used if main stash has no space /// Output to send back to client protected void PlaceItemInInventory( List> stashFS2D, List> sortingTableFS2D, List itemWithChildren, BotBaseInventory playerInventory, bool useSortingTable, ItemEventRouterResponse output) { throw new NotImplementedException(); } /// /// Handle Remove event /// Remove item from player inventory + insured items array /// Also deletes child items /// /// Profile to remove item from (pmc or scav) /// Items id to remove /// Session id /// OPTIONAL - ItemEventRouterResponse public void RemoveItem(PmcData profile, string itemId, string sessionId, ItemEventRouterResponse output = null) { throw new NotImplementedException(); } /// /// Delete desired item from a player profiles mail /// /// Session id /// Remove request /// OPTIONAL - ItemEventRouterResponse public void RemoveItemAndChildrenFromMailRewards(string sessionId, InventoryRemoveRequestData removeRequest, ItemEventRouterResponse output = null) { throw new NotImplementedException(); } /// /// Find item by id in player inventory and remove x of its count /// /// player profile /// Item id to decrement StackObjectsCount of /// Number of item to remove /// Session id /// ItemEventRouterResponse /// ItemEventRouterResponse public ItemEventRouterResponse RemoveItemByCount(PmcData pmcData, string itemId, int countToRemove, string sessionId, ItemEventRouterResponse output = null) { throw new NotImplementedException(); } /// /// Get the height and width of an item - can have children that alter size /// /// Item to get size of /// Items id to get size of /// /// [width, height] public List GetItemSize(string? itemTpl, string itemId, List inventoryItems) { throw new NotImplementedException(); } /// /// Calculates the size of an item including attachments /// takes into account if item is folded /// /// Items template id /// Items id /// Hashmap of inventory items /// An array representing the [width, height] of the item protected List GetSizeByInventoryItemHash(string itemTpl, string itemId, InventoryItemHash inventoryItemHash) { throw new NotImplementedException(); } /// /// Get a blank two-dimensional representation of a container /// /// Horizontal size of container /// Vertical size of container /// Two-dimensional representation of container protected List> GetBlankContainerMap(int containerH, int containerY) { throw new NotImplementedException(); } /// /// Get a 2d mapping of a container with what grid slots are filled /// /// Horizontal size of container /// Vertical size of container /// Players inventory items /// Id of the container /// Two-dimensional representation of container public List> GetContainerMap(double containerH, double containerV, List itemList, string containerId) { throw new NotImplementedException(); } protected bool IsVertical(ItemLocation itemLocation) { throw new NotImplementedException(); } protected InventoryItemHash GetInventoryItemHash(List inventoryItems) { throw new NotImplementedException(); } /// /// Return the inventory that needs to be modified (scav/pmc etc) /// Changes made to result apply to character inventory /// Based on the item action, determine whose inventories we should be looking at for from and to. /// /// Item interaction request /// Item being moved/split/etc to inventory /// Session id / players Id /// OwnerInventoryItems with inventory of player/scav to adjust public OwnerInventoryItems GetOwnerInventoryItems( InventoryBaseActionRequestData request, string item, string sessionId) { var pmcItems = _profileHelper.GetPmcProfile(sessionId).Inventory.Items; var scavProfile = _profileHelper.GetScavProfile(sessionId); var fromInventoryItems = pmcItems; var fromType = "pmc"; if (request.FromOwner is not null) { if (request.FromOwner.Id == scavProfile.Id) { fromInventoryItems = scavProfile.Inventory.Items; fromType = "scav"; } else if (request.FromOwner.Type.ToLower() == "mail") { // Split requests don't use 'use' but 'splitItem' property fromInventoryItems = _dialogueHelper.GetMessageItemContents(request.FromOwner.Id, sessionId, item); fromType = "mail"; } } // Don't need to worry about mail for destination because client doesn't allow // users to move items back into the mail stash. var toInventoryItems = pmcItems; var toType = "pmc"; // Destination is scav inventory, update values if (request.ToOwner?.Id == scavProfile.Id) { toInventoryItems = scavProfile.Inventory.Items; toType = "scav"; } // From and To types match, same inventory var movingToSameInventory = fromType == toType; return new OwnerInventoryItems{ From = fromInventoryItems, To = toInventoryItems, SameInventory = movingToSameInventory, IsMail = fromType == "mail", }; } /// /// Get a two-dimensional array to represent stash slots /// 0 value = free, 1 = taken /// /// Player profile /// session id /// 2-dimensional array protected int[,] GetStashSlotMap(PmcData pmcData, string sessionID) { throw new NotImplementedException(); } /// /// Get a blank two-dimensional array representation of a container /// /// Container to get data for /// blank two-dimensional array public List> GetContainerSlotMap(string containerTpl) { throw new NotImplementedException(); } /// /// Get a two-dimensional array representation of the players sorting table /// /// Player profile /// two-dimensional array protected List> GetSortingTableSlotMap(PmcData pmcData) { throw new NotImplementedException(); } /// /// Get Players Stash Size /// /// Players id /// Dictionary of 2 values, horizontal and vertical stash size protected Dictionary GetPlayerStashSize(string sessionID) { throw new NotImplementedException(); } /// /// Get the players stash items tpl /// /// Player id /// Stash tpl protected string GetStashType(string sessionID) { throw new NotImplementedException(); } /// /// Internal helper function to transfer an item + children from one profile to another. /// /// Inventory of the source (can be non-player) /// Inventory of the destination /// Move request public void MoveItemToProfile(List sourceItems, List toItems, InventoryMoveRequestData request) { throw new NotImplementedException(); } /// /// Internal helper function to move item within the same profile. /// /// profile to edit /// /// client move request /// /// True if move was successful public bool MoveItemInternal( PmcData pmcData, List inventoryItems, InventoryMoveRequestData moveRequest, out string errorMessage) { errorMessage = string.Empty; HandleCartridges(inventoryItems, moveRequest); // Find item we want to 'move' var matchingInventoryItem = inventoryItems.FirstOrDefault((item) => item.Id == moveRequest.Item); if (matchingInventoryItem is null) { var noMatchingItemMesage = $"Unable to move item: { moveRequest.Item}, cannot find in inventory"; _logger.Error(noMatchingItemMesage); errorMessage = noMatchingItemMesage; return false; } _logger.Debug("${ moveRequest.Action} item: ${ moveRequest.item} from slotid: ${ matchingInventoryItem.slotId} to container: ${ moveRequest.to.container}"); // Don't move shells from camora to cartridges (happens when loading shells into mts-255 revolver shotgun) if (matchingInventoryItem.SlotId?.Contains("camora_") is null && moveRequest.To.Container == "cartridges") { _logger.Warning( _localisationService.GetText("inventory-invalid_move_to_container", new { slotId = matchingInventoryItem.SlotId, container = moveRequest.To.Container, })); return true; } // Edit items details to match its new location matchingInventoryItem.ParentId = moveRequest.To.Id; matchingInventoryItem.SlotId = moveRequest.To.Container; // Ensure fastpanel dict updates when item was moved out of fast-panel-accessible slot UpdateFastPanelBinding(pmcData, matchingInventoryItem); // Item has location property, ensure its value is handled if (moveRequest.To.Location is not null) { matchingInventoryItem.Location = moveRequest.To.Location; } else { // Moved from slot with location to one without, clean up if (matchingInventoryItem.Location is not null) { matchingInventoryItem.Location = null; } } return true; } /// /// Update fast panel bindings when an item is moved into a container that doesn't allow quick slot access /// /// Player profile /// item being moved protected void UpdateFastPanelBinding(PmcData pmcData, Item itemBeingMoved) { // Find matching _id in fast panel if (!pmcData.Inventory.FastPanel.TryGetValue(itemBeingMoved.Id, out var fastPanelSlot)) { return; } // Get moved items parent (should be container item was put into) var itemParent = pmcData.Inventory.Items.FirstOrDefault((item) => item.Id == itemBeingMoved.ParentId); if (itemParent is null) { return; } // Reset fast panel value if item was moved to a container other than pocket/rig (cant be used from fastpanel) List slots = ["pockets", "tacticalvest"]; var wasMovedToFastPanelAccessibleContainer = slots.Contains( itemParent?.SlotId?.ToLower() ?? ""); if (!wasMovedToFastPanelAccessibleContainer) { pmcData.Inventory.FastPanel[fastPanelSlot[0].ToString()] = ""; } } /// /// Internal helper function to handle cartridges in inventory if any of them exist. /// protected void HandleCartridges(List items, InventoryMoveRequestData request) { throw new NotImplementedException(); } /// /// Get details for how a random loot container should be handled, max rewards, possible reward tpls /// /// Container being opened /// Reward details public RewardDetails GetRandomLootContainerRewardDetails(string itemTpl) { throw new NotImplementedException(); } /// /// Get inventory configuration /// /// Inventory configuration public InventoryConfig GetInventoryConfig() { throw new NotImplementedException(); } /// /// Recursively checks if the given item is /// inside the stash, that is it has the stash as /// ancestor with slotId=hideout /// /// Player profile /// Item to look for /// True if item exists inside stash public bool IsItemInStash(PmcData pmcData, Item itemToCheck) { throw new NotImplementedException(); } public void ValidateInventoryUsesMongoIds(List itemsToValidate) { throw new NotImplementedException(); } /// /// Does the provided item have a root item with the provided id /// /// Profile with items /// Item to check /// Root item id to check for /// True when item has rootId, false when not public bool DoesItemHaveRootId(PmcData pmcData, Item item, string rootId) { throw new NotImplementedException(); } } public class InventoryItemHash { [JsonPropertyName("byItemId")] public Dictionary ByItemId { get; set; } [JsonPropertyName("byParentId")] public Dictionary> ByParentId { get; set; } }