diff --git a/Libraries/Core/Callbacks/InventoryCallbacks.cs b/Libraries/Core/Callbacks/InventoryCallbacks.cs
index 6b70468f..cd5eb169 100644
--- a/Libraries/Core/Callbacks/InventoryCallbacks.cs
+++ b/Libraries/Core/Callbacks/InventoryCallbacks.cs
@@ -1,9 +1,10 @@
-using SptCommon.Annotations;
using Core.Controllers;
+using Core.Helpers;
using Core.Models.Eft.Common;
using Core.Models.Eft.Inventory;
using Core.Models.Eft.ItemEvent;
using Core.Models.Eft.Quests;
+using SptCommon.Annotations;
namespace Core.Callbacks;
@@ -14,77 +15,80 @@ public class InventoryCallbacks(
)
{
///
- /// Handle client/game/profile/items/moving Move event
+ /// Handle client/game/profile/items/moving Move event
///
///
///
///
///
///
- public ItemEventRouterResponse MoveItem(PmcData pmcData, InventoryMoveRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse MoveItem(PmcData pmcData, InventoryMoveRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.MoveItem(pmcData, info, sessionID, output);
return output;
}
///
- /// Handle Remove event
+ /// Handle Remove event
///
///
///
///
///
///
- public ItemEventRouterResponse RemoveItem(PmcData pmcData, InventoryRemoveRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse RemoveItem(PmcData pmcData, InventoryRemoveRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.RemoveItem(pmcData, info, sessionID, output);
return output;
}
///
- /// Handle Split event
+ /// Handle Split event
///
///
///
///
///
///
- public ItemEventRouterResponse SplitItem(PmcData pmcData, InventorySplitRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse SplitItem(PmcData pmcData, InventorySplitRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.SplitItem(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse MergeItem(PmcData pmcData, InventoryMergeRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse MergeItem(PmcData pmcData, InventoryMergeRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.MergeItem(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse TransferItem(PmcData pmcData, InventoryTransferRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse TransferItem(PmcData pmcData, InventoryTransferRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.TransferItem(pmcData, info, sessionID, output);
return output;
}
///
- /// Handle Swap
+ /// Handle Swap
///
///
///
@@ -96,7 +100,6 @@ public class InventoryCallbacks(
}
///
- ///
///
///
///
@@ -108,7 +111,6 @@ public class InventoryCallbacks(
}
///
- ///
///
///
///
@@ -120,136 +122,138 @@ public class InventoryCallbacks(
}
///
- ///
///
///
- ///
- ///
+ ///
+ ///
///
- public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData info, string sessionID)
+ public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData request, string sessionId)
{
- return _inventoryController.TagItem(pmcData, info, sessionID);
+ return _inventoryController.TagItem(pmcData, request, sessionId);
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse BindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse BindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.BindItem(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse UnBindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse UnBindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.UnBindItem(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse ExamineItem(PmcData pmcData, InventoryExamineRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse ExamineItem(PmcData pmcData, InventoryExamineRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.ExamineItem(pmcData, info, sessionID, output);
return output;
}
///
- /// Handle ReadEncyclopedia
+ /// Handle ReadEncyclopedia
///
///
///
///
///
- public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData info, string sessionID)
+ public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData info,
+ string sessionID)
{
return _inventoryController.ReadEncyclopedia(pmcData, info, sessionID);
}
///
- /// Handle ApplyInventoryChanges
+ /// Handle ApplyInventoryChanges
///
///
///
///
///
///
- public ItemEventRouterResponse SortInventory(PmcData pmcData, InventorySortRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse SortInventory(PmcData pmcData, InventorySortRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.SortInventory(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData info,
+ string sessionID, ItemEventRouterResponse output)
{
_inventoryController.CreateMapMarker(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData info,
+ string sessionID, ItemEventRouterResponse output)
{
_inventoryController.DeleteMapMarker(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.EditMapMarker(pmcData, info, sessionID, output);
return output;
}
///
- /// Handle OpenRandomLootContainer
+ /// Handle OpenRandomLootContainer
///
///
///
///
///
///
- public ItemEventRouterResponse OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData info, string sessionID,
+ public ItemEventRouterResponse OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData info,
+ string sessionID,
ItemEventRouterResponse output)
{
_inventoryController.OpenRandomLootContainer(pmcData, info, sessionID, output);
@@ -257,57 +261,58 @@ public class InventoryCallbacks(
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.RedeemProfileReward(pmcData, info, sessionID);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse SetFavoriteItem(PmcData pmcData, SetFavoriteItems info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse SetFavoriteItem(PmcData pmcData, SetFavoriteItems info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.SetFavoriteItem(pmcData, info, sessionID);
return output;
}
///
- /// TODO: MOVE INTO QUEST CODE
- /// Handle game/profile/items/moving - QuestFail
+ /// TODO: MOVE INTO QUEST CODE
+ /// Handle game/profile/items/moving - QuestFail
///
///
///
///
///
///
- public ItemEventRouterResponse FailQuest(PmcData pmcData, FailQuestRequestData info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse FailQuest(PmcData pmcData, FailQuestRequestData info, string sessionID,
+ ItemEventRouterResponse output)
{
_questController.FailQuest(pmcData, info, sessionID, output);
return output;
}
///
- ///
///
///
///
///
///
///
- public ItemEventRouterResponse PinOrLock(PmcData pmcData, PinOrLockItemRequest info, string sessionID, ItemEventRouterResponse output)
+ public ItemEventRouterResponse PinOrLock(PmcData pmcData, PinOrLockItemRequest info, string sessionID,
+ ItemEventRouterResponse output)
{
_inventoryController.PinOrLock(pmcData, info, sessionID, output);
return output;
diff --git a/Libraries/Core/Controllers/InventoryController.cs b/Libraries/Core/Controllers/InventoryController.cs
index 9fa52cbe..62808b17 100644
--- a/Libraries/Core/Controllers/InventoryController.cs
+++ b/Libraries/Core/Controllers/InventoryController.cs
@@ -2,15 +2,18 @@ using SptCommon.Annotations;
using Core.Generators;
using Core.Helpers;
using Core.Models.Eft.Common;
+using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.Inventory;
using Core.Models.Eft.ItemEvent;
-using Core.Models.Eft.Quests;
using Core.Models.Enums;
using Core.Models.Utils;
using Core.Routers;
using Core.Services;
using Core.Utils;
using Core.Utils.Cloners;
+using Core.Models.Eft.Profile;
+using Core.Models.Spt.Dialog;
+using Product = Core.Models.Eft.ItemEvent.Product;
namespace Core.Controllers;
@@ -27,6 +30,7 @@ public class InventoryController(
ProfileHelper _profileHelper,
PaymentHelper _paymentHelper,
TraderHelper _traderHelper,
+ ItemHelper _itemHelper,
DatabaseService _databaseService,
FenceService _fenceService,
RagfairOfferService _ragfairOfferService,
@@ -38,15 +42,13 @@ public class InventoryController(
ICloner _cloner
)
{
- public void MoveItem(PmcData pmcData, InventoryMoveRequestData moveRequest, string sessionID, ItemEventRouterResponse output)
+ public void MoveItem(PmcData pmcData, InventoryMoveRequestData moveRequest, string sessionId,
+ ItemEventRouterResponse output)
{
- if (output.Warnings?.Count > 0)
- {
- return;
- }
+ if (output.Warnings?.Count > 0) return;
// Changes made to result apply to character inventory
- var ownerInventoryItems = _inventoryHelper.GetOwnerInventoryItems(moveRequest, moveRequest.Item, sessionID);
+ var ownerInventoryItems = _inventoryHelper.GetOwnerInventoryItems(moveRequest, moveRequest.Item, sessionId);
if (ownerInventoryItems.SameInventory.GetValueOrDefault(false))
{
// Don't move items from trader to profile, this can happen when editing a traders preset weapons
@@ -57,7 +59,7 @@ public class InventoryController(
}
// Check for item in inventory before allowing internal transfer
- var originalItemLocation = ownerInventoryItems.From?.FirstOrDefault((item) => item.Id == moveRequest.Item);
+ var originalItemLocation = ownerInventoryItems.From?.FirstOrDefault(item => item.Id == moveRequest.Item);
if (originalItemLocation is null)
{
// Internal item move but item never existed, possible dupe glitch
@@ -67,22 +69,30 @@ public class InventoryController(
var originalLocationSlotId = originalItemLocation.SlotId;
- var moveResult = _inventoryHelper.MoveItemInternal(pmcData, ownerInventoryItems.From ?? [], moveRequest, out var errorMessage);
+ var moveResult = _inventoryHelper.MoveItemInternal(
+ pmcData,
+ ownerInventoryItems.From ?? [],
+ moveRequest,
+ out var errorMessage
+ );
if (!moveResult)
{
_httpResponseUtil.AppendErrorToOutput(output, errorMessage);
return;
}
- // Item is moving into or out of place of fame dogtag slot
- if (moveRequest.To?.Container != null && (moveRequest.To.Container.StartsWith("dogtag") || originalLocationSlotId!.StartsWith("dogtag")))
- {
+ // Item is moving into or out of place of fame dog tag slot
+ if (moveRequest.To?.Container != null &&
+ (moveRequest.To.Container.StartsWith("dogtag") || originalLocationSlotId.StartsWith("dogtag")))
_hideoutHelper.ApplyPlaceOfFameDogtagBonus(pmcData);
- }
}
else
{
- _inventoryHelper.MoveItemToProfile(ownerInventoryItems.From ?? [], ownerInventoryItems.To ?? [], moveRequest);
+ _inventoryHelper.MoveItemToProfile(
+ ownerInventoryItems.From ?? [],
+ ownerInventoryItems.To ?? [],
+ moveRequest
+ );
}
}
@@ -95,103 +105,670 @@ public class InventoryController(
);
}
- public void PinOrLock(PmcData pmcData, PinOrLockItemRequest info, string sessionId, ItemEventRouterResponse output)
+ public void PinOrLock(PmcData pmcData, PinOrLockItemRequest request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ var itemToAdjust = pmcData.Inventory!.Items!.FirstOrDefault(item => item.Id == request.Item);
+ if (itemToAdjust is null)
+ {
+ _logger.Error($"Unable find item: {request.Item} to: {request.State} on player {sessionId}to: ");
+
+ return;
+ }
+
+ // Nullguard
+ itemToAdjust.Upd ??= new Upd();
+
+ itemToAdjust.Upd.PinLockState = request.State;
}
- public void SetFavoriteItem(PmcData pmcData, SetFavoriteItems info, string sessionId)
+ public void SetFavoriteItem(PmcData pmcData, SetFavoriteItems request, string sessionId)
{
- throw new NotImplementedException();
+ // The client sends the full list of favorite items, so clear the current favorites
+ pmcData.Inventory.FavoriteItems = [];
+ pmcData.Inventory.FavoriteItems.AddRange(request.Items);
}
- public void RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData info, string sessionId)
+ public void RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData request, string sessionId)
{
- throw new NotImplementedException();
+ var fullProfile = _profileHelper.GetFullProfile(sessionId);
+ foreach (var rewardEvent in request.Events)
+ {
+ // Hard coded to `SYSTEM` for now
+ // TODO: make this dynamic
+ var dialog = fullProfile.DialogueRecords["59e7125688a45068a6249071"];
+ var mail = dialog.Messages.FirstOrDefault(message => message.Id == rewardEvent.MessageId);
+ var mailEvent =
+ mail.ProfileChangeEvents.FirstOrDefault(changeEvent => changeEvent.Id == rewardEvent.EventId);
+
+ switch (mailEvent.Type)
+ {
+ case ProfileChangeEventType.TraderSalesSum:
+ pmcData.TradersInfo[mailEvent.Entity].SalesSum = mailEvent.Value;
+ _traderHelper.LevelUp(mailEvent.Entity, pmcData);
+ _logger.Success($"Set trader {mailEvent.Entity}: Sales Sum to: {mailEvent.Value}");
+ break;
+ case ProfileChangeEventType.TraderStanding:
+ pmcData.TradersInfo[mailEvent.Entity].Standing = mailEvent.Value;
+ _traderHelper.LevelUp(mailEvent.Entity, pmcData);
+ _logger.Success($"Set trader {mailEvent.Entity}: Standing to: {mailEvent.Value}");
+ break;
+ case ProfileChangeEventType.ProfileLevel:
+ pmcData.Info.Experience = mailEvent.Value.Value;
+ // Will calculate level below
+ _traderHelper.ValidateTraderStandingsAndPlayerLevelForProfile(sessionId);
+ _logger.Success($"Set profile xp to: {mailEvent.Value}");
+ break;
+ case ProfileChangeEventType.SkillPoints:
+ {
+ var profileSkill = pmcData.Skills.Common.FirstOrDefault(x => x.Id == mailEvent.Entity);
+ if (profileSkill is null)
+ {
+ _logger.Warning($"Unable to find skill with name: {mailEvent.Entity}");
+ continue;
+ }
+
+ profileSkill.Progress = mailEvent.Value;
+ _logger.Success($"Set profile skill: {mailEvent.Entity} to: {mailEvent.Value}");
+ break;
+ }
+ case ProfileChangeEventType.ExamineAllItems:
+ {
+ var itemsToInspect = _itemHelper.GetItems().Where(x => x.Type != "Node");
+ FlagItemsAsInspectedAndRewardXp(itemsToInspect.Select(x => x.Id), fullProfile);
+ _logger.Success($"Flagged {itemsToInspect.Count()} items as examined");
+
+ break;
+ }
+ case ProfileChangeEventType.UnlockTrader:
+ pmcData.TradersInfo[mailEvent.Entity].Unlocked = true;
+ _logger.Success($"Trader {mailEvent.Entity} Unlocked");
+
+ break;
+ case ProfileChangeEventType.AssortmentUnlockRule:
+ fullProfile.SptData.BlacklistedItemTemplates ??= [];
+ fullProfile.SptData.BlacklistedItemTemplates.Add(mailEvent.Entity);
+ _logger.Success($"Item {mailEvent.Entity} is now blacklisted");
+
+ break;
+ case ProfileChangeEventType.HideoutAreaLevel:
+ {
+ var areaName = mailEvent.Entity;
+ var newValue = mailEvent.Value;
+ var hideoutAreaType = Enum.Parse(areaName ?? "NOTSET");
+
+ var desiredArea = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == hideoutAreaType);
+ if (desiredArea is not null) desiredArea.Level = newValue;
+
+ break;
+ }
+ default:
+ _logger.Warning($"Unhandled profile reward event: {mailEvent.Type}");
+
+ break;
+ }
+ }
}
- public void OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData info, string sessionId, ItemEventRouterResponse output)
+ /**
+ * Flag an item as seen in profiles encyclopedia + add inspect xp to profile
+ * @param itemTpls Inspected item tpls
+ * @param fullProfile Profile to add xp to
+ */
+ protected void FlagItemsAsInspectedAndRewardXp(IEnumerable itemTpls, SptProfile fullProfile)
{
- throw new NotImplementedException();
+ foreach (var itemTpl in itemTpls)
+ {
+ var item = _itemHelper.GetItem(itemTpl);
+ if (!item.Key)
+ {
+ _logger.Warning(
+ _localisationService.GetText("inventory-unable_to_inspect_item_not_in_db", itemTpl)
+ );
+
+ return;
+ }
+
+ fullProfile.CharacterData.PmcData.Info.Experience += item.Value.Properties.ExamineExperience;
+ fullProfile.CharacterData.PmcData.Encyclopedia[itemTpl] = false;
+
+ fullProfile.CharacterData.ScavData.Info.Experience += item.Value.Properties.ExamineExperience;
+ fullProfile.CharacterData.ScavData.Encyclopedia[itemTpl] = false;
+ }
+
+ // TODO: update this with correct calculation using values from globals json
+ _profileHelper.AddSkillPointsToPlayer(
+ fullProfile.CharacterData.PmcData,
+ SkillTypes.Intellect,
+ 0.05 * itemTpls.Count()
+ );
}
- public void EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData info, string sessionId, ItemEventRouterResponse output)
+ public void OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ /** Container player opened in their inventory */
+ var openedItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
+ var containerDetailsDb = _itemHelper.GetItem(openedItem.Template);
+ var isSealedWeaponBox = containerDetailsDb.Value.Name.Contains("event_container_airdrop");
+
+ var foundInRaid = openedItem.Upd?.SpawnedInSession;
+ var rewards = new List>();
+ var unlockedWeaponCrates = new List
+ {
+ "665829424de4820934746ce6",
+ "665732e7ac60f009f270d1ef",
+ "665888282c4a1b73af576b77"
+ };
+ // Temp fix for unlocked weapon crate hideout craft
+ if (isSealedWeaponBox || unlockedWeaponCrates.Contains(containerDetailsDb.Value.Id))
+ {
+ var containerSettings = _inventoryHelper.GetInventoryConfig().SealedAirdropContainer;
+ rewards.AddRange(_lootGenerator.GetSealedWeaponCaseLoot(containerSettings));
+
+ if (containerSettings.FoundInRaid) foundInRaid = containerSettings.FoundInRaid;
+ }
+ else
+ {
+ var rewardContainerDetails = _inventoryHelper.GetRandomLootContainerRewardDetails(openedItem.Template);
+ if (rewardContainerDetails?.RewardCount == null)
+ {
+ _logger.Error($"Unable to add loot to container: {openedItem.Template}, no rewards found");
+ }
+ else
+ {
+ rewards.AddRange(_lootGenerator.GetRandomLootContainerLoot(rewardContainerDetails));
+
+ if (rewardContainerDetails.FoundInRaid) foundInRaid = rewardContainerDetails.FoundInRaid;
+ }
+ }
+
+ // Add items to player inventory
+ if (rewards.Count > 0)
+ {
+ var addItemsRequest = new AddItemsDirectRequest
+ {
+ ItemsWithModsToAdd = rewards,
+ FoundInRaid = foundInRaid,
+ Callback = null,
+ UseSortingTable = true
+ };
+ _inventoryHelper.AddItemsToStash(sessionId, addItemsRequest, pmcData, output);
+ if (output.Warnings.Count > 0) return;
+ }
+
+ // Find and delete opened container item from player inventory
+ _inventoryHelper.RemoveItem(pmcData, request.Item, sessionId, output);
}
- public void DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData info, string sessionId, ItemEventRouterResponse output)
+ public void EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ var mapItem = _mapMarkerService.EditMarkerOnMap(pmcData, request);
+
+ // sync with client
+ output.ProfileChanges[sessionId].Items.ChangedItems.Add(mapItem.ConvertToProduct());
}
- public void CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData info, string sessionId, ItemEventRouterResponse output)
+ public void DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ var mapItem = _mapMarkerService.DeleteMarkerFromMap(pmcData, request);
+
+ // sync with client
+ output.ProfileChanges[sessionId].Items.ChangedItems.Add(mapItem.ConvertToProduct());
}
- public void SortInventory(PmcData pmcData, InventorySortRequestData info, string sessionId, ItemEventRouterResponse output)
+ public void CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ var adjustedMapItem = _mapMarkerService.CreateMarkerOnMap(pmcData, request);
+
+ // Sync with client
+ output.ProfileChanges[sessionId].Items.ChangedItems.Add(adjustedMapItem.ConvertToProduct());
}
- public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData info, string sessionId)
+ public void SortInventory(PmcData pmcData, InventorySortRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ foreach (var change in request.ChangedItems)
+ {
+ var inventoryItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == change.Id);
+ if (inventoryItem is null)
+ {
+ _logger.Error(
+ _localisationService.GetText("inventory-unable_to_sort_inventory_restart_game", change.Id)
+ );
+
+ continue;
+ }
+
+ inventoryItem.ParentId = change.ParentId;
+ inventoryItem.SlotId = change.SlotId;
+ if (change.Location is not null)
+ inventoryItem.Location = change.Location;
+ else
+ inventoryItem.Location = null;
+ }
}
- public void ExamineItem(PmcData pmcData, InventoryExamineRequestData info, string sessionId, ItemEventRouterResponse output)
+ public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData body,
+ string sessionId)
{
- throw new NotImplementedException();
+ foreach (var id in body.Ids) pmcData.Encyclopedia[id] = true;
+
+ return _eventOutputHolder.GetOutput(sessionId);
}
- public void UnBindItem(PmcData pmcData, InventoryBindRequestData info, string sessionId, ItemEventRouterResponse output)
+ public void ExamineItem(PmcData pmcData, InventoryExamineRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ var itemId = "";
+ if (request.FromOwner is not null)
+ {
+ try
+ {
+ itemId = GetExaminedItemTpl(request);
+ }
+ catch
+ {
+ _logger.Error(_localisationService.GetText("inventory-examine_item_does_not_exist", request.Item));
+ }
+
+ // get hideout item
+ if (request.FromOwner.Type == "HideoutProduction") itemId = request.Item;
+ }
+
+ if (itemId is null)
+ {
+ // item template
+ if (_databaseService.GetItems().ContainsKey(request.Item)) itemId = request.Item;
+
+ // Player inventory
+ var target = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
+ if (target is not null) itemId = target.Template;
+ }
+
+ if (itemId is not null)
+ {
+ var fullProfile = _profileHelper.GetFullProfile(sessionId);
+ FlagItemsAsInspectedAndRewardXp([itemId], fullProfile);
+ }
}
- public void BindItem(PmcData pmcData, InventoryBindRequestData info, string sessionId, ItemEventRouterResponse output)
+ protected string? GetExaminedItemTpl(InventoryExamineRequestData request)
{
- throw new NotImplementedException();
+ if (_presetHelper.IsPreset(request.Item)) return _presetHelper.GetBaseItemTpl(request.Item);
+
+ if (request.FromOwner.Id == Traders.FENCE)
+ // Get tpl from fence assorts
+ return _fenceService.GetRawFenceAssorts().Items.FirstOrDefault(x => x.Id == request.Item)?.Template;
+
+ if (request.FromOwner.Type == "Trader")
+ // Not fence
+ // get tpl from trader assort
+ return _databaseService
+ .GetTrader(request.FromOwner.Id)
+ .Assort.Items.FirstOrDefault(item => item.Id == request.Item)
+ ?.Template;
+
+ if (request.FromOwner.Type == "RagFair")
+ {
+ // Try to get tplId from items.json first
+ var item = _itemHelper.GetItem(request.Item);
+ if (item.Key) return item.Value.Id;
+
+ // Try alternate way of getting offer if first approach fails
+ var offer = _ragfairOfferService.GetOfferByOfferId(request.Item) ??
+ _ragfairOfferService.GetOfferByOfferId(request.FromOwner.Id);
+
+ // Try find examine item inside offer items array
+ var matchingItem = offer.Items.FirstOrDefault(offerItem => offerItem.Id == request.Item);
+ if (matchingItem is not null) return matchingItem.Template;
+
+ // Unable to find item in database or ragfair
+ _logger.Warning(_localisationService.GetText("inventory-unable_to_find_item", request.Item));
+ }
+
+ _logger.Error($"Unable to get item with id: {request.Item}");
+
+ return null;
}
- public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData info, string sessionId)
+ public void UnBindItem(PmcData pmcData, InventoryBindRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ // Remove kvp from requested fast panel index
+
+ // TODO - does this work
+ pmcData.Inventory.FastPanel.Remove(request.Index.ToString());
}
- public ItemEventRouterResponse ToggleItem(PmcData pmcData, InventoryToggleRequestData info, string sessionId)
+ public void BindItem(PmcData pmcData, InventoryBindRequestData bindRequest, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ foreach (var kvp in pmcData.Inventory.FastPanel
+ .Where(kvp => kvp.Value == bindRequest.Index.Value.ToString()))
+ {
+ pmcData.Inventory.FastPanel.Remove(kvp.Key);
+
+ break;
+ }
+
+ // Create link between fast panel slot and requested item
+ pmcData.Inventory.FastPanel[bindRequest.Index.ToString()] = bindRequest.Item;
}
- public ItemEventRouterResponse FoldItem(PmcData pmcData, InventoryFoldRequestData info, string sessionId)
+ public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData request, string sessionId)
{
- throw new NotImplementedException();
+ var itemToTag = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
+ if (itemToTag is null)
+ {
+ _logger.Warning(
+ $"Unable to tag item: {request.Item} as it cannot be found in player {sessionId} inventory"
+ );
+
+ return new ItemEventRouterResponse { Warnings = [], ProfileChanges = { } };
+ }
+
+ // Null guard
+ itemToTag.Upd ??= new Upd();
+
+ itemToTag.Upd.Tag = new UpdTag { Color = request.TagColor, Name = request.TagName };
+
+ return _eventOutputHolder.GetOutput(sessionId);
}
- public ItemEventRouterResponse SwapItem(PmcData pmcData, InventorySwapRequestData info, string sessionId)
+ public ItemEventRouterResponse ToggleItem(PmcData pmcData, InventoryToggleRequestData request, string sessionId)
{
- throw new NotImplementedException();
+ // May need to reassign to scav profile
+ var playerData = pmcData;
+
+ // Fix for toggling items while on they're in the Scav inventory
+ if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
+ playerData = _profileHelper.GetScavProfile(sessionId);
+
+ var itemToToggle = playerData.Inventory.Items.FirstOrDefault(x => x.Id == request.Item);
+ if (itemToToggle is not null)
+ {
+ _itemHelper.AddUpdObjectToItem(
+ itemToToggle,
+ _localisationService.GetText("inventory-item_to_toggle_missing_upd", itemToToggle.Id)
+ );
+
+ itemToToggle.Upd.Togglable = new UpdTogglable() { On = request.Value };
+
+ return _eventOutputHolder.GetOutput(sessionId);
+ }
+
+ _logger.Warning(_localisationService.GetText("inventory-unable_to_toggle_item_not_found", request.Item));
+
+ return new ItemEventRouterResponse { Warnings = [], ProfileChanges = { } };
}
- public void TransferItem(PmcData pmcData, InventoryTransferRequestData info, string sessionId, ItemEventRouterResponse output)
+ public ItemEventRouterResponse FoldItem(PmcData pmcData, InventoryFoldRequestData request, string sessionId)
{
- throw new NotImplementedException();
+ // May need to reassign to scav profile
+ var playerData = pmcData;
+
+ // We may be folding data on scav profile, get that profile instead
+ if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
+ playerData = _profileHelper.GetScavProfile(sessionId);
+
+ var itemToFold = playerData.Inventory.Items.FirstOrDefault(item => item?.Id == request.Item);
+ if (itemToFold is null)
+ {
+ // Item not found
+ _logger.Warning(
+ _localisationService.GetText("inventory-unable_to_fold_item_not_found_in_inventory", request.Item)
+ );
+
+ return new ItemEventRouterResponse { Warnings = [], ProfileChanges = { } };
+ }
+
+ // Item may not have upd object
+ _itemHelper.AddUpdObjectToItem(itemToFold);
+
+ itemToFold.Upd.Foldable = new UpdFoldable { Folded = request.Value };
+
+ return _eventOutputHolder.GetOutput(sessionId);
}
- public void MergeItem(PmcData pmcData, InventoryMergeRequestData info, string sessionId, ItemEventRouterResponse output)
+ public ItemEventRouterResponse SwapItem(PmcData pmcData, InventorySwapRequestData request, string sessionId)
{
- throw new NotImplementedException();
+ // During post-raid scav transfer, the swap may be in the scav inventory
+ var playerData = pmcData;
+ if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
+ playerData = _profileHelper.GetScavProfile(sessionId);
+
+ var itemOne = playerData.Inventory.Items.FirstOrDefault(x => x.Id == request.Item);
+ if (itemOne is null)
+ _logger.Error(
+ _localisationService.GetText(
+ "inventory-unable_to_find_item_to_swap",
+ new
+ {
+ item1Id = request.Item,
+ item2Id = request.Item2
+ }
+ )
+ );
+
+ var itemTwo = playerData.Inventory.Items.FirstOrDefault(x => x.Id == request.Item2);
+ if (itemTwo is null)
+ _logger.Error(
+ _localisationService.GetText(
+ "inventory-unable_to_find_item_to_swap",
+ new
+ {
+ item1Id = request.Item2,
+ item2Id = request.Item
+ }
+ )
+ );
+
+ // to.id is the parentid
+ itemOne.ParentId = request.To.Id;
+
+ // to.container is the slotid
+ itemOne.SlotId = request.To.Container;
+
+ // Request object has location data, add it in, otherwise remove existing location from object
+ if (request.To.Location is not null)
+ itemOne.Location = request.To.Location;
+ else
+ // biome-ignore lint/performance/noDelete: Delete is fine here as we entirely want to get rid of the location.
+ itemOne.Location = null;
+
+ itemTwo.ParentId = request.To2.Id;
+ itemTwo.SlotId = request.To2.Container;
+ if (request.To2.Location is not null)
+ itemTwo.Location = request.To2.Location;
+ else
+ itemTwo.Location = null;
+
+ // Client already informed of inventory locations, nothing for us to do
+ return _eventOutputHolder.GetOutput(sessionId);
}
- public void SplitItem(PmcData pmcData, InventorySplitRequestData info, string sessionId, ItemEventRouterResponse output)
+ public void TransferItem(PmcData pmcData, InventoryTransferRequestData request, string sessionId,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ // TODO - check GetOwnerInventoryItems() call still works
+ var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(request, request.Item, sessionId);
+ var sourceItem = inventoryItems.From.FirstOrDefault(item => item.Id == request.Item);
+ var destinationItem = inventoryItems.To.FirstOrDefault(item => item.Id == request.With);
+
+ if (sourceItem is null)
+ {
+ var errorMessage = $"Unable to transfer stack, cannot find source: {request.Item}";
+ _logger.Error(errorMessage);
+
+ _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
+
+ return;
+ }
+
+ if (destinationItem is not null)
+ {
+ var errorMessage = $"Unable to transfer stack, cannot find destination: {request.With}";
+ _logger.Error(errorMessage);
+
+ _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
+
+ return;
+ }
+
+ sourceItem.Upd ??= new Upd { StackObjectsCount = 1 };
+
+ var sourceStackCount = sourceItem.Upd.StackObjectsCount;
+ if (sourceStackCount > request.Count)
+ // Source items stack count greater than new desired count
+ sourceItem.Upd.StackObjectsCount = sourceStackCount - request.Count;
+ else
+ // Moving a full stack onto a smaller stack
+ sourceItem.Upd.StackObjectsCount = sourceStackCount - 1;
+
+ destinationItem.Upd ??= new Upd { StackObjectsCount = 1 };
+
+ var destinationStackCount = destinationItem.Upd.StackObjectsCount;
+ destinationItem.Upd.StackObjectsCount = destinationStackCount + request.Count;
}
- public void RemoveItem(PmcData pmcData, InventoryRemoveRequestData info, string sessionId, ItemEventRouterResponse output)
+ public void MergeItem(PmcData pmcData, InventoryMergeRequestData body, string sessionID,
+ ItemEventRouterResponse output)
{
- throw new NotImplementedException();
+ // Changes made to result apply to character inventory
+ var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(body, body.Item, sessionID);
+
+ // Get source item (can be from player or trader or mail)
+ var sourceItem = inventoryItems.From.FirstOrDefault((x) => x.Id == body.Item);
+ if (sourceItem is null)
+ {
+ var errorMessage = $"Unable to merge stacks as source item: {body.With} cannot be found";
+ _logger.Error(errorMessage);
+
+ _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
+
+ return;
+ }
+
+ // Get item being merged into
+ var destinationItem = inventoryItems.To.FirstOrDefault((x) => x.Id == body.With);
+ if (destinationItem is null)
+ {
+ var errorMessage = $"Unable to merge stacks as destination item: {body.With} cannot be found";
+ _logger.Error(errorMessage);
+
+ _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
+
+ return;
+ }
+
+ if (destinationItem.Upd?.StackObjectsCount is null)
+ // No stackcount on destination, add one
+ destinationItem.Upd = new Upd { StackObjectsCount = 1 };
+
+ if (sourceItem.Upd is null)
+ sourceItem.Upd = new Upd { StackObjectsCount = 1 };
+ else if (sourceItem.Upd.StackObjectsCount is null)
+ // Items pulled out of raid can have no stack count if the stack should be 1
+ sourceItem.Upd.StackObjectsCount = 1;
+
+ // Remove FiR status from destination stack when source stack has no FiR but destination does
+ if (!sourceItem.Upd.SpawnedInSession.GetValueOrDefault(false) &&
+ destinationItem.Upd.SpawnedInSession.GetValueOrDefault(false)) destinationItem.Upd.SpawnedInSession = false;
+
+ destinationItem.Upd.StackObjectsCount +=
+ sourceItem.Upd.StackObjectsCount; // Add source stackcount to destination
+ output.ProfileChanges[sessionID]
+ .Items.DeletedItems.Add(new Product { Id = sourceItem.Id }); // Inform client source item being deleted
+
+ var indexOfItemToRemove = inventoryItems.From.FindIndex((x) => x.Id == sourceItem.Id);
+ if (indexOfItemToRemove == -1)
+ {
+ var errorMessage = $"Unable to find item: {sourceItem.Id} to remove from sender inventory";
+ _logger.Error(errorMessage);
+
+ _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
+
+ return;
+ }
+
+ inventoryItems.From.RemoveAt(indexOfItemToRemove); // Remove source item from 'from' inventory
+ }
+
+ public void SplitItem(PmcData pmcData, InventorySplitRequestData request, string sessionID,
+ ItemEventRouterResponse output)
+ {
+ // Changes made to result apply to character inventory
+ var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(request, request.NewItem, sessionID);
+
+ // Handle cartridge edge-case
+ if (request.Container.Location is null && request.Container.ContainerName == "cartridges")
+ {
+ var matchingItems = inventoryItems.To.Where((x) => x.ParentId == request.Container.Id);
+ request.Container.Location = matchingItems.Count(); // Wrong location for first cartridge
+ }
+
+ // The item being merged has three possible sources: pmc, scav or mail, getOwnerInventoryItems() handles getting correct one
+ var itemToSplit = inventoryItems.From.FirstOrDefault((x) => x.Id == request.SplitItem);
+ if (itemToSplit is null)
+ {
+ var errorMessage = $"Unable to split stack as source item: {request.SplitItem} cannot be found";
+ _logger.Error(errorMessage);
+
+ _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
+
+ return;
+ }
+
+ // Create new upd object that retains properties of original upd + new stack count size
+ var updatedUpd = _cloner.Clone(itemToSplit.Upd);
+ updatedUpd.StackObjectsCount = request.Count;
+
+ // Remove split item count from source stack
+ itemToSplit.Upd.StackObjectsCount -= request.Count;
+
+ // Inform client of change
+ output.ProfileChanges[sessionID]
+ .Items.NewItems.Add(
+ new Product
+ {
+ Id = request.NewItem,
+ Template = itemToSplit.Template,
+ Upd = updatedUpd
+ }
+ );
+
+ // Update player inventory
+ inventoryItems.To.Add(
+ new Item
+ {
+ Id = request.NewItem,
+ Template = itemToSplit.Template,
+ ParentId = request.Container.Id,
+ SlotId = request.Container.ContainerName,
+ Location = request.Container.Location,
+ Upd = updatedUpd
+ }
+ );
+ }
+
+ public void RemoveItem(PmcData pmcData, InventoryRemoveRequestData request, string sessionId,
+ ItemEventRouterResponse output)
+ {
+ if (request.FromOwner?.Type == "Mail")
+ {
+ _inventoryHelper.RemoveItemAndChildrenFromMailRewards(sessionId, request, output);
+
+ return;
+ }
+
+ var profileToRemoveItemFrom = request?.FromOwner.Id == pmcData.Id
+ ? pmcData
+ : _profileHelper.GetFullProfile(sessionId).CharacterData.ScavData;
+
+ _inventoryHelper.RemoveItem(profileToRemoveItemFrom, request.Item, sessionId, output);
}
}
diff --git a/Libraries/Core/Controllers/LauncherV2Controller.cs b/Libraries/Core/Controllers/LauncherV2Controller.cs
index 7bd220bd..ae288ced 100644
--- a/Libraries/Core/Controllers/LauncherV2Controller.cs
+++ b/Libraries/Core/Controllers/LauncherV2Controller.cs
@@ -1,5 +1,4 @@
-using SptCommon.Annotations;
-using Core.Helpers;
+using SptCommon.Annotations;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.Launcher;
using Core.Models.Spt.Config;
diff --git a/Libraries/Core/Helpers/InventoryHelper.cs b/Libraries/Core/Helpers/InventoryHelper.cs
index f69c7c1f..671483dc 100644
--- a/Libraries/Core/Helpers/InventoryHelper.cs
+++ b/Libraries/Core/Helpers/InventoryHelper.cs
@@ -1,26 +1,30 @@
using System.Text.Json.Serialization;
-using SptCommon.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.Enums;
using Core.Models.Spt.Config;
using Core.Models.Spt.Inventory;
using Core.Models.Utils;
+using Core.Servers;
using Core.Services;
-using Core.Models.Eft.Player;
-using System.ComponentModel;
-using Core.Models.Eft.Hideout;
-using Core.Models.Enums;
-using Core.Models.Spt.Bots;
-using Core.Models.Spt.Services;
-using System.Xml.Linq;
+using Core.Utils;
+using SptCommon.Annotations;
namespace Core.Helpers;
[Injectable]
public class InventoryHelper(
ISptLogger _logger,
+ HashUtil hashUtil,
+ HttpResponseUtil httpResponseUtil,
+ FenceService fenceService,
+ DialogueHelper dialogueHelper,
+ ContainerHelper containerHelper,
+ DatabaseServer databaseServer,
+ PaymentHelper paymentHelper,
+ TraderAssortHelper traderAssortHelper,
ProfileHelper _profileHelper,
DialogueHelper _dialogueHelper,
ContainerHelper _containerHelper,
@@ -29,7 +33,7 @@ public class InventoryHelper(
)
{
///
- /// Add multiple items to player stash (assuming they all fit)
+ /// Add multiple items to player stash (assuming they all fit)
///
/// Session id
/// AddItemsDirectRequest request
@@ -45,7 +49,7 @@ public class InventoryHelper(
}
///
- /// Add whatever is passed in request.itemWithModsToAdd into player inventory (if it fits)
+ /// Add whatever is passed in request.itemWithModsToAdd into player inventory (if it fits)
///
/// Session id
/// AddItemDirect request
@@ -61,7 +65,7 @@ public class InventoryHelper(
}
///
- /// Set FiR status for an item + its children
+ /// Set FiR status for an item + its children
///
/// An item
/// Item was found in raid
@@ -71,7 +75,7 @@ public class InventoryHelper(
}
///
- /// Remove properties from a Upd object used by a trader/ragfair that are unnecessary to a player
+ /// 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)
@@ -80,7 +84,7 @@ public class InventoryHelper(
}
///
- /// Can all provided items be added into player inventory
+ /// Can all provided items be added into player inventory
///
/// Player id
/// Array of items with children to try and fit
@@ -91,7 +95,7 @@ public class InventoryHelper(
}
///
- /// Do the provided items all fit into the grid
+ /// Do the provided items all fit into the grid
///
/// Container grid to fit items into
/// Items to try and fit into grid
@@ -102,7 +106,7 @@ public class InventoryHelper(
}
///
- /// Does an item fit into a container grid
+ /// Does an item fit into a container grid
///
/// Container grid
/// Item to check fits
@@ -125,7 +129,8 @@ public class InventoryHelper(
findSlotResult.Y.Value,
itemSize[0],
itemSize[1],
- findSlotResult.Rotation.Value);
+ findSlotResult.Rotation.Value
+ );
}
catch (Exception ex)
{
@@ -142,7 +147,7 @@ public class InventoryHelper(
}
///
- /// Find a free location inside a container to fit the item
+ /// Find a free location inside a container to fit the item
///
/// Container grid to add item to
/// Item to add to grid
@@ -170,7 +175,8 @@ public class InventoryHelper(
findSlotResult.Y.Value,
itemSize[0],
itemSize[1],
- findSlotResult.Rotation.Value);
+ findSlotResult.Rotation.Value
+ );
}
catch (Exception ex)
{
@@ -178,6 +184,7 @@ public class InventoryHelper(
return;
}
+
// Store details for object, incuding container item will be placed in
rootItemAdded.ParentId = containerId;
rootItemAdded.SlotId = desiredSlotId;
@@ -186,16 +193,15 @@ public class InventoryHelper(
X = findSlotResult.X,
Y = findSlotResult.Y,
R = findSlotResult.Rotation.GetValueOrDefault(false) ? 1 : 0,
- Rotation = findSlotResult.Rotation,
+ Rotation = findSlotResult.Rotation
};
// Success! exit
- return;
}
}
///
- /// Find a location to place an item into inventory and place it
+ /// 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
@@ -215,9 +221,9 @@ public class InventoryHelper(
}
///
- /// Handle Remove event
- /// Remove item from player inventory + insured items array
- /// Also deletes child items
+ /// 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
@@ -229,18 +235,19 @@ public class InventoryHelper(
}
///
- /// Delete desired item from a player profiles mail
+ /// Delete desired item from a player profiles mail
///
/// Session id
/// Remove request
/// OPTIONAL - ItemEventRouterResponse
- public void RemoveItemAndChildrenFromMailRewards(string sessionId, InventoryRemoveRequestData removeRequest, ItemEventRouterResponse output = null)
+ 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
+ /// Find item by id in player inventory and remove x of its count
///
/// player profile
/// Item id to decrement StackObjectsCount of
@@ -248,13 +255,14 @@ public class InventoryHelper(
/// Session id
/// ItemEventRouterResponse
/// ItemEventRouterResponse
- public ItemEventRouterResponse RemoveItemByCount(PmcData pmcData, string itemId, int countToRemove, string sessionId, ItemEventRouterResponse output = null)
+ 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
+ /// 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
@@ -267,8 +275,8 @@ public class InventoryHelper(
}
///
- /// Calculates the size of an item including attachments
- /// takes into account if item is folded
+ /// Calculates the size of an item including attachments
+ /// takes into account if item is folded
///
/// Items template id
/// Items id
@@ -280,19 +288,18 @@ public class InventoryHelper(
var (key, tmpItem) = _itemHelper.GetItem(itemTpl);
// Invalid item
- if (!key)
- {
- _logger.Error(_localisationService.GetText("inventory-invalid_item_missing_from_db", itemTpl));
- }
+ if (!key) _logger.Error(_localisationService.GetText("inventory-invalid_item_missing_from_db", itemTpl));
// Item found but no _props property
if (key && tmpItem.Properties is null)
- {
- _localisationService.GetText("inventory-item_missing_props_property", new {
- itemTpl = itemTpl,
- itemName = tmpItem?.Name
- });
- }
+ _localisationService.GetText(
+ "inventory-item_missing_props_property",
+ new
+ {
+ itemTpl,
+ itemName = tmpItem?.Name
+ }
+ );
// No item object or getItem() returned false
if (!key && tmpItem is null)
@@ -320,52 +327,44 @@ public class InventoryHelper(
var outY = (int)tmpItem.Properties.Height;
// Item types to ignore
- var skipThisItems = new List { BaseClasses.BACKPACK, BaseClasses.SEARCHABLE_ITEM, BaseClasses.SIMPLE_CONTAINER };
+ var skipThisItems = new List
+ { BaseClasses.BACKPACK, BaseClasses.SEARCHABLE_ITEM, BaseClasses.SIMPLE_CONTAINER };
var rootIsFolded = rootItem?.Upd?.Foldable?.Folded == true;
// The item itself is collapsible
if (isFoldable is not null && string.IsNullOrEmpty(foldedSlot) && rootIsFolded)
- {
outX -= tmpItem.Properties.SizeReduceRight.Value;
- }
// Calculate size contribution from child items/attachments
if (!skipThisItems.Contains(tmpItem.Parent))
- {
while (toDo.Count > 0)
{
- if (inventoryItemHash.ByParentId.ContainsKey(toDo[0])) {
- foreach (var item in inventoryItemHash.ByParentId[toDo[0]]) {
+ if (inventoryItemHash.ByParentId.ContainsKey(toDo[0]))
+ foreach (var item in inventoryItemHash.ByParentId[toDo[0]])
+ {
// Filtering child items outside of mod slots, such as those inside containers, without counting their ExtraSize attribute
- if (item.SlotId.IndexOf("mod_") < 0)
- {
- continue;
- }
+ if (item.SlotId.IndexOf("mod_") < 0) continue;
toDo.Add(item.Id);
// If the barrel is folded the space in the barrel is not counted
var itemResult = _itemHelper.GetItem(item.Template);
if (!itemResult.Key)
- {
_logger.Error(
- _localisationService.GetText("inventory-get_item_size_item_not_found_by_tpl", item.Template));
- }
+ _localisationService.GetText(
+ "inventory-get_item_size_item_not_found_by_tpl",
+ item.Template
+ )
+ );
var itm = itemResult.Value;
var childFoldable = itm.Properties.Foldable.GetValueOrDefault(false);
var childFolded = item.Upd?.Foldable is not null && item.Upd.Foldable.Folded == true;
- if (isFoldable is true && foldedSlot == item.SlotId && (rootIsFolded || childFolded))
- {
- continue;
- }
+ if (isFoldable is true && foldedSlot == item.SlotId && (rootIsFolded || childFolded)) continue;
- if (childFoldable && rootIsFolded && childFolded)
- {
- continue;
- }
+ if (childFoldable && rootIsFolded && childFolded) continue;
// Calculating child ExtraSize
if (itm.Properties.ExtraSizeForceAdd == true)
@@ -378,25 +377,30 @@ public class InventoryHelper(
else
{
sizeUp = sizeUp < itm.Properties.ExtraSizeUp ? itm.Properties.ExtraSizeUp.Value : sizeUp;
- sizeDown = sizeDown < itm.Properties.ExtraSizeDown ? itm.Properties.ExtraSizeDown.Value : sizeDown;
- sizeLeft = sizeLeft < itm.Properties.ExtraSizeLeft ? itm.Properties.ExtraSizeLeft.Value : sizeLeft;
- sizeRight = sizeRight < itm.Properties.ExtraSizeRight ? itm.Properties.ExtraSizeRight.Value : sizeRight;
+ sizeDown = sizeDown < itm.Properties.ExtraSizeDown
+ ? itm.Properties.ExtraSizeDown.Value
+ : sizeDown;
+ sizeLeft = sizeLeft < itm.Properties.ExtraSizeLeft
+ ? itm.Properties.ExtraSizeLeft.Value
+ : sizeLeft;
+ sizeRight = sizeRight < itm.Properties.ExtraSizeRight
+ ? itm.Properties.ExtraSizeRight.Value
+ : sizeRight;
}
}
- }
toDo.RemoveAt(0);
}
- }
- return [
+ return
+ [
outX + sizeLeft + sizeRight + forcedLeft + forcedRight,
- outY + sizeUp + sizeDown + forcedUp + forcedDown,
+ outY + sizeUp + sizeDown + forcedUp + forcedDown
];
}
///
- /// Get a blank two-dimensional representation of a container
+ /// Get a blank two-dimensional representation of a container
///
/// Horizontal size of container
/// Vertical size of container
@@ -417,7 +421,7 @@ public class InventoryHelper(
}
///
- /// Get a 2d mapping of a container with what grid slots are filled
+ /// Get a 2d mapping of a container with what grid slots are filled
///
/// Horizontal size of container
/// Vertical size of container
@@ -434,13 +438,12 @@ public class InventoryHelper(
// Get subset of items that belong to the desired container
if (!inventoryItemHash.ByParentId.TryGetValue(containerId, out List- containerItemHash))
- {
// No items in container, exit early
return container2D;
- }
// Check each item in container
- foreach (var item in containerItemHash) {
+ foreach (var item in containerItemHash)
+ {
var itemLocation = item?.Location as ItemLocation;
if (itemLocation is null)
{
@@ -461,28 +464,29 @@ public class InventoryHelper(
var fillTo = itemLocation.X + fW;
for (var y = 0; y < fH; y++)
- {
try
{
var rowIndex = itemLocation.Y + y;
var containerRow = container2D[rowIndex.Value];
if (containerRow is null)
- {
_logger.Error("Unable to find container: { containerId} row line: { itemLocation.y + y}");
- }
// Fill the corresponding cells in the container map to show the slot is taken
Array.Fill(containerRow, 1, itemLocation.X.Value, fW);
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
_logger.Error(
- _localisationService.GetText("inventory-unable_to_fill_container", new {
- id = item.Id,
- error = $"{ex.Message} {ex.StackTrace}"
- })
+ _localisationService.GetText(
+ "inventory-unable_to_fill_container",
+ new
+ {
+ id = item.Id,
+ error = $"{ex.Message} {ex.StackTrace}"
+ }
+ )
);
}
-
- }
}
return container2D;
@@ -498,29 +502,27 @@ public class InventoryHelper(
{
var inventoryItemHash = new InventoryItemHash
{
- ByItemId = new(),
- ByParentId = new()
+ ByItemId = new Dictionary(),
+ ByParentId = new Dictionary>()
};
foreach (var item in inventoryItems)
{
inventoryItemHash.ByItemId.TryAdd(item.Id, item);
- if (item.ParentId is null) {
- continue;
- }
+ if (item.ParentId is null) continue;
- if (!inventoryItemHash.ByParentId.ContainsKey(item.ParentId)) {
+ if (!inventoryItemHash.ByParentId.ContainsKey(item.ParentId))
inventoryItemHash.ByParentId[item.ParentId] = [];
- }
inventoryItemHash.ByParentId[item.ParentId].Add(item);
}
+
return inventoryItemHash;
}
///
- /// 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.
+ /// 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
@@ -571,13 +573,13 @@ public class InventoryHelper(
From = fromInventoryItems,
To = toInventoryItems,
SameInventory = movingToSameInventory,
- IsMail = fromType == "mail",
+ IsMail = fromType == "mail"
};
}
///
- /// Get a two-dimensional array to represent stash slots
- /// 0 value = free, 1 = taken
+ /// Get a two-dimensional array to represent stash slots
+ /// 0 value = free, 1 = taken
///
/// Player profile
/// session id
@@ -588,7 +590,7 @@ public class InventoryHelper(
}
///
- /// Get a blank two-dimensional array representation of a container
+ /// Get a blank two-dimensional array representation of a container
///
/// Container to get data for
/// blank two-dimensional array
@@ -604,7 +606,7 @@ public class InventoryHelper(
}
///
- /// Get a two-dimensional array representation of the players sorting table
+ /// Get a two-dimensional array representation of the players sorting table
///
/// Player profile
/// two-dimensional array
@@ -614,7 +616,7 @@ public class InventoryHelper(
}
///
- /// Get Players Stash Size
+ /// Get Players Stash Size
///
/// Players id
/// Dictionary of 2 values, horizontal and vertical stash size
@@ -624,7 +626,7 @@ public class InventoryHelper(
}
///
- /// Get the players stash items tpl
+ /// Get the players stash items tpl
///
/// Player id
/// Stash tpl
@@ -634,18 +636,54 @@ public class InventoryHelper(
}
///
- /// Internal helper function to transfer an item + children from one profile to another.
+ /// 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();
+ HandleCartridges(sourceItems, request);
+
+ // Get all children item has, they need to move with item
+ var idsToMove = _itemHelper.FindAndReturnChildrenByItems(sourceItems, request.Item);
+ foreach (var itemId in idsToMove) {
+ var itemToMove = sourceItems.FirstOrDefault((item) => item.Id == itemId);
+ if (itemToMove is null)
+ {
+ _logger.Error(_localisationService.GetText("inventory-unable_to_find_item_to_move", itemId));
+ continue;
+ }
+
+ // Only adjust the values for parent item, not children (their values are already correctly tied to parent)
+ if (itemId == request.Item)
+ {
+ itemToMove.ParentId = request.To.Id;
+ itemToMove.SlotId = request.To.Container;
+
+ if (request.To.Location is not null)
+ {
+ // Update location object
+ itemToMove.Location = request.To.Location;
+ }
+ else
+ {
+ // No location in request, delete it
+ if (itemToMove.Location is null)
+ {
+ // biome-ignore lint/performance/noDelete: Delete is fine here as we're trying to remove the entire data property.
+ itemToMove.Location = null;
+ }
+ }
+ }
+
+ toItems.Add(itemToMove);
+ sourceItems.RemoveAt(sourceItems.IndexOf(itemToMove));
+ }
}
///
- /// Internal helper function to move item within the same profile.
+ /// Internal helper function to move item within the same profile.
///
/// profile to edit
///
@@ -662,7 +700,7 @@ public class InventoryHelper(
HandleCartridges(inventoryItems, moveRequest);
// Find item we want to 'move'
- var matchingInventoryItem = inventoryItems.FirstOrDefault((item) => item.Id == moveRequest.Item);
+ 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";
@@ -673,7 +711,7 @@ public class InventoryHelper(
}
_logger.Debug(
- $"{ moveRequest.Action} item: ${ moveRequest.Item} from slotid: { matchingInventoryItem.SlotId} to container: { moveRequest.To.Container}"
+ $"{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)
@@ -685,7 +723,7 @@ public class InventoryHelper(
new
{
slotId = matchingInventoryItem.SlotId,
- container = moveRequest.To.Container,
+ container = moveRequest.To.Container
}
)
);
@@ -708,17 +746,14 @@ public class InventoryHelper(
else
{
// Moved from slot with location to one without, clean up
- if (matchingInventoryItem.Location is not null)
- {
- matchingInventoryItem.Location = null;
- }
+ 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
+ /// Update fast panel bindings when an item is moved into a container that doesn't allow quick slot access
///
/// Player profile
/// item being moved
@@ -726,39 +761,39 @@ public class InventoryHelper(
{
// Find matching _id in fast panel
- if (!pmcData.Inventory.FastPanel.TryGetValue(itemBeingMoved.Id, out var fastPanelSlot))
- {
- return;
- }
+ 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;
- }
+ 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()] = "";
- }
+ if (!wasMovedToFastPanelAccessibleContainer) pmcData.Inventory.FastPanel[fastPanelSlot[0].ToString()] = "";
}
///
- /// Internal helper function to handle cartridges in inventory if any of them exist.
+ /// Internal helper function to handle cartridges in inventory if any of them exist.
///
protected void HandleCartridges(List
- items, InventoryMoveRequestData request)
{
- throw new NotImplementedException();
+ // Not moving item into a cartridge slot, skip
+ if (request.To.Container != "cartridges")
+ {
+ return;
+ }
+
+ // Get a count of cartridges in existing magazine
+ var cartridgeCount = items.Count(item => item.ParentId == request.To.Id);
+
+ request.To.Location = cartridgeCount;
}
///
- /// Get details for how a random loot container should be handled, max rewards, possible reward tpls
+ /// Get details for how a random loot container should be handled, max rewards, possible reward tpls
///
/// Container being opened
/// Reward details
@@ -768,7 +803,7 @@ public class InventoryHelper(
}
///
- /// Get inventory configuration
+ /// Get inventory configuration
///
/// Inventory configuration
public InventoryConfig GetInventoryConfig()
@@ -777,9 +812,9 @@ public class InventoryHelper(
}
///
- /// Recursively checks if the given item is
- /// inside the stash, that is it has the stash as
- /// ancestor with slotId=hideout
+ /// 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
@@ -795,7 +830,7 @@ public class InventoryHelper(
}
///
- /// Does the provided item have a root item with the provided id
+ /// Does the provided item have a root item with the provided id
///
/// Profile with items
/// Item to check
diff --git a/Libraries/Core/Models/Eft/Common/Tables/Item.cs b/Libraries/Core/Models/Eft/Common/Tables/Item.cs
index 25170621..86d4439c 100644
--- a/Libraries/Core/Models/Eft/Common/Tables/Item.cs
+++ b/Libraries/Core/Models/Eft/Common/Tables/Item.cs
@@ -25,6 +25,20 @@ public record Item
[JsonPropertyName("upd")]
public Upd? Upd { get; set; }
+
+ public ItemEvent.Product ConvertToProduct()
+ {
+ // TODO - maybe this entire product item can be replaced with Item?
+ return new ItemEvent.Product
+ {
+ Id = Id,
+ Template = Template,
+ ParentId = ParentId,
+ SlotId = SlotId,
+ Upd = Upd,
+ Location = (ItemLocation)Location
+ };
+ }
}
public record ItemLocation
diff --git a/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs b/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs
index 812940ae..79d368da 100644
--- a/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs
+++ b/Libraries/Core/Models/Eft/Common/Tables/TemplateItem.cs
@@ -137,8 +137,9 @@ public record Props
[JsonPropertyName("LootExperience")]
public double? LootExperience { get; set; }
+ // Checked on live
[JsonPropertyName("ExamineExperience")]
- public double? ExamineExperience { get; set; }
+ public int? ExamineExperience { get; set; }
[JsonPropertyName("HideEntrails")]
public bool? HideEntrails { get; set; }
diff --git a/Libraries/Core/Models/Eft/Inventory/InventoryReadEncyclopediaRequestData.cs b/Libraries/Core/Models/Eft/Inventory/InventoryReadEncyclopediaRequestData.cs
index d4473d9b..5734e796 100644
--- a/Libraries/Core/Models/Eft/Inventory/InventoryReadEncyclopediaRequestData.cs
+++ b/Libraries/Core/Models/Eft/Inventory/InventoryReadEncyclopediaRequestData.cs
@@ -1,9 +1,9 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
namespace Core.Models.Eft.Inventory;
public record InventoryReadEncyclopediaRequestData : InventoryBaseActionRequestData
{
[JsonPropertyName("ids")]
- public List? Ids { get; set; }
+ public List Ids { get; set; }
}
diff --git a/Libraries/Core/Models/Eft/Inventory/SetFavoriteItems.cs b/Libraries/Core/Models/Eft/Inventory/SetFavoriteItems.cs
index c6a223b0..86323d83 100644
--- a/Libraries/Core/Models/Eft/Inventory/SetFavoriteItems.cs
+++ b/Libraries/Core/Models/Eft/Inventory/SetFavoriteItems.cs
@@ -1,11 +1,11 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
namespace Core.Models.Eft.Inventory;
public record SetFavoriteItems : InventoryBaseActionRequestData
{
[JsonPropertyName("items")]
- public List