using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Builds; using SPTarkov.Server.Core.Models.Eft.PresetBuild; using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils.Cloners; namespace SPTarkov.Server.Core.Controllers; [Injectable] public class BuildController( ISptLogger logger, DatabaseService databaseService, ProfileHelper profileHelper, ServerLocalisationService serverLocalisationService, ItemHelper itemHelper, SaveServer saveServer, ICloner cloner ) { /// /// Handle client/handbook/builds/my/list /// /// Session/player id /// public UserBuilds? GetUserBuilds(MongoId sessionID) { const string secureContainerSlotId = "SecuredContainer"; var profile = profileHelper.GetFullProfile(sessionID); if (profile.UserBuildData is null) { profile.UserBuildData = new UserBuilds { EquipmentBuilds = [], WeaponBuilds = [], MagazineBuilds = [], }; } // Ensure the secure container in the default presets match what the player has equipped var defaultEquipmentPresetsClone = cloner .Clone(databaseService.GetTemplates().DefaultEquipmentPresets) .ToList(); // Get players secure container var playerSecureContainer = profile.CharacterData?.PmcData?.Inventory?.Items?.FirstOrDefault(x => x.SlotId == secureContainerSlotId ); var firstDefaultItemsSecureContainer = defaultEquipmentPresetsClone .FirstOrDefault() ?.Items?.FirstOrDefault(x => x.SlotId == secureContainerSlotId); if ( playerSecureContainer is not null && playerSecureContainer.Template != firstDefaultItemsSecureContainer?.Template ) // Default equipment presets' secure container tpl doesn't match players secure container tpl { foreach (var defaultPreset in defaultEquipmentPresetsClone) { // Find presets secure container var secureContainer = defaultPreset.Items?.FirstOrDefault(item => item.SlotId == secureContainerSlotId ); if (secureContainer is not null) { secureContainer.Template = playerSecureContainer.Template; } } } // Clone player build data from profile and append the above defaults onto end var userBuildsClone = cloner.Clone(profile?.UserBuildData); userBuildsClone.EquipmentBuilds ??= []; userBuildsClone.EquipmentBuilds?.AddRange(defaultEquipmentPresetsClone); return userBuildsClone; } /// /// Handle client/builds/weapon/save /// /// Session/Player id /// public void SaveWeaponBuild(MongoId sessionId, PresetBuildActionRequestData body) { var pmcData = profileHelper.GetPmcProfile(sessionId); // Replace duplicate Id's. The first item is the base item. // The root ID and the base item ID need to match. body.Items = itemHelper.ReplaceIDs(body.Items, pmcData); body.Root = body.Items.FirstOrDefault().Id; // Create new object ready to save into profile userbuilds.weaponBuilds var newBuild = new WeaponBuild { Id = body.Id, Name = body.Name, Root = body.Root, Items = body.Items, }; var profile = profileHelper.GetFullProfile(sessionId); var savedWeaponBuilds = profile.UserBuildData.WeaponBuilds; var existingBuild = savedWeaponBuilds.FirstOrDefault(x => x.Id == body.Id); if (existingBuild is not null) { // exists, replace profile.UserBuildData.WeaponBuilds.Remove(existingBuild); profile.UserBuildData.WeaponBuilds.Add(existingBuild); } else { // Add fresh profile.UserBuildData.WeaponBuilds.Add(newBuild); } } /// /// Handle client/builds/equipment/save event /// /// Session/player id /// public void SaveEquipmentBuild(MongoId sessionID, PresetBuildActionRequestData request) { var profile = profileHelper.GetFullProfile(sessionID); var pmcData = profile.CharacterData.PmcData; var existingSavedEquipmentBuilds = saveServer .GetProfile(sessionID) .UserBuildData.EquipmentBuilds; // Replace duplicate ID's. The first item is the base item. // Root ID and the base item ID need to match. request.Items = itemHelper.ReplaceIDs(request.Items, pmcData); var newBuild = new EquipmentBuild { Id = request.Id, Name = request.Name, BuildType = EquipmentBuildType.Custom, Root = request.Items[0].Id, Items = request.Items, }; var existingBuild = existingSavedEquipmentBuilds?.FirstOrDefault(build => build.Name == request.Name || build.Id == request.Id ); if (existingBuild is not null) { // Already exists, replace profile.UserBuildData.EquipmentBuilds.Remove(existingBuild); profile.UserBuildData.EquipmentBuilds.Add(newBuild); } else { // Fresh, add new profile.UserBuildData.EquipmentBuilds.Add(newBuild); } } /// /// Handle client/builds/delete /// /// Session/Player id /// public void RemoveBuild(MongoId sessionId, RemoveBuildRequestData request) { RemovePlayerBuild(request.Id, sessionId); } /// /// Handle client/builds/magazine/save /// /// Session/Player id /// public void CreateMagazineTemplate(MongoId sessionId, SetMagazineRequest request) { var result = new MagazineBuild { Id = request.Id, Name = request.Name, Caliber = request.Caliber, TopCount = request.TopCount, BottomCount = request.BottomCount, Items = request.Items, }; var profile = profileHelper.GetFullProfile(sessionId); profile.UserBuildData.MagazineBuilds ??= []; // Check if template with desired name already exists and remove it var magazineBuildToRemove = profile.UserBuildData.MagazineBuilds.FirstOrDefault(item => item.Name == request.Name ); if (magazineBuildToRemove is not null) { profile.UserBuildData.MagazineBuilds.Remove(magazineBuildToRemove); } // Add new template to profile profile.UserBuildData.MagazineBuilds.Add(result); } /// /// Handle client/builds/delete /// Remove build from players profile /// /// /// Session/Player id protected void RemovePlayerBuild(MongoId idToRemove, MongoId sessionID) { var profile = saveServer.GetProfile(sessionID); var weaponBuilds = profile.UserBuildData.WeaponBuilds; var equipmentBuilds = profile.UserBuildData.EquipmentBuilds; var magazineBuilds = profile.UserBuildData.MagazineBuilds; // Check for id in weapon array first var matchingWeaponBuild = weaponBuilds.FirstOrDefault(weaponBuild => weaponBuild.Id == idToRemove ); if (matchingWeaponBuild is not null) { weaponBuilds.Remove(matchingWeaponBuild); return; } // Id not found in weapons, try equipment var matchingEquipmentBuild = equipmentBuilds.FirstOrDefault(equipmentBuild => equipmentBuild.Id == idToRemove ); if (matchingEquipmentBuild is not null) { equipmentBuilds.Remove(matchingEquipmentBuild); return; } // Id not found in weapons/equipment, try mags var matchingMagazineBuild = magazineBuilds.FirstOrDefault(magBuild => magBuild.Id == idToRemove ); if (matchingMagazineBuild is not null) { magazineBuilds.Remove(matchingMagazineBuild); return; } // Not found in weapons,equipment or magazines, not good logger.Error( serverLocalisationService.GetText("build-unable_to_delete_preset", idToRemove) ); } }