partially implemented inventory item movement

This commit is contained in:
Chomp
2025-01-18 18:01:40 +00:00
parent 8180fab8b8
commit 69d22cef11
7 changed files with 375 additions and 108 deletions
+3 -4
View File
@@ -23,10 +23,9 @@ public class InventoryCallbacks(
/// <returns></returns>
public ItemEventRouterResponse MoveItem(PmcData pmcData, InventoryMoveRequestData info, string sessionID, ItemEventRouterResponse output)
{
// _inventoryController.MoveItem(pmcData, info, sessionID, output);
// TODO: InventoryController is not implemented rn
// return output;
throw new NotImplementedException();
_inventoryController.MoveItem(pmcData, info, sessionID, output);
return output;
}
/// <summary>
+2 -2
View File
@@ -27,7 +27,7 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
public bool IsCriticalError(List<Warning> warnings)
{
// List of non-critical error codes, we return true if any error NOT included is passed in
var nonCriticalErrorCodes = new List<BackendErrorCodes>() { BackendErrorCodes.NOTENOUGHSPACE };
var nonCriticalErrorCodes = new List<BackendErrorCodes> { BackendErrorCodes.NotEnoughSpace };
foreach (var warning in warnings)
{
@@ -44,7 +44,7 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
public int GetErrorCode(List<Warning> warnings)
{
return int.Parse((warnings[0].Code is null
? BackendErrorCodes.UNKNOWN_ERROR.ToString()
? BackendErrorCodes.UnknownError.ToString()
: warnings.FirstOrDefault()?.Code) ?? string.Empty);
}
}
+132 -1
View File
@@ -1,9 +1,140 @@
using Core.Annotations;
using Core.Generators;
using Core.Helpers;
using Core.Models.Eft.Common;
using Core.Models.Eft.Inventory;
using Core.Models.Eft.ItemEvent;
using Core.Models.Enums;
using Core.Models.Utils;
using Core.Routers;
using Core.Services;
using Core.Utils;
using Core.Utils.Cloners;
namespace Core.Controllers;
[Injectable]
public class InventoryController
{
// TODO
protected ISptLogger<InventoryController> _logger;
protected HashUtil _hashUtil;
protected RandomUtil _randomUtil;
protected HttpResponseUtil _httpResponseUtil;
protected PresetHelper _presetHelper;
protected InventoryHelper _inventoryHelper;
protected QuestHelper _questHelper;
protected HideoutHelper _hideoutHelper;
protected ProfileHelper _profileHelper;
protected PaymentHelper _paymentHelper;
protected TraderHelper _traderHelper;
protected DatabaseService _databaseService;
protected FenceService _fenceService;
protected RagfairOfferService _ragfairOfferService;
protected MapMarkerService _mapMarkerService;
protected LocalisationService _localisationService;
protected PlayerService _playerService;
protected LootGenerator _lootGenerator;
protected EventOutputHolder _eventOutputHolder;
public InventoryController(
ISptLogger<InventoryController> logger,
HashUtil hashUtil,
RandomUtil randomUtil,
HttpResponseUtil httpResponseUtil,
PresetHelper presetHelper,
InventoryHelper inventoryHelper,
QuestHelper questHelper,
HideoutHelper hideoutHelper,
ProfileHelper profileHelper,
PaymentHelper paymentHelper,
TraderHelper traderHelper,
DatabaseService databaseService,
FenceService fenceService,
RagfairOfferService ragfairOfferService,
MapMarkerService mapMarkerService,
LocalisationService localisationService,
PlayerService playerService,
LootGenerator lootGenerator,
EventOutputHolder eventOutputHolder,
ICloner cloner
)
{
_logger = logger;
_hashUtil = hashUtil;
_randomUtil = randomUtil;
_httpResponseUtil = httpResponseUtil;
_presetHelper = presetHelper;
_inventoryHelper = inventoryHelper;
_questHelper = questHelper;
_hideoutHelper = hideoutHelper;
_profileHelper = profileHelper;
_paymentHelper = paymentHelper;
_traderHelper = traderHelper;
_databaseService = databaseService;
_fenceService = fenceService;
_ragfairOfferService = ragfairOfferService;
_mapMarkerService = mapMarkerService;
_localisationService = localisationService;
_playerService = playerService;
_lootGenerator = lootGenerator;
_eventOutputHolder = eventOutputHolder;
}
public void MoveItem(PmcData pmcData, InventoryMoveRequestData moveRequest, string sessionID, ItemEventRouterResponse output)
{
if (output.Warnings.Count > 0)
{
return;
}
// Changes made to result apply to character inventory
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
if (moveRequest.FromOwner?.Type == "Trader" && !ownerInventoryItems.IsMail.GetValueOrDefault(false))
{
AppendTraderExploitErrorResponse(output);
return;
}
// Check for item in inventory before allowing internal transfer
var originalItemLocation = ownerInventoryItems.From.FirstOrDefault((item) => item.Id == moveRequest.Item);
if (originalItemLocation is null)
{
// Internal item move but item never existed, possible dupe glitch
AppendTraderExploitErrorResponse(output);
return;
}
var originalLocationSlotId = originalItemLocation?.SlotId;
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.StartsWith("dogtag") || originalLocationSlotId.StartsWith("dogtag"))
{
_hideoutHelper.ApplyPlaceOfFameDogtagBonus(pmcData);
}
}
else
{
_inventoryHelper.MoveItemToProfile(ownerInventoryItems.From, ownerInventoryItems.To, moveRequest);
}
}
private void AppendTraderExploitErrorResponse(ItemEventRouterResponse output)
{
_httpResponseUtil.AppendErrorToOutput(
output,
_localisationService.GetText("inventory-edit_trader_item"),
(BackendErrorCodes)228
);
}
}
+146 -13
View File
@@ -1,4 +1,4 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
using Core.Annotations;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
@@ -6,12 +6,32 @@ 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<InventoryHelper> _logger;
protected ProfileHelper _profileHelper;
protected DialogueHelper _dialogueHelper;
protected LocalisationService _localisationService;
public InventoryHelper(
ISptLogger<InventoryHelper> logger,
ProfileHelper profileHelper,
DialogueHelper dialogueHelper,
LocalisationService localisationService
)
{
_logger = logger;
_profileHelper = profileHelper;
_dialogueHelper = dialogueHelper;
_localisationService = localisationService;
}
/// <summary>
/// Add multiple items to player stash (assuming they all fit)
/// </summary>
@@ -49,7 +69,7 @@ public class InventoryHelper
/// </summary>
/// <param name="itemWithChildren">An item</param>
/// <param name="foundInRaid">Item was found in raid</param>
protected void SetFindInRaidStatusForItem(Item[] itemWithChildren, bool foundInRaid)
protected void SetFindInRaidStatusForItem(List<Item> itemWithChildren, bool foundInRaid)
{
throw new NotImplementedException();
}
@@ -236,17 +256,59 @@ public class InventoryHelper
/// Based on the item action, determine whose inventories we should be looking at for from and to.
/// </summary>
/// <param name="request">Item interaction request</param>
/// <param name="sessionId">Session id / playerid</param>
/// <param name="item">Item being moved/split/etc to inventory</param>
/// <param name="sessionId">Session id / players Id</param>
/// <returns>OwnerInventoryItems with inventory of player/scav to adjust</returns>
public OwnerInventoryItems GetOwnerInventoryItems(
InventoryMoveRequestData request,
InventoryBaseActionRequestData request,
string item,
string sessionId)
{
throw new NotImplementedException();
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",
};
}
/// <summary>
/// Get a two dimensional array to represent stash slots
/// Get a two-dimensional array to represent stash slots
/// 0 value = free, 1 = taken
/// </summary>
/// <param name="pmcData">Player profile</param>
@@ -303,7 +365,7 @@ public class InventoryHelper
/// <param name="sourceItems">Inventory of the source (can be non-player)</param>
/// <param name="toItems">Inventory of the destination</param>
/// <param name="request">Move request</param>
public void MoveItemToProfile(Item[] sourceItems, Item[] toItems, InventoryMoveRequestData request)
public void MoveItemToProfile(List<Item> sourceItems, List<Item> toItems, InventoryMoveRequestData request)
{
throw new NotImplementedException();
}
@@ -314,13 +376,63 @@ public class InventoryHelper
/// <param name="pmcData">profile to edit</param>
/// <param name="inventoryItems"></param>
/// <param name="moveRequest">client move request</param>
/// <param name="errorMessage"></param>
/// <returns>True if move was successful</returns>
public (bool success, string errorMessage) MoveItemInternal(
public bool MoveItemInternal(
PmcData pmcData,
Item[] inventoryItems,
InventoryMoveRequestData moveRequest)
List<Item> inventoryItems,
InventoryMoveRequestData moveRequest,
out string errorMessage)
{
throw new NotImplementedException();
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;
}
/// <summary>
@@ -330,13 +442,34 @@ public class InventoryHelper
/// <param name="itemBeingMoved">item being moved</param>
protected void UpdateFastPanelBinding(PmcData pmcData, Item itemBeingMoved)
{
throw new NotImplementedException();
// 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<string> slots = ["pockets", "tacticalvest"];
var wasMovedToFastPanelAccessibleContainer = slots.Contains(
itemParent?.SlotId?.ToLower() ?? "");
if (!wasMovedToFastPanelAccessibleContainer)
{
pmcData.Inventory.FastPanel[fastPanelSlot[0].ToString()] = "";
}
}
/// <summary>
/// Internal helper function to handle cartridges in inventory if any of them exist.
/// </summary>
protected void HandleCartridges(Item[] items, InventoryMoveRequestData request)
protected void HandleCartridges(List<Item> items, InventoryMoveRequestData request)
{
throw new NotImplementedException();
}
@@ -1,9 +1,9 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
using Core.Models.Eft.Common.Request;
namespace Core.Models.Eft.Inventory;
public record InventoryBaseActionRequestData : BaseInteractionRequestData
public abstract record InventoryBaseActionRequestData : BaseInteractionRequestData
{
}
+89 -85
View File
@@ -2,89 +2,93 @@ namespace Core.Models.Enums;
public enum BackendErrorCodes
{
NONE = 0,
UNKNOWN_ERROR = 200,
NOT_AUTHORIZED = 201,
NEED_AUTHORIZATION_CODE = 209,
WRONG_AUTHORIZATION_CODE = 211,
NEED_CAPTCHA = 214,
NO_NEED_CAPTCHA = 215,
CAPTCHA_INVALID_ANSWER = 216,
CAPTCHA_FAILED = 218,
CAPTCHA_BRUTE_FORCED = 219,
NO_ROOM_IN_STASH = 223,
NICKNAME_NOT_UNIQUE = 225,
NICKNAME_NOT_VALID = 226,
UNSUPPORTED_CLIENT_VERSION = 232,
REPORT_NOT_ALLOWED = 238,
NICKNAME_IS_ABUSIVE = 241,
NICKNAME_CHANGE_TIMEOUT = 242,
NOT_ENOUGH_SPACE_TO_UNPACK = 257,
NOT_MODIFIED = 304,
HTTP_BAD_REQUEST = 400,
HTTP_NOT_AUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_METHOD_NOT_ALLOWED = 405,
UNKNOWN_TRADING_ERROR = 500,
HTTPNOTIMPLEMENTED = 501,
HTTPBADGATEWAY = 502,
HTTPSERVICEUNAVAILABLE = 503,
HTTPGATEWAYTIMEOUT = 504,
TRADEROUTOFMONEY = 505,
HTTPVARIANTALSONEGOTIATES = 506,
PRICECHANGED = 509,
TRADERDISABLED = 512,
ITEMHASBEENSOLD = 513,
NOTENOUGHSPACEFORMONEY = 518,
HTTPINVALIDSSLCERTIFICATE = 526,
UNKNOWNRAGFAIRERROR = 550,
UNKNOWNRAGFAIRERROR2 = 551,
UNKNOWNMATCHMAKERERROR = 600,
SESSIONPARAMETERSERROR = 601,
SESSIONLOST = 602,
SERVERNOTREGISTERED = 604,
UNKNOWNQUESTERROR = 700,
QUESTBADPARAM = 702,
QUESTNOTFOUND = 703,
QUESTISUNAVAILABLE = 704,
NOFREESPACEFORREWARDS = 705,
WRONGQUESTSTATUS = 706,
CANTCOMPLETEQUEST = 707,
UNKNOWNMAILERROR = 900,
TOOMANYFRIENDREQUESTS = 925,
UNKNOWNSCRIPTEXECUTIONERROR = 1000,
UNKNOWNREPAIRINGERROR = 1200,
UNKNOWNINSURANCEERROR = 1300,
UNKNOWNCURRENCYEXCHANGEERROR = 1400,
OFFERNOTFOUND = 1503,
NOTENOUGHSPACE = 1505,
OFFEROUTOFSTOCK = 1506,
OFFERSOLD = 1507,
RAGFAIRUNAVAILABLE = 1511,
BANNEDERRORCODE = 1513,
INSUFFICIENTNUMBERINSTOCK = 1516,
TOOMANYITEMSTOSELL = 1517,
INCORRECTCLIENTPRICE = 1519,
EXAMINATIONFAILED = 22001,
ITEMALREADYEXAMINED = 22002,
UNKNOWNNGINXERROR = 9000,
PARSERESPONSEERROR = 9001,
UNKNOWNMATCHMAKERERROR2 = 503000, // They have two of these...why :/
UNKNOWNGROUPERROR = 502000,
GROUPREQUESTNOTFOUND = 502002,
GROUPFULL = 502004,
PLAYERALREADYINGROUP = 502005,
PLAYERNOTINGROUP = 502006,
PLAYERNOTLEADER = 502007,
CANTCHANGEREADYSTATE = 502010,
PLAYERFORBIDDENGROUPINVITES = 502011,
LEADERALREADYREADY = 502012,
GROUPSENDINVITEERROR = 502013,
PLAYERISOFFLINE = 502014,
PLAYERISNOTSEARCHINGFORGROUP = 502018,
PLAYERALREADYLOOKINGFORGAME = 503001,
PLAYERINRAID = 503002,
LIMITFORPRESETSREACHED = 504001,
PLAYERPROFILENOTFOUND = 505001,
None,
UnknownError = 200,
NotAuthorized,
NeedAuthorizationCode = 209,
WrongAuthorizationCode = 211,
NeedCaptcha = 214,
NoNeedCaptcha,
CaptchaInvalidAnswer,
CaptchaFailed = 218,
CaptchaBruteForced,
NoRoomInStash = 223,
NicknameNotUnique = 225,
NicknameNotValid,
UnsupportedClientVersion = 232,
ReportNotAllowed = 238,
NicknameIsAbusive = 241,
NicknameChangeTimeout,
NotEnoughSpaceToUnpack = 257,
NotModified = 304,
HTTPBadRequest = 400,
HTTPNotAuthorized,
HTTPForbidden = 403,
HTTPNotFound,
HTTPMethodNotAllowed,
UnknownTradingError = 500,
HTTPNotImplemented,
HTTPBadGateway,
HTTPServiceUnavailable,
HTTPGatewayTimeout,
TraderOutOfMoney,
HTTPVariantAlsoNegotiates,
PriceChanged = 509,
TraderDisabled = 512,
ItemHasBeenSold,
NotEnoughSpaceForMoney = 518,
HTTPInvalidSSLCertificate = 526,
UnknownRagfairError = 550,
UnknownRagfairError2,
UnknownMatchMakerError = 600,
SessionParametersError,
SessionLost,
ServerNotRegistered = 604,
UnknownQuestError = 700,
QuestBadParam = 702,
QuestNotFound,
QuestIsUnavailable,
NoFreeSpaceForRewards,
WrongQuestStatus,
CantCompleteQuest,
UnknownMailError = 900,
TooManyFriendRequests = 925,
UnknownScriptExecutionError = 1000,
UnknownRepairingError = 1200,
UnknownInsuranceError = 1300,
UnknownCurrencyExchangeError = 1400,
OfferNotFound = 1503,
NotEnoughSpace = 1505,
OfferOutOfStock,
OfferSold,
RagfairUnavailable = 1511,
BannedErrorCode = 1513,
InsufficientNumberInStock = 1516,
TooManyItemsToSell,
IncorrectClientPrice = 1519,
ExaminationFailed = 22001,
ItemAlreadyExamined,
UnknownNginxError = 9000,
ParseResponseError,
UnknownMatchmakerError = 503000,
UnknownGroupError = 502000,
GroupRequestNotFound = 502002,
GroupFull = 502004,
PlayerAlreadyInGroup,
PlayerNotInGroup,
PlayerNotLeader,
CantChangeReadyState = 502010,
PlayerForbiddenGroupInvites,
LeaderAlreadyReady,
GroupSendInviteError,
PlayerIsOffline,
PlayerIsNotSearchingForGroup = 502018,
PlayerTooManyPartyRequests = 502021,
PlayerIsInRegularMode = 502032,
PlayerIsInPveMode,
PlayerAlreadyLookingForGame = 503001,
PlayerInRaid,
LimitForPresetsReached = 504001,
PlayerProfileNotFound = 505001,
NoArenaSync = 507006
}
+1 -1
View File
@@ -98,7 +98,7 @@ public class HttpResponseUtil
public ItemEventRouterResponse AppendErrorToOutput(
ItemEventRouterResponse output,
string? message = null,
BackendErrorCodes errorCode = BackendErrorCodes.NONE
BackendErrorCodes errorCode = BackendErrorCodes.None
)
{
if (string.IsNullOrEmpty(message))