040be2feaa
Convert constructors into primary constructors Simplified logic with use of ??, ??= and method groups Cleaned up redundant conditional access qualifiers
324 lines
10 KiB
C#
324 lines
10 KiB
C#
using SPTarkov.DI.Annotations;
|
|
using SPTarkov.Server.Core.Helpers;
|
|
using SPTarkov.Server.Core.Models.Common;
|
|
using SPTarkov.Server.Core.Models.Eft.Common;
|
|
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
|
using SPTarkov.Server.Core.Models.Eft.Customization;
|
|
using SPTarkov.Server.Core.Models.Eft.Hideout;
|
|
using SPTarkov.Server.Core.Models.Eft.ItemEvent;
|
|
using SPTarkov.Server.Core.Models.Eft.Trade;
|
|
using SPTarkov.Server.Core.Models.Enums;
|
|
using SPTarkov.Server.Core.Models.Utils;
|
|
using SPTarkov.Server.Core.Routers;
|
|
using SPTarkov.Server.Core.Servers;
|
|
using SPTarkov.Server.Core.Services;
|
|
using SPTarkov.Server.Core.Utils.Cloners;
|
|
|
|
namespace SPTarkov.Server.Core.Controllers;
|
|
|
|
[Injectable]
|
|
public class CustomizationController(
|
|
ISptLogger<CustomizationController> logger,
|
|
EventOutputHolder eventOutputHolder,
|
|
DatabaseService databaseService,
|
|
SaveServer saveServer,
|
|
ServerLocalisationService serverLocalisationService,
|
|
ProfileHelper profileHelper,
|
|
ICloner cloner,
|
|
PaymentService paymentService
|
|
)
|
|
{
|
|
/// <summary>
|
|
/// Get purchasable clothing items from trader that match players side (usec/bear)
|
|
/// </summary>
|
|
/// <param name="traderId">trader to look up clothing for</param>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <returns>Suit array</returns>
|
|
public List<Suit> GetTraderSuits(MongoId traderId, MongoId sessionId)
|
|
{
|
|
var pmcData = profileHelper.GetPmcProfile(sessionId);
|
|
var clothing = databaseService.GetCustomization();
|
|
var suits = databaseService.GetTrader(traderId).Suits;
|
|
|
|
var matchingSuits = suits?.Where(s => clothing.ContainsKey(s.SuiteId)).ToList();
|
|
matchingSuits = matchingSuits
|
|
?.Where(s =>
|
|
clothing[s.SuiteId]?.Properties?.Side?.Contains(pmcData?.Info?.Side ?? string.Empty)
|
|
?? false
|
|
)
|
|
.ToList();
|
|
|
|
if (matchingSuits == null)
|
|
{
|
|
throw new Exception(
|
|
serverLocalisationService.GetText(
|
|
"customisation-unable_to_get_trader_suits",
|
|
traderId
|
|
)
|
|
);
|
|
}
|
|
|
|
return matchingSuits;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle CustomizationBuy event
|
|
/// Purchase/unlock a clothing item from a trader
|
|
/// </summary>
|
|
/// <param name="pmcData">Player profile</param>
|
|
/// <param name="buyClothingRequest">Request object</param>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <returns>ItemEventRouterResponse</returns>
|
|
public ItemEventRouterResponse BuyCustomisation(
|
|
PmcData pmcData,
|
|
BuyClothingRequestData buyClothingRequest,
|
|
MongoId sessionId
|
|
)
|
|
{
|
|
var output = eventOutputHolder.GetOutput(sessionId);
|
|
|
|
var traderOffer = GetTraderClothingOffer(sessionId, buyClothingRequest.Offer);
|
|
if (traderOffer is null)
|
|
{
|
|
logger.Error(
|
|
serverLocalisationService.GetText(
|
|
"customisation-unable_to_find_suit_by_id",
|
|
buyClothingRequest.Offer
|
|
)
|
|
);
|
|
return output;
|
|
}
|
|
|
|
var suitId = traderOffer.SuiteId;
|
|
if (OutfitAlreadyPurchased(traderOffer.SuiteId, sessionId))
|
|
{
|
|
var suitDetails = databaseService.GetCustomization().GetValueOrDefault(suitId);
|
|
logger.Error(
|
|
serverLocalisationService.GetText(
|
|
"customisation-item_already_purchased",
|
|
new { itemId = suitDetails?.Id, itemName = suitDetails?.Name }
|
|
)
|
|
);
|
|
|
|
return output;
|
|
}
|
|
|
|
// Charge player for buying item
|
|
PayForClothingItems(sessionId, pmcData, buyClothingRequest.Items, output);
|
|
|
|
var profile = saveServer.GetProfile(sessionId);
|
|
|
|
// TODO: Merge with function _profileHelper.addHideoutCustomisationUnlock
|
|
var rewardToStore = new CustomisationStorage
|
|
{
|
|
Id = suitId,
|
|
Source = CustomisationSource.UNLOCKED_IN_GAME,
|
|
Type = CustomisationType.SUITE,
|
|
};
|
|
|
|
profile.CustomisationUnlocks.Add(rewardToStore);
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Has an outfit been purchased by a player
|
|
/// </summary>
|
|
/// <param name="suitId">clothing id</param>
|
|
/// <param name="sessionId">Session id of profile to check for clothing in</param>
|
|
/// <returns>true if already purchased</returns>
|
|
protected bool OutfitAlreadyPurchased(MongoId suitId, MongoId sessionId)
|
|
{
|
|
var fullProfile = profileHelper.GetFullProfile(sessionId);
|
|
|
|
// Check if clothing can be found by id
|
|
return fullProfile.CustomisationUnlocks.Exists(customisation =>
|
|
Equals(customisation.Id, suitId)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get clothing offer from trader by suit id
|
|
/// </summary>
|
|
/// <param name="sessionId">Session/Player id</param>
|
|
/// <param name="offerId"></param>
|
|
/// <returns>Suit</returns>
|
|
protected Suit? GetTraderClothingOffer(MongoId sessionId, string? offerId)
|
|
{
|
|
var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s.Id == offerId);
|
|
if (foundSuit is null)
|
|
{
|
|
logger.Error(
|
|
serverLocalisationService.GetText(
|
|
"customisation-unable_to_find_suit_with_id",
|
|
offerId
|
|
)
|
|
);
|
|
}
|
|
|
|
return foundSuit;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update output object and player profile with purchase details
|
|
/// </summary>
|
|
/// <param name="sessionId">Session id</param>
|
|
/// <param name="pmcData">Player profile</param>
|
|
/// <param name="itemsToPayForClothingWith">Clothing purchased</param>
|
|
/// <param name="output">Client response</param>
|
|
protected void PayForClothingItems(
|
|
MongoId sessionId,
|
|
PmcData pmcData,
|
|
List<PaymentItemForClothing>? itemsToPayForClothingWith,
|
|
ItemEventRouterResponse output
|
|
)
|
|
{
|
|
if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var inventoryItemToProcess in itemsToPayForClothingWith)
|
|
{
|
|
var options = new ProcessBuyTradeRequestData
|
|
{
|
|
SchemeItems =
|
|
[
|
|
new IdWithCount
|
|
{
|
|
Count = inventoryItemToProcess.Count.Value,
|
|
Id = inventoryItemToProcess.Id,
|
|
},
|
|
],
|
|
TransactionId = Traders.RAGMAN,
|
|
Action = "BuyCustomization",
|
|
Type = "",
|
|
ItemId = "",
|
|
Count = 0,
|
|
SchemeId = 0,
|
|
};
|
|
|
|
paymentService.PayMoney(pmcData, options, sessionId, output);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all suits from Traders
|
|
/// </summary>
|
|
/// <param name="sessionId">Session/Player id</param>
|
|
/// <returns></returns>
|
|
protected List<Suit> GetAllTraderSuits(MongoId sessionId)
|
|
{
|
|
var traders = databaseService.GetTraders();
|
|
var result = new List<Suit>();
|
|
|
|
foreach (var (traderId, trader) in traders)
|
|
{
|
|
if (
|
|
trader.Base?.CustomizationSeller is not null
|
|
&& trader.Base.CustomizationSeller.Value
|
|
)
|
|
{
|
|
result.AddRange(GetTraderSuits(traderId, sessionId));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle client/hideout/customization/offer/list
|
|
/// </summary>
|
|
/// <param name="sessionId">Session/Player id</param>
|
|
/// <returns></returns>
|
|
public HideoutCustomisation GetHideoutCustomisation(MongoId sessionId)
|
|
{
|
|
return databaseService.GetHideout().Customisation!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle client/customization/storage
|
|
/// </summary>
|
|
/// <param name="sessionId">Session/Player id</param>
|
|
/// <returns></returns>
|
|
public List<CustomisationStorage> GetCustomisationStorage(MongoId sessionId)
|
|
{
|
|
var customisationResultsClone = cloner.Clone(
|
|
databaseService.GetTemplates().CustomisationStorage
|
|
);
|
|
|
|
var profile = profileHelper.GetFullProfile(sessionId);
|
|
if (profile is null)
|
|
{
|
|
return customisationResultsClone!;
|
|
}
|
|
|
|
customisationResultsClone!.AddRange(profile.CustomisationUnlocks ?? []);
|
|
|
|
return customisationResultsClone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle CustomizationSet event
|
|
/// </summary>
|
|
/// <param name="sessionId">Session/Player id</param>
|
|
/// <param name="request"></param>
|
|
/// <param name="pmcData">Players PMC profile</param>
|
|
/// <returns></returns>
|
|
public ItemEventRouterResponse SetCustomisation(
|
|
MongoId sessionId,
|
|
CustomizationSetRequest request,
|
|
PmcData pmcData
|
|
)
|
|
{
|
|
foreach (var customisation in request.Customizations)
|
|
{
|
|
switch (customisation.Type)
|
|
{
|
|
case "dogTag":
|
|
pmcData.Customization!.DogTag = customisation.Id;
|
|
break;
|
|
case "suite":
|
|
ApplyClothingItemToProfile(customisation, pmcData);
|
|
break;
|
|
default:
|
|
logger.Error($"Unhandled customisation type: {customisation.Type}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return eventOutputHolder.GetOutput(sessionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies a purchased suit to the players doll
|
|
/// </summary>
|
|
/// <param name="customisation">Suit to apply to profile</param>
|
|
/// <param name="pmcData">Profile to update</param>
|
|
protected void ApplyClothingItemToProfile(CustomizationSetOption customisation, PmcData pmcData)
|
|
{
|
|
if (!databaseService.GetCustomization().TryGetValue(customisation.Id, out var dbSuit))
|
|
{
|
|
logger.Error(
|
|
$"Unable to find suit customisation id: {customisation.Id}, cannot apply clothing to player profile: {pmcData.Id}"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Body
|
|
if (dbSuit.Parent == CustomisationTypeId.UPPER)
|
|
{
|
|
pmcData.Customization.Body = dbSuit.Properties.Body;
|
|
pmcData.Customization.Hands = dbSuit.Properties.Hands;
|
|
|
|
return;
|
|
}
|
|
|
|
// Feet
|
|
if (dbSuit.Parent == CustomisationTypeId.LOWER)
|
|
{
|
|
pmcData.Customization.Feet = dbSuit.Properties.Feet;
|
|
}
|
|
}
|
|
}
|