Files
SPT-Server-Build/Libraries/SPTarkov.Server.Core/Controllers/CustomizationController.cs
T
Cj 529fe61f23 Controller house keeping (#532)
* Cleanup BotController.cs

* More controller cleanup

* More dialogue changes
2025-08-05 16:24:59 +01:00

283 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;
using Customization = SPTarkov.Server.Core.Models.Eft.Common.Tables.Customization;
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));
matchingSuits = matchingSuits?.Where(s =>
clothing[s.SuiteId]?.Properties?.Side?.Contains(pmcData?.Info?.Side ?? string.Empty) ?? false
);
if (matchingSuits == null)
{
throw new Exception(serverLocalisationService.GetText("customisation-unable_to_get_trader_suits", traderId));
}
return matchingSuits.ToList();
}
/// <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)) ?? false;
}
/// <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, MongoId 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 = MongoId.Empty(),
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>
/// <returns>Hideout customizations</returns>
public HideoutCustomisation GetHideoutCustomisation()
{
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);
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>ItemEventRouterResponse</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;
case "voice":
pmcData.Customization.Voice = customisation.Id;
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;
}
pmcData.Customization ??= new Customization();
switch (dbSuit?.Parent)
{
// Body
case CustomisationTypeId.UPPER:
pmcData.Customization.Body = dbSuit.Properties.Body;
pmcData.Customization.Hands = dbSuit.Properties.Hands;
return;
// Feet
case CustomisationTypeId.LOWER:
pmcData.Customization.Feet = dbSuit.Properties.Feet;
break;
}
}
}