using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Generators; 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.Launcher; using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Launcher; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services; namespace SPTarkov.Server.Core.Controllers; [Injectable] public class ProfileController( ISptLogger logger, SaveServer saveServer, CreateProfileService createProfileService, ProfileFixerService profileFixerService, PlayerScavGenerator playerScavGenerator, ProfileHelper profileHelper ) { /// /// Handle /launcher/profiles /// /// public virtual List GetMiniProfiles() { return saveServer.GetProfiles().Select(kvp => GetMiniProfile(kvp.Key)).ToList(); } /// /// Handle launcher/profile/info /// /// Session/Player id /// public virtual MiniProfile GetMiniProfile(MongoId sessionId) { var profile = saveServer.GetProfile(sessionId); if (profile?.CharacterData == null) { throw new Exception($"Unable to find character data for id: {sessionId}. Profile may be corrupt"); } var pmc = profile.CharacterData.PmcData; var maxLvl = profileHelper.GetMaxLevel(); // Player hasn't completed profile creation process, send defaults var currentLevel = pmc?.Info?.Level.GetValueOrDefault(1); var xpToNextLevel = profileHelper.GetExperience((currentLevel ?? 1) + 1); if (pmc?.Info?.Level == null) { return new MiniProfile { Username = profile.ProfileInfo?.Username ?? "", Nickname = "unknown", HasPassword = profile.ProfileInfo?.Password != "", Side = "unknown", CurrentLevel = 0, CurrentExperience = 0, PreviousExperience = 0, NextLevel = xpToNextLevel, MaxLevel = maxLvl, Edition = profile.ProfileInfo?.Edition ?? "", ProfileId = profile.ProfileInfo?.ProfileId ?? "", SptData = profileHelper.GetDefaultSptDataObject(), }; } return new MiniProfile { Username = profile.ProfileInfo?.Username, Nickname = pmc.Info.Nickname, HasPassword = profile.ProfileInfo?.Password != "", Side = pmc.Info.Side, CurrentLevel = pmc.Info.Level, CurrentExperience = pmc.Info.Experience ?? 0, PreviousExperience = currentLevel == 0 ? 0 : profileHelper.GetExperience(currentLevel.Value), NextLevel = xpToNextLevel, MaxLevel = maxLvl, Edition = profile.ProfileInfo?.Edition ?? "", ProfileId = profile.ProfileInfo?.ProfileId ?? "", SptData = profile.SptData, }; } /// /// Handle client/game/profile/list /// /// Session/Player id /// Return a full profile, scav and pmc profiles + meta data public virtual List GetCompleteProfile(MongoId sessionId) { var profile = profileHelper.GetCompleteProfile(sessionId); // Some users like to crank massive skill multipliers and send the client invalid information, // causing a json exception during parsing if (profile.Any()) { if (profile[0].Skills != null) { // Pmc profile is index 0 profileFixerService.CheckForSkillsOverMaxLevel(profile[0]); } if (profile[1].Skills != null) { // We also do the scav profile here because it is also affected by the skill multipliers profileFixerService.CheckForSkillsOverMaxLevel(profile[1]); } } return profile; } /// /// Handle client/game/profile/create /// /// Create profile request /// Player id /// Player id public virtual async ValueTask CreateProfile(ProfileCreateRequestData request, MongoId sessionId) { return await createProfileService.CreateProfile(sessionId, request); } /// /// Generate a player scav object /// PMC profile MUST exist first before player-scav can be generated /// /// Player id /// PmcData public virtual PmcData GeneratePlayerScav(MongoId sessionId) { return playerScavGenerator.Generate(sessionId); } /// /// Handle client/game/profile/nickname/validate /// /// Validate nickname request /// Session/Player id /// public virtual NicknameValidationResult ValidateNickname(ValidateNicknameRequestData request, MongoId sessionId) { if (request.Nickname?.Length < 3) { return NicknameValidationResult.Short; } if (profileHelper.IsNicknameTaken(request, sessionId)) { return NicknameValidationResult.Taken; } return NicknameValidationResult.Valid; } /// /// Handle client/game/profile/nickname/change event /// Client allows player to adjust their profile name /// /// Change nickname request /// Player id /// public virtual NicknameValidationResult ChangeNickname(ProfileChangeNicknameRequestData request, MongoId sessionId) { var output = ValidateNickname(new ValidateNicknameRequestData { Nickname = request.Nickname }, sessionId); if (output == NicknameValidationResult.Valid) { var pmcData = profileHelper.GetPmcProfile(sessionId); pmcData.Info.Nickname = request.Nickname; pmcData.Info.LowerNickname = request.Nickname.ToLowerInvariant(); } return output; } /// /// Handle client/game/profile/voice/change event /// /// Change voice request /// Player id public virtual void ChangeVoice(ProfileChangeVoiceRequestData request, MongoId sessionID) { var pmcData = profileHelper.GetPmcProfile(sessionID); pmcData.Customization.Voice = request.Voice; } /// /// Handle client/game/profile/search /// /// Search profiles request /// Player id /// Found profiles public virtual List SearchProfiles(SearchProfilesRequestData request, MongoId sessionID) { var result = new List(); // Find any profiles with a nickname containing the entered name var allProfiles = saveServer.GetProfiles().Values; foreach (var profile in allProfiles) { var pmcProfile = profile?.CharacterData?.PmcData; if (!pmcProfile?.Info?.LowerNickname?.Contains(request.Nickname.ToLowerInvariant()) ?? false) { continue; } result.Add(profileHelper.GetChatRoomMemberFromPmcProfile(pmcProfile)); } return result; } /// /// Handle client/profile/status /// /// Session/Player id /// public virtual GetProfileStatusResponseData GetProfileStatus(MongoId sessionId) { var account = saveServer.GetProfile(sessionId).ProfileInfo; var response = new GetProfileStatusResponseData { MaxPveCountExceeded = false, Profiles = [ new ProfileStatusData { ProfileId = account.ScavengerId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0, }, new ProfileStatusData { ProfileId = account.ProfileId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0, }, ], }; return response; } /// /// Handle client/profile/view /// /// Session/Player id /// Get other profile request /// GetOtherProfileResponse public virtual GetOtherProfileResponse GetOtherProfile(MongoId sessionId, GetOtherProfileRequest request) { // Find the profile by the account ID, fall back to the current player if we can't find the account var profileToView = profileHelper.GetFullProfileByAccountId(request.AccountId); if (profileToView?.CharacterData?.PmcData is null || profileToView.CharacterData.ScavData is null) { logger.Warning($"Unable to get profile: {request.AccountId} to show, falling back to own profile"); profileToView = profileHelper.GetFullProfile(sessionId); } var profileToViewPmc = profileToView.CharacterData.PmcData; var profileToViewScav = profileToView.CharacterData.ScavData; // Get the keys needed to find profiles hideout-related items var hideoutKeys = new HashSet(); hideoutKeys.UnionWith(profileToViewPmc.Inventory.HideoutAreaStashes.Keys); hideoutKeys.Add(profileToViewPmc.Inventory.HideoutCustomizationStashId); // Find hideout items e.g. posters var hideoutRootItems = profileToViewPmc.Inventory.Items.Where(x => hideoutKeys.Contains(x.Id)); var itemsToReturn = new List(); foreach (var rootItems in hideoutRootItems) { // Check each root items for children and add var itemWithChildren = profileToViewPmc.Inventory.Items.GetItemWithChildren(rootItems.Id); itemsToReturn.AddRange(itemWithChildren); } var profile = new GetOtherProfileResponse { Id = profileToViewPmc.Id, Aid = profileToViewPmc.Aid, Info = new OtherProfileInfo { Nickname = profileToViewPmc.Info.Nickname, Side = profileToViewPmc.Info.Side, Experience = profileToViewPmc.Info.Experience, MemberCategory = (int)(profileToViewPmc.Info.MemberCategory ?? MemberCategory.Default), BannedState = profileToViewPmc.Info.BannedState, BannedUntil = profileToViewPmc.Info.BannedUntil, RegistrationDate = profileToViewPmc.Info.RegistrationDate, }, Customization = new OtherProfileCustomization { Head = profileToViewPmc.Customization.Head, Body = profileToViewPmc.Customization.Body, Feet = profileToViewPmc.Customization.Feet, Hands = profileToViewPmc.Customization.Hands, Dogtag = profileToViewPmc.Customization.DogTag, Voice = profileToViewPmc.Customization.Voice, }, Skills = profileToViewPmc.Skills, Equipment = new OtherProfileEquipment { Id = profileToViewPmc.Inventory.Equipment, Items = profileToViewPmc.Inventory.Items }, Achievements = profileToViewPmc.Achievements, FavoriteItems = profileHelper.GetOtherProfileFavorites(profileToViewPmc), PmcStats = new OtherProfileStats { Eft = new OtherProfileSubStats { TotalInGameTime = profileToViewPmc.Stats.Eft.TotalInGameTime, OverAllCounters = profileToViewPmc.Stats.Eft.OverallCounters, }, }, ScavStats = new OtherProfileStats { Eft = new OtherProfileSubStats { TotalInGameTime = profileToViewScav.Stats.Eft.TotalInGameTime, OverAllCounters = profileToViewScav.Stats.Eft.OverallCounters, }, }, Hideout = profileToViewPmc.Hideout, CustomizationStash = profileToViewPmc.Inventory.HideoutCustomizationStashId, HideoutAreaStashes = profileToViewPmc.Inventory.HideoutAreaStashes, Items = itemsToReturn, }; return profile; } /// /// Handle client/profile/settings /// /// Session/Player id /// Get profile settings request /// public virtual bool SetChosenProfileIcon(MongoId sessionId, GetProfileSettingsRequest request) { var profileToUpdate = profileHelper.GetPmcProfile(sessionId); if (profileToUpdate == null) { return false; } if (request.MemberCategory != null) { profileToUpdate.Info.SelectedMemberCategory = request.MemberCategory as MemberCategory?; } if (request.SquadInviteRestriction != null) { profileToUpdate.Info.SquadInviteRestriction = request.SquadInviteRestriction; } return true; } }