tradeHelper and traderHelper
This commit is contained in:
@@ -1,15 +1,44 @@
|
||||
using SptCommon.Annotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Transactions;
|
||||
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.Eft.Trade;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Utils;
|
||||
using Core.Routers;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
|
||||
namespace Core.Helpers;
|
||||
|
||||
[Injectable]
|
||||
public class TradeHelper()
|
||||
public class TradeHelper(
|
||||
ISptLogger<TradeHelper> _logger,
|
||||
DatabaseService _databaseService,
|
||||
EventOutputHolder _eventOutputHolder,
|
||||
TraderHelper _traderHelper,
|
||||
ItemHelper _itemHelper,
|
||||
PaymentService _paymentService,
|
||||
FenceService _fenceService,
|
||||
LocalisationService _localisationService,
|
||||
HttpResponseUtil _httpResponseUtil,
|
||||
InventoryHelper _inventoryHelper,
|
||||
RagfairServer _ragfairServer,
|
||||
TraderAssortHelper _traderAssortHelper,
|
||||
TraderPurchasePersisterService _traderPurchasePersisterService,
|
||||
ConfigServer _configServer,
|
||||
ICloner _cloner
|
||||
)
|
||||
{
|
||||
|
||||
protected TraderConfig _traderConfig = _configServer.GetConfig<TraderConfig>();
|
||||
protected InventoryConfig _inventoryConfig = _configServer.GetConfig<InventoryConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Buy item from flea or trader
|
||||
/// </summary>
|
||||
@@ -26,9 +55,217 @@ public class TradeHelper()
|
||||
ItemEventRouterResponse output
|
||||
)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
List<Item> offerItems = [];
|
||||
Action<double, ProcessBuyTradeRequestData, string, PmcData>? buyCallback = null;
|
||||
|
||||
if (buyRequestData.TransactionId.ToLower() == "ragfair")
|
||||
{
|
||||
buyCallback = BuyCallback1;
|
||||
// Get raw offer from ragfair, clone to prevent altering offer itself
|
||||
var allOffers = _ragfairServer.GetOffers();
|
||||
var offerWithItemCloned = _cloner.Clone(allOffers.FirstOrDefault(x => x.Id == buyRequestData.ItemId));
|
||||
offerItems = offerWithItemCloned.Items;
|
||||
}
|
||||
else if (buyRequestData.TransactionId == Traders.FENCE)
|
||||
{
|
||||
buyCallback = BuyCallback2;
|
||||
|
||||
var fenceItems = _fenceService.GetRawFenceAssorts().Items;
|
||||
var rootItemIndex = fenceItems.FindIndex(item => item.Id == buyRequestData.ItemId);
|
||||
if (rootItemIndex == -1)
|
||||
{
|
||||
_logger.Debug($"Tried to buy item {buyRequestData.ItemId} from fence that no longer exists");
|
||||
var message = _localisationService.GetText("ragfair-offer_no_longer_exists");
|
||||
_httpResponseUtil.AppendErrorToOutput(output, message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
offerItems = _itemHelper.FindAndReturnChildrenAsItems(fenceItems, buyRequestData.ItemId);
|
||||
}
|
||||
else
|
||||
{
|
||||
buyCallback = BuyCallback3;
|
||||
// Get all trader assort items
|
||||
var traderItems = _traderAssortHelper.GetAssort(sessionID, buyRequestData.TransactionId).Items;
|
||||
|
||||
// Get item + children for purchase
|
||||
var relevantItems = _itemHelper.FindAndReturnChildrenAsItems(traderItems, buyRequestData.ItemId);
|
||||
if (relevantItems.Count == 0)
|
||||
{
|
||||
_logger.Error($"Purchased trader: {buyRequestData.TransactionId} offer: {buyRequestData.ItemId} has no items");
|
||||
}
|
||||
|
||||
offerItems.AddRange(relevantItems);
|
||||
}
|
||||
|
||||
// Get item details from db
|
||||
var itemDbDetails = _itemHelper.GetItem(offerItems.FirstOrDefault().Template).Value;
|
||||
var itemMaxStackSize = itemDbDetails.Properties.StackMaxSize;
|
||||
var itemsToSendTotalCount = buyRequestData.Count;
|
||||
var itemsToSendRemaining = itemsToSendTotalCount;
|
||||
|
||||
// Construct array of items to send to player
|
||||
List<List<Item>> itemsToSendToPlayer = [];
|
||||
while (itemsToSendRemaining > 0)
|
||||
{
|
||||
var offerClone = _cloner.Clone(offerItems);
|
||||
// Handle stackable items that have a max stack size limit
|
||||
var itemCountToSend = Math.Min(itemMaxStackSize ?? 0, itemsToSendRemaining ?? 0);
|
||||
offerClone.FirstOrDefault().Upd.StackObjectsCount = itemCountToSend;
|
||||
|
||||
// Prevent any collisions
|
||||
_itemHelper.RemapRootItemId(offerClone);
|
||||
if (offerClone.Count > 1)
|
||||
{
|
||||
_itemHelper.ReparentItemAndChildren(offerClone.FirstOrDefault(), offerClone);
|
||||
}
|
||||
|
||||
itemsToSendToPlayer.Add(offerClone);
|
||||
|
||||
// Remove amount of items added to player stash
|
||||
itemsToSendRemaining -= itemCountToSend;
|
||||
}
|
||||
|
||||
// Construct request
|
||||
AddItemsDirectRequest request = new AddItemsDirectRequest
|
||||
{
|
||||
ItemsWithModsToAdd = itemsToSendToPlayer,
|
||||
FoundInRaid = foundInRaid,
|
||||
Callback = buyCallback,
|
||||
UseSortingTable = false
|
||||
};
|
||||
|
||||
// Add items + their children to stash
|
||||
_inventoryHelper.AddItemsToStash(sessionID, request, pmcData, output);
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/// Pay for purchase
|
||||
_paymentService.PayMoney(pmcData, buyRequestData, sessionID, output);
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
var errorMessage = $"Transaction failed: {output.Warnings.FirstOrDefault().ErrorMessage}";
|
||||
_httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.UnknownTradingError);
|
||||
}
|
||||
}
|
||||
|
||||
public void BuyCallback1(
|
||||
double buyCount,
|
||||
ProcessBuyTradeRequestData buyRequestData,
|
||||
string sessionID,
|
||||
PmcData pmcData)
|
||||
{
|
||||
var allOffers = _ragfairServer.GetOffers();
|
||||
|
||||
// We store ragfair offerid in buyRequestData.item_id
|
||||
var offerWithItem = allOffers.FirstOrDefault(x => x.Id == buyRequestData.ItemId);
|
||||
var itemPurchased = offerWithItem.Items.FirstOrDefault();
|
||||
|
||||
// Ensure purchase does not exceed trader item limit
|
||||
var assortHasBuyRestrictions = _itemHelper.HasBuyRestrictions(itemPurchased);
|
||||
if (assortHasBuyRestrictions)
|
||||
{
|
||||
this.checkPurchaseIsWithinTraderItemLimit(
|
||||
sessionID,
|
||||
pmcData,
|
||||
buyRequestData.TransactionId,
|
||||
itemPurchased,
|
||||
buyRequestData.ItemId,
|
||||
buyCount
|
||||
);
|
||||
|
||||
// Decrement trader item count
|
||||
PurchaseDetails itemPurchaseDetails = new PurchaseDetails()
|
||||
{
|
||||
Items =
|
||||
[
|
||||
new PurchaseItems()
|
||||
{
|
||||
ItemId = buyRequestData.ItemId,
|
||||
Count = buyCount
|
||||
}
|
||||
],
|
||||
TraderId = buyRequestData.TransactionId
|
||||
};
|
||||
_traderHelper.AddTraderPurchasesToPlayerProfile(sessionID, itemPurchaseDetails, itemPurchased);
|
||||
}
|
||||
}
|
||||
|
||||
public void BuyCallback2(
|
||||
double buyCount,
|
||||
ProcessBuyTradeRequestData buyRequestData,
|
||||
string sessionID,
|
||||
PmcData pmcData)
|
||||
{
|
||||
// Update assort/flea item values
|
||||
var traderAssorts = _traderHelper.GetTraderAssortsByTraderId(buyRequestData.TransactionId).Items;
|
||||
var itemPurchased = traderAssorts.FirstOrDefault(assort => assort.Id == buyRequestData.ItemId);
|
||||
|
||||
// Decrement trader item count
|
||||
itemPurchased.Upd.StackObjectsCount -= buyCount;
|
||||
|
||||
_fenceService.AmendOrRemoveFenceOffer(buyRequestData.ItemId, buyCount);
|
||||
}
|
||||
|
||||
public void BuyCallback3(
|
||||
double buyCount,
|
||||
ProcessBuyTradeRequestData buyRequestData,
|
||||
string sessionID,
|
||||
PmcData pmcData)
|
||||
{
|
||||
// Update assort/flea item values
|
||||
var traderAssorts = _traderHelper.GetTraderAssortsByTraderId(buyRequestData.TransactionId).Items;
|
||||
var itemPurchased = traderAssorts.FirstOrDefault(item => item.Id == buyRequestData.ItemId);
|
||||
|
||||
// Ensure purchase does not exceed trader item limit
|
||||
var assortHasBuyRestrictions = _itemHelper.HasBuyRestrictions(itemPurchased);
|
||||
if (assortHasBuyRestrictions)
|
||||
{
|
||||
// Will throw error if check fails
|
||||
this.checkPurchaseIsWithinTraderItemLimit(
|
||||
sessionID,
|
||||
pmcData,
|
||||
buyRequestData.TransactionId,
|
||||
itemPurchased,
|
||||
buyRequestData.ItemId,
|
||||
buyCount
|
||||
);
|
||||
}
|
||||
|
||||
// Check if trader has enough stock
|
||||
if (itemPurchased.Upd.StackObjectsCount < buyCount)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Unable to purchase {buyCount} items, this would exceed the remaining stock left {itemPurchased.Upd.StackObjectsCount} from the traders assort: {buyRequestData.TransactionId} this refresh"
|
||||
);
|
||||
}
|
||||
|
||||
// Decrement trader item count
|
||||
itemPurchased.Upd.StackObjectsCount -= buyCount;
|
||||
|
||||
if (assortHasBuyRestrictions)
|
||||
{
|
||||
var itemPurchaseDat = new PurchaseDetails()
|
||||
{
|
||||
Items = new List<PurchaseItems>()
|
||||
{
|
||||
new PurchaseItems()
|
||||
{
|
||||
ItemId = buyRequestData.ItemId,
|
||||
Count = buyCount
|
||||
}
|
||||
},
|
||||
TraderId = buyRequestData.TransactionId
|
||||
};
|
||||
|
||||
_traderHelper.AddTraderPurchasesToPlayerProfile(sessionID, itemPurchaseDat, itemPurchased);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sell item to trader
|
||||
/// </summary>
|
||||
@@ -45,7 +282,49 @@ public class TradeHelper()
|
||||
ItemEventRouterResponse output
|
||||
)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// TODO - make more generic to support all quests that have this condition type
|
||||
// Try to reduce perf hit as this is expensive to do every sale
|
||||
// MUST OCCUR PRIOR TO ITEMS BEING REMOVED FROM INVENTORY
|
||||
if (sellRequest.TransactionId == Traders.RAGMAN)
|
||||
{
|
||||
// Edge case, `Circulate` quest needs to track when certain items are sold to him
|
||||
this.incrementCirculateSoldToTraderCounter(profileWithItemsToSell, profileToReceiveMoney, sellRequest);
|
||||
}
|
||||
|
||||
var pattern = @"\s+";
|
||||
|
||||
// Find item in inventory and remove it
|
||||
foreach (var itemToBeRemoved in sellRequest.Items)
|
||||
{
|
||||
var itemIdToFind = Regex.Replace(itemToBeRemoved.Id, pattern, ""); // Strip out whitespace
|
||||
// Find item in player inventory, or show error to player if not found
|
||||
var matchingItemInInventory = profileWithItemsToSell.Inventory.Items.FirstOrDefault(x => x.Id == itemIdToFind);
|
||||
if (matchingItemInInventory is null)
|
||||
{
|
||||
var errorMessage = $"Unable to sell item {itemToBeRemoved.Id}, cannot be found in player inventory";
|
||||
_logger.Error(errorMessage);
|
||||
|
||||
_httpResponseUtil.AppendErrorToOutput(output, errorMessage);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Selling: id: {matchingItemInInventory.Id} tpl: {matchingItemInInventory.Template}");
|
||||
|
||||
if (sellRequest.TransactionId == Traders.FENCE)
|
||||
{
|
||||
_fenceService.AddItemsToFenceAssort(
|
||||
profileWithItemsToSell.Inventory.Items,
|
||||
matchingItemInInventory
|
||||
);
|
||||
}
|
||||
|
||||
// Remove item from inventory + any child items it has
|
||||
_inventoryHelper.RemoveItem(profileWithItemsToSell, itemToBeRemoved.Id, sessionID, output);
|
||||
}
|
||||
|
||||
// Give player money for sold item(s)
|
||||
_paymentService.GiveProfileMoney(profileToReceiveMoney, sellRequest.Price, sellRequest, output, sessionID);
|
||||
}
|
||||
|
||||
protected void incrementCirculateSoldToTraderCounter(
|
||||
@@ -54,7 +333,74 @@ public class TradeHelper()
|
||||
ProcessSellTradeRequestData sellRequest
|
||||
)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var circulateQuestId = "6663149f1d3ec95634095e75";
|
||||
var activeCirculateQuest = profileToReceiveMoney.Quests.FirstOrDefault(
|
||||
quest => quest.QId == circulateQuestId && quest.Status == QuestStatusEnum.Started
|
||||
);
|
||||
|
||||
// Player not on Circulate quest ,exit
|
||||
if (activeCirculateQuest is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Find related task condition
|
||||
var taskCondition = profileToReceiveMoney.TaskConditionCounters.Values.FirstOrDefault(
|
||||
condition => condition.SourceId == circulateQuestId && condition.Type == "SellItemToTrader"
|
||||
);
|
||||
|
||||
// No relevant condtion in profile, nothing to increment
|
||||
if (taskCondition is null)
|
||||
{
|
||||
_logger.Error("Unable to find `sellToTrader` task counter for Circulate quest in profile, skipping");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Condition exists in profile
|
||||
var circulateQuestDb = _databaseService.GetQuests();
|
||||
if (!circulateQuestDb.TryGetValue(circulateQuestId, out var _))
|
||||
{
|
||||
_logger.Error($"Unable to find quest: {circulateQuestId} in db, skipping");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get sellToTrader condition from quest
|
||||
var sellItemToTraderCondition = circulateQuestDb[circulateQuestId]
|
||||
.Conditions.AvailableForFinish.FirstOrDefault(
|
||||
condition => condition.ConditionType == "SellItemToTrader"
|
||||
);
|
||||
|
||||
// Quest doesnt have a sellItemToTrader condition, nothing to do
|
||||
if (sellItemToTraderCondition is null)
|
||||
{
|
||||
_logger.Error("Unable to find `sellToTrader` counter for Circulate quest in db, skipping");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over items sold to trader
|
||||
var itemsTplsThatIncrement = sellItemToTraderCondition.Target;
|
||||
foreach (var itemSoldToTrader in sellRequest.Items)
|
||||
{
|
||||
// Get sold items' details from profile
|
||||
var itemDetails = profileWithItemsToSell.Inventory.Items.FirstOrDefault(
|
||||
inventoryItem => inventoryItem.Id == itemSoldToTrader.Id
|
||||
);
|
||||
if (itemDetails is null)
|
||||
{
|
||||
_logger.Error($"Unable to find item in inventory to sell to trader with id: {itemSoldToTrader.Id}, cannot increment counter, skipping");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is sold item on the increment list
|
||||
if (itemsTplsThatIncrement.List.Contains(itemDetails.Template))
|
||||
{
|
||||
taskCondition.Value += itemSoldToTrader.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,9 +418,35 @@ public class TradeHelper()
|
||||
string traderId,
|
||||
Item assortBeingPurchased,
|
||||
string assortId,
|
||||
int count
|
||||
double count
|
||||
)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var traderPurchaseData = _traderPurchasePersisterService.GetProfileTraderPurchase(
|
||||
sessionId,
|
||||
traderId,
|
||||
assortBeingPurchased.Id
|
||||
);
|
||||
var traderItemPurchaseLimit = _traderHelper.GetAccountTypeAdjustedTraderPurchaseLimit(
|
||||
(double)assortBeingPurchased.Upd?.BuyRestrictionMax,
|
||||
pmcData.Info.GameVersion
|
||||
);
|
||||
if ((traderPurchaseData?.PurchaseCount ?? 0 + count) > traderItemPurchaseLimit)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Unable to purchase: {count} items, this would exceed your purchase limit of {traderItemPurchaseLimit} from the trader: {traderId} assort: {assortId} this refresh"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record PurchaseDetails
|
||||
{
|
||||
public List<PurchaseItems> Items { get; set; }
|
||||
public string TraderId { get; set; }
|
||||
}
|
||||
|
||||
public record PurchaseItems
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
public double Count { get; set; }
|
||||
}
|
||||
|
||||
@@ -423,14 +423,14 @@ public class TraderHelper(
|
||||
/// <param name="newPurchaseDetails">New item assort id + count</param>
|
||||
public void AddTraderPurchasesToPlayerProfile(
|
||||
string sessionID,
|
||||
KeyValuePair<List<KeyValuePair<string, double>>, string> newPurchaseDetails,
|
||||
PurchaseDetails newPurchaseDetails,
|
||||
Item itemPurchased)
|
||||
{
|
||||
var profile = _profileHelper.GetFullProfile(sessionID);
|
||||
var traderId = newPurchaseDetails.Value;
|
||||
var traderId = newPurchaseDetails.TraderId;
|
||||
|
||||
// Iterate over assorts bought and add to profile
|
||||
foreach (var purchasedItem in newPurchaseDetails.Key)
|
||||
foreach (var purchasedItem in newPurchaseDetails.Items)
|
||||
{
|
||||
var currentTime = _timeUtil.GetTimeStamp();
|
||||
|
||||
@@ -441,18 +441,18 @@ public class TraderHelper(
|
||||
|
||||
// Null guard when dict doesnt exist
|
||||
|
||||
if (profile.TraderPurchases[traderId][purchasedItem.Key] is null)
|
||||
if (profile.TraderPurchases[traderId][purchasedItem.ItemId] is null)
|
||||
{
|
||||
profile.TraderPurchases[traderId][purchasedItem.Key] = new TraderPurchaseData
|
||||
profile.TraderPurchases[traderId][purchasedItem.ItemId] = new TraderPurchaseData
|
||||
{
|
||||
PurchaseCount = purchasedItem.Value,
|
||||
PurchaseCount = purchasedItem.Count,
|
||||
PurchaseTimestamp = currentTime,
|
||||
};
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.TraderPurchases[traderId][purchasedItem.Key].PurchaseCount + purchasedItem.Value >
|
||||
if (profile.TraderPurchases[traderId][purchasedItem.ItemId].PurchaseCount + purchasedItem.Count >
|
||||
GetAccountTypeAdjustedTraderPurchaseLimit(
|
||||
(double)itemPurchased.Upd.BuyRestrictionMax,
|
||||
profile.CharacterData.PmcData.Info.GameVersion
|
||||
@@ -471,8 +471,8 @@ public class TraderHelper(
|
||||
);
|
||||
}
|
||||
|
||||
profile.TraderPurchases[traderId][purchasedItem.Key].PurchaseCount += purchasedItem.Value;
|
||||
profile.TraderPurchases[traderId][purchasedItem.Key].PurchaseTimestamp = currentTime;
|
||||
profile.TraderPurchases[traderId][purchasedItem.ItemId].PurchaseCount += purchasedItem.Count;
|
||||
profile.TraderPurchases[traderId][purchasedItem.ItemId].PurchaseTimestamp = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user