364 lines
13 KiB
C#
364 lines
13 KiB
C#
using Core.Helpers;
|
|
using Core.Models.Eft.Common;
|
|
using Core.Models.Eft.Common.Tables;
|
|
using Core.Models.Eft.ItemEvent;
|
|
using Core.Models.Eft.Ragfair;
|
|
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 SptCommon.Annotations;
|
|
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
|
|
|
namespace Core.Controllers;
|
|
|
|
[Injectable]
|
|
public class TradeController(
|
|
ISptLogger<TradeController> _logger,
|
|
DatabaseService _databaseService,
|
|
EventOutputHolder _eventOutputHolder,
|
|
TradeHelper _tradeHelper,
|
|
TimeUtil _timeUtil,
|
|
RandomUtil _randomUtil,
|
|
HashUtil _hashUtil,
|
|
ItemHelper _itemHelper,
|
|
ProfileHelper _profileHelper,
|
|
RagfairOfferHelper _ragfairOfferHelper,
|
|
TraderHelper _traderHelper,
|
|
RagfairServer _ragfairServer,
|
|
HttpResponseUtil _httpResponseUtil,
|
|
LocalisationService _localisationService,
|
|
RagfairPriceService _ragfairPriceService,
|
|
MailSendService _mailSendService,
|
|
ConfigServer _configServer
|
|
)
|
|
{
|
|
protected RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
|
|
protected TraderConfig _traderConfig = _configServer.GetConfig<TraderConfig>();
|
|
|
|
/// <summary>
|
|
/// Handle TradingConfirm event
|
|
/// </summary>
|
|
/// <param name="pmcData">Players PMC profile</param>
|
|
/// <param name="request"></param>
|
|
/// <param name="sessionID">Session/Player id</param>
|
|
/// <returns></returns>
|
|
public ItemEventRouterResponse ConfirmTrading(
|
|
PmcData pmcData,
|
|
ProcessBaseTradeRequestData request,
|
|
string sessionID)
|
|
{
|
|
var output = _eventOutputHolder.GetOutput(sessionID);
|
|
|
|
// Buying
|
|
if (request.Type == "buy_from_trader")
|
|
{
|
|
var foundInRaid = _traderConfig.PurchasesAreFoundInRaid;
|
|
var buyData = (ProcessBuyTradeRequestData) request;
|
|
_tradeHelper.BuyItem(pmcData, buyData, sessionID, foundInRaid, output);
|
|
|
|
return output;
|
|
}
|
|
|
|
// Selling
|
|
if (request.Type == "sell_to_trader")
|
|
{
|
|
var sellData = (ProcessSellTradeRequestData) request;
|
|
_tradeHelper.sellItem(pmcData, pmcData, sellData, sessionID, output);
|
|
|
|
return output;
|
|
}
|
|
|
|
var errorMessage = $"Unhandled trade event: {request.Type}";
|
|
_logger.Error(errorMessage);
|
|
|
|
return _httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.RagfairUnavailable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle RagFairBuyOffer event
|
|
/// </summary>
|
|
/// <param name="pmcData">Players PMC profile</param>
|
|
/// <param name="request"></param>
|
|
/// <param name="sessionID">Session/Player id</param>
|
|
/// <returns></returns>
|
|
public ItemEventRouterResponse ConfirmRagfairTrading(
|
|
PmcData pmcData,
|
|
ProcessRagfairTradeRequestData request,
|
|
string sessionID)
|
|
{
|
|
var output = _eventOutputHolder.GetOutput(sessionID);
|
|
|
|
foreach (var offer in request.Offers)
|
|
{
|
|
var fleaOffer = _ragfairServer.GetOffer(offer.Id);
|
|
if (fleaOffer is null)
|
|
{
|
|
return _httpResponseUtil.AppendErrorToOutput(
|
|
output,
|
|
$"Offer with ID {offer.Id} not found",
|
|
BackendErrorCodes.OfferNotFound
|
|
);
|
|
}
|
|
|
|
if (offer.Count == 0)
|
|
{
|
|
var errorMessage = _localisationService.GetText(
|
|
"ragfair-unable_to_purchase_0_count_item",
|
|
_itemHelper.GetItem(fleaOffer.Items[0].Template).Value.Name
|
|
);
|
|
return _httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.OfferOutOfStock);
|
|
}
|
|
|
|
if (_ragfairOfferHelper.OfferIsFromTrader(fleaOffer))
|
|
{
|
|
BuyTraderItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
|
|
}
|
|
else
|
|
{
|
|
BuyPmcItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
|
|
}
|
|
|
|
// Exit loop early if problem found
|
|
if (output.Warnings?.Count > 0)
|
|
{
|
|
return output;
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buy an item off the flea sold by a trader
|
|
/// </summary>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <param name="pmcData">Player profile</param>
|
|
/// <param name="fleaOffer">Offer being purchased</param>
|
|
/// <param name="requestOffer">request data from client</param>
|
|
/// <param name="output">Output to send back to client</param>
|
|
protected void BuyTraderItemFromRagfair(
|
|
string sessionId,
|
|
PmcData pmcData,
|
|
RagfairOffer fleaOffer,
|
|
OfferRequest requestOffer,
|
|
ItemEventRouterResponse output)
|
|
{
|
|
// Skip buying items when player doesn't have needed loyalty
|
|
if (PlayerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer, pmcData))
|
|
{
|
|
var errorMessage = $"Unable to buy item: {fleaOffer.Items[0].Template} from trader: {fleaOffer.User.Id} as loyalty level too low, skipping";
|
|
if (_logger.IsLogEnabled(LogLevel.Debug))
|
|
{
|
|
_logger.Debug(errorMessage);
|
|
}
|
|
|
|
_httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.RagfairUnavailable);
|
|
|
|
return;
|
|
}
|
|
|
|
// Trigger purchase of item from trader
|
|
var buyData = new ProcessBuyTradeRequestData
|
|
{
|
|
Action = "TradingConfirm",
|
|
Type = "buy_from_ragfair",
|
|
TransactionId = fleaOffer.User.Id,
|
|
ItemId = fleaOffer.Root,
|
|
Count = requestOffer.Count,
|
|
SchemeId = 0,
|
|
SchemeItems = requestOffer.Items
|
|
};
|
|
_tradeHelper.BuyItem(pmcData, buyData, sessionId, _traderConfig.PurchasesAreFoundInRaid, output);
|
|
|
|
// Remove/lower offer quantity of item purchased from trader flea offer
|
|
_ragfairServer.ReduceOfferQuantity(fleaOffer.Id, requestOffer.Count ?? 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buy an item off the flea sold by a PMC
|
|
/// </summary>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <param name="pmcData">Player profile</param>
|
|
/// <param name="fleaOffer">Offer being purchased</param>
|
|
/// <param name="requestOffer">request data from client</param>
|
|
/// <param name="output">Output to send back to client</param>
|
|
protected void BuyPmcItemFromRagfair(
|
|
string sessionId,
|
|
PmcData pmcData,
|
|
RagfairOffer fleaOffer,
|
|
OfferRequest requestOffer,
|
|
ItemEventRouterResponse output)
|
|
{
|
|
var buyData = new ProcessBuyTradeRequestData
|
|
{
|
|
Action = "TradingConfirm",
|
|
Type = "buy_from_ragfair",
|
|
TransactionId = "ragfair",
|
|
ItemId = fleaOffer.Id, // Store ragfair offerId in buyRequestData.item_id
|
|
Count = requestOffer.Count,
|
|
SchemeId = 0,
|
|
SchemeItems = requestOffer.Items
|
|
};
|
|
|
|
// buyItem() must occur prior to removing the offer stack, otherwise item inside offer doesn't exist for confirmTrading() to use
|
|
_tradeHelper.BuyItem(pmcData, buyData, sessionId, _ragfairConfig.Dynamic.PurchasesAreFoundInRaid, output);
|
|
if (output.Warnings?.Count > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// resolve when a profile buy another profile's offer
|
|
var offerOwnerId = fleaOffer.User.Id;
|
|
var offerBuyCount = requestOffer.Count;
|
|
|
|
if (IsPlayerOffer(fleaOffer.Id, fleaOffer.User?.Id))
|
|
{
|
|
// Complete selling the offer now it has been purchased
|
|
_ragfairOfferHelper.CompleteOffer(offerOwnerId, fleaOffer, offerBuyCount ?? 0);
|
|
|
|
return;
|
|
}
|
|
|
|
// Remove/lower offer quantity of item purchased from PMC flea offer
|
|
_ragfairServer.ReduceOfferQuantity(fleaOffer.Id, requestOffer.Count ?? 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is the provided offerId and ownerId from a player made offer
|
|
/// </summary>
|
|
/// <param name="offerId">id of the offer</param>
|
|
/// <param name="offerOwnerId">Owner id</param>
|
|
/// <returns>true if offer was made by a player</returns>
|
|
protected bool IsPlayerOffer(
|
|
string offerId,
|
|
string? offerOwnerId)
|
|
{
|
|
// No ownerId, not player offer
|
|
if (offerOwnerId is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var offerCreatorProfile = _profileHelper.GetPmcProfile(offerOwnerId);
|
|
if (offerCreatorProfile is null || offerCreatorProfile.RagfairInfo.Offers?.Count == 0)
|
|
// No profile or no offers
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Does offer id exist in profile
|
|
return offerCreatorProfile.RagfairInfo.Offers.Any(offer => offer.Id == offerId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does Player have necessary trader loyalty to purchase flea offer
|
|
/// </summary>
|
|
/// <param name="fleaOffer">Flea offer being bought</param>
|
|
/// <param name="pmcData">Player profile</param>
|
|
/// <returns>True if player can buy offer</returns>
|
|
protected bool PlayerLacksTraderLoyaltyLevelToBuyOffer(
|
|
RagfairOffer fleaOffer,
|
|
PmcData pmcData)
|
|
{
|
|
return fleaOffer.LoyaltyLevel > pmcData.TradersInfo[fleaOffer.User.Id].LoyaltyLevel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle SellAllFromSavage event
|
|
/// </summary>
|
|
/// <param name="pmcData">Players PMC profile</param>
|
|
/// <param name="request"></param>
|
|
/// <param name="sessionId">Session/Player id</param>
|
|
/// <returns></returns>
|
|
public ItemEventRouterResponse SellScavItemsToFence(
|
|
PmcData pmcData,
|
|
SellScavItemsToFenceRequestData request,
|
|
string sessionId)
|
|
{
|
|
var output = _eventOutputHolder.GetOutput(sessionId);
|
|
|
|
MailMoneyToPlayer(sessionId, (int) request.TotalValue, Traders.FENCE);
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send the specified rouble total to player as mail
|
|
/// </summary>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <param name="roublesToSend">amount of roubles to send</param>
|
|
/// <param name="trader">Trader to sell items to</param>
|
|
protected void MailMoneyToPlayer(
|
|
string sessionId,
|
|
int roublesToSend,
|
|
string trader)
|
|
{
|
|
if (_logger.IsLogEnabled(LogLevel.Debug))
|
|
{
|
|
_logger.Debug($"Selling scav items to fence for {roublesToSend} roubles");
|
|
}
|
|
|
|
// Create single currency item with all currency on it
|
|
var rootCurrencyReward = new Item
|
|
{
|
|
Id = _hashUtil.Generate(),
|
|
Template = Money.ROUBLES,
|
|
Upd = new Upd
|
|
{
|
|
StackObjectsCount = roublesToSend
|
|
}
|
|
};
|
|
|
|
// Ensure money is properly split to follow its max stack size limit
|
|
var curencyReward = _itemHelper.SplitStackIntoSeparateItems(rootCurrencyReward);
|
|
|
|
// Send mail from trader
|
|
_mailSendService.SendLocalisedNpcMessageToPlayer(
|
|
sessionId,
|
|
_traderHelper.GetTraderById(trader).ToString(),
|
|
MessageType.MESSAGE_WITH_ITEMS,
|
|
_randomUtil.GetArrayValue(_databaseService.GetTrader(trader).Dialogue.TryGetValue("SoldItems", out var items) ? items : new List<string>()),
|
|
curencyReward.SelectMany(x => x).ToList(),
|
|
_timeUtil.GetHoursAsSeconds(72)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Looks up an items children and gets total handbook price for them
|
|
/// </summary>
|
|
/// <param name="parentItemId">parent item that has children we want to sum price of</param>
|
|
/// <param name="items">All items (parent + children)</param>
|
|
/// <param name="handbookPrices">Prices of items from handbook</param>
|
|
/// <param name="traderDetails">Trader being sold to, to perform buy category check against</param>
|
|
/// <returns>Rouble price</returns>
|
|
protected int GetPriceOfItemAndChildren(
|
|
string parentItemId,
|
|
List<Item> items,
|
|
Dictionary<string, int?> handbookPrices,
|
|
TraderBase traderDetails)
|
|
{
|
|
var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(items, parentItemId);
|
|
|
|
var totalPrice = 0;
|
|
foreach (var itemToSell in itemWithChildren)
|
|
{
|
|
var itemDetails = _itemHelper.GetItem(itemToSell.Template);
|
|
if (!(itemDetails.Key && _itemHelper.IsOfBaseclasses(itemDetails.Value.Id, traderDetails.ItemsBuy.Category)))
|
|
// Skip if tpl isn't item OR item doesn't fulfil match traders buy categories
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Get price of item multiplied by how many are in stack
|
|
totalPrice += (int) ((handbookPrices[itemToSell.Template] ?? 0) * (itemToSell.Upd?.StackObjectsCount ?? 1));
|
|
}
|
|
|
|
return totalPrice;
|
|
}
|
|
}
|