using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.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.Spt.Config; using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils; using Info = SPTarkov.Server.Core.Models.Eft.Profile.Info; namespace SPTarkov.Server.Core.Controllers; [Injectable] public class LauncherController( ISptLogger logger, IReadOnlyList loadedMods, HashUtil hashUtil, SaveServer saveServer, HttpServerHelper httpServerHelper, ProfileHelper profileHelper, DatabaseService databaseService, ServerLocalisationService serverLocalisationService, ConfigServer configServer ) { protected readonly CoreConfig _coreConfig = configServer.GetConfig(); /// /// Handle launcher connecting to server /// /// ConnectResponse public ConnectResponse Connect() { // Get all possible profile types + filter out any that are blacklisted var profileTemplates = databaseService .GetProfileTemplates() .Where(profile => !_coreConfig.Features.CreateNewProfileTypesBlacklist.Contains(profile.Key)) .ToDictionary(); return new ConnectResponse { BackendUrl = httpServerHelper.GetBackendUrl(), Name = _coreConfig.ServerName, Editions = profileTemplates.Select(x => x.Key).ToList(), ProfileDescriptions = GetProfileDescriptions(profileTemplates), }; } /// /// Get descriptive text for each of the profile editions a player can choose, keyed by profile.json profile type e.g. "Edge Of Darkness" /// /// Profiles to get descriptions of /// Dictionary of profile types with related descriptive text protected Dictionary GetProfileDescriptions(Dictionary profileTemplates) { var result = new Dictionary(); foreach (var (profileKey, profile) in profileTemplates) { result.TryAdd(profileKey, serverLocalisationService.GetText(profile.DescriptionLocaleKey)); } return result; } /// /// /// Session/Player id /// public Info? Find(MongoId sessionId) { return saveServer.GetProfiles().TryGetValue(sessionId, out var profile) ? profile.ProfileInfo : null; } /// /// /// /// public MongoId Login(LoginRequestData? info) { foreach (var (sessionId, profile) in saveServer.GetProfiles()) { var account = profile.ProfileInfo; if (info?.Username == account?.Username) { return sessionId; } } return MongoId.Empty(); } /// /// /// /// public async Task Register(RegisterData info) { foreach (var (_, profile) in saveServer.GetProfiles()) { if (info.Username == profile.ProfileInfo?.Username) { return MongoId.Empty(); } } return await CreateAccount(info); } /// /// /// /// protected async Task CreateAccount(RegisterData info) { var profileId = new MongoId(); var scavId = new MongoId(); var newProfileDetails = new Info { ProfileId = profileId, ScavengerId = scavId, Aid = hashUtil.GenerateAccountId(), Username = info.Username, Password = info.Password, IsWiped = true, Edition = info.Edition, }; saveServer.CreateProfile(newProfileDetails); await saveServer.LoadProfileAsync(profileId); await saveServer.SaveProfileAsync(profileId); return profileId; } /// /// /// /// public MongoId ChangeUsername(ChangeRequestData info) { var sessionID = Login(info); if (!sessionID.IsEmpty) { saveServer.GetProfile(sessionID).ProfileInfo!.Username = info.Change; } return sessionID; } /// /// /// /// public string? ChangePassword(ChangeRequestData info) { var sessionID = Login(info); if (!string.IsNullOrEmpty(sessionID)) { saveServer.GetProfile(sessionID).ProfileInfo!.Password = info.Change; } return sessionID; } /// /// Handle launcher requesting profile be wiped /// /// Registration data /// Session id public MongoId Wipe(RegisterData info) { if (!_coreConfig.AllowProfileWipe) { return MongoId.Empty(); } var sessionId = Login(info); if (!sessionId.IsEmpty) { var profileInfo = saveServer.GetProfile(sessionId).ProfileInfo; profileInfo!.Edition = info.Edition; profileInfo.IsWiped = true; } return sessionId; } /// /// /// public string GetCompatibleTarkovVersion() { return _coreConfig.CompatibleTarkovVersion; } /// /// Get the mods the server has currently loaded /// /// Dictionary of mod name and mod details public Dictionary GetLoadedServerMods() { return loadedMods.ToDictionary(sptMod => sptMod.ModMetadata?.Name ?? "UNKNOWN MOD", sptMod => sptMod.ModMetadata); } /// /// Get the mods a profile has ever loaded into game with /// /// Session/Player id /// Array of mod details public List GetServerModsProfileUsed(MongoId sessionId) { var profile = profileHelper.GetFullProfile(sessionId); if (profile?.SptData?.Mods is not null) { return GetProfileModsGroupedByModName(profile?.SptData?.Mods); } return []; } /// /// /// /// public List GetProfileModsGroupedByModName(List profileMods) { // Group all mods used by profile by name var modsGroupedByName = new Dictionary>(); foreach (var mod in profileMods) { if (!modsGroupedByName.ContainsKey(mod.Name)) { modsGroupedByName[mod.Name] = []; } modsGroupedByName[mod.Name].Add(mod); } // Find the highest versioned mod and add to results array var result = new List(); foreach (var (modName, modDatas) in modsGroupedByName) { var modVersions = modDatas.Select(x => x.Version); // var highestVersion = MaxSatisfying(modVersions, "*"); ?? TODO: Node used SemVer here var chosenVersion = modDatas.FirstOrDefault(x => x.Name == modName); // && x.Version == highestVersion if (chosenVersion is null) { continue; } result.Add(chosenVersion); } return result; } }