using Core.Annotations; using Core.Generators; using Core.Helpers; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Profile; using Core.Models.Enums; using Core.Models.Utils; using Core.Routers; using Core.Servers; using Core.Utils; using Core.Utils.Cloners; namespace Core.Services; [Injectable] public class CreateProfileService { protected ISptLogger _logger; protected TimeUtil _timeUtil; protected HashUtil _hashUtil; protected DatabaseService _databaseService; protected LocalisationService _localisationService; protected ProfileHelper _profileHelper; protected ItemHelper _itemHelper; protected TraderHelper _traderHelper; protected QuestHelper _questHelper; protected QuestRewardHelper _questRewardHelper; protected ProfileFixerService _profileFixerService; protected SaveServer _saveServer; protected EventOutputHolder _eventOutputHolder; protected PlayerScavGenerator _playerScavGenerator; protected ICloner _cloner; public CreateProfileService( ISptLogger logger, TimeUtil timeUtil, HashUtil hashUtil, DatabaseService databaseService, LocalisationService localisationService, ProfileHelper profileHelper, ItemHelper itemHelper, TraderHelper traderHelper, QuestHelper questHelper, QuestRewardHelper questRewardHelper, ProfileFixerService profileFixerService, SaveServer saveServer, EventOutputHolder eventOutputHolder, PlayerScavGenerator playerScavGenerator, ICloner cloner) { _logger = logger; _timeUtil = timeUtil; _hashUtil = hashUtil; _databaseService = databaseService; _localisationService = localisationService; _profileHelper = profileHelper; _itemHelper = itemHelper; _traderHelper = traderHelper; _questHelper = questHelper; _questRewardHelper = questRewardHelper; _profileFixerService = profileFixerService; _saveServer = saveServer; _eventOutputHolder = eventOutputHolder; _playerScavGenerator = playerScavGenerator; _cloner = cloner; } public string CreateProfile(string sessionId, ProfileCreateRequestData request) { var account = _saveServer.GetProfile(sessionId).ProfileInfo; var profileTemplate = _cloner.Clone(_databaseService.GetProfiles()?[account.Edition]?[request.Side.ToLower()]); var pmcData = profileTemplate.Character; // Delete existing profile DeleteProfileBySessionId(sessionId); // PMC pmcData.Id = account.ProfileId; pmcData.Aid = account.Aid; pmcData.Savage = account.ScavengerId; pmcData.SessionId = sessionId; pmcData.Info.Nickname = request.Nickname; pmcData.Info.LowerNickname = account.Username.ToLower(); pmcData.Info.RegistrationDate = _timeUtil.GetTimeStamp(); pmcData.Info.Voice = _databaseService.GetCustomization()[request.VoiceId].Name; pmcData.Stats = _profileHelper.GetDefaultCounters(); pmcData.Info.NeedWipeOptions = []; pmcData.Customization.Head = request.HeadId; pmcData.Health.UpdateTime = _timeUtil.GetTimeStamp(); pmcData.Quests = []; pmcData.Hideout.Seed = _timeUtil.GetTimeStamp() + 8 * 60 * 60 * 24 * 365; // 8 years in future why? who knows, we saw it in live pmcData.RepeatableQuests = []; pmcData.CarExtractCounts = new(); pmcData.CoopExtractCounts = new(); pmcData.Achievements = new(); UpdateInventoryEquipmentId(pmcData); if (pmcData.UnlockedInfo == null) { pmcData.UnlockedInfo = new UnlockedInfo { UnlockedProductionRecipe = [] }; } // Add required items to pmc stash AddMissingInternalContainersToProfile(pmcData); // Change item IDs to be unique pmcData.Inventory.Items = _itemHelper.ReplaceIDs( pmcData.Inventory.Items, pmcData, null, pmcData.Inventory.FastPanel ); // Create profile var profileDetails = new SptProfile { ProfileInfo = account, CharacterData = new Characters { PmcData = pmcData, ScavData = new() }, Suits = profileTemplate.Suits, UserBuildData = profileTemplate.UserBuilds, DialogueRecords = profileTemplate.Dialogues, SptData = _profileHelper.GetDefaultSptDataObject(), VitalityData = new(), InraidData = new(), InsuranceList = [], TraderPurchases = new(), PlayerAchievements = new(), FriendProfileIds = [], CustomisationUnlocks = [] }; AddCustomisationUnlocksToProfile(profileDetails); _profileFixerService.CheckForAndFixPmcProfileIssues(profileDetails.CharacterData.PmcData); _saveServer.AddProfile(profileDetails); if (profileTemplate.Trader.SetQuestsAvailableForStart ?? false) { _questHelper.AddAllQuestsToProfile(profileDetails.CharacterData.PmcData, [QuestStatusEnum.AvailableForStart]); } // Profile is flagged as wanting quests set to ready to hand in and collect rewards if (profileTemplate.Trader.SetQuestsAvailableForFinish ?? false) { _questHelper.AddAllQuestsToProfile(profileDetails.CharacterData.PmcData, [ QuestStatusEnum.AvailableForStart, QuestStatusEnum.Started, QuestStatusEnum.AvailableForFinish, ]); // Make unused response so applyQuestReward works ItemEventRouterResponse? response = _eventOutputHolder.GetOutput(sessionId); // Add rewards for starting quests to profile GivePlayerStartingQuestRewards(profileDetails, sessionId, response); } ResetAllTradersInProfile(sessionId); _saveServer.GetProfile(sessionId).CharacterData.ScavData = _playerScavGenerator.Generate(sessionId); // Store minimal profile and reload it _saveServer.SaveProfile(sessionId); _saveServer.LoadProfile(sessionId); // Completed account creation _saveServer.GetProfile(sessionId).ProfileInfo.IsWiped = false; _saveServer.SaveProfile(sessionId); return pmcData.Id; } /** * Delete a profile * @param sessionID Id of profile to delete */ protected void DeleteProfileBySessionId(string sessionID) { if (_saveServer.GetProfiles().ContainsKey(sessionID)) { _saveServer.DeleteProfileById(sessionID); } else { _logger.Warning( _localisationService.GetText("profile-unable_to_find_profile_by_id_cannot_delete", sessionID) ); } } /** * make profiles pmcData.Inventory.equipment unique * @param pmcData Profile to update */ protected void UpdateInventoryEquipmentId(PmcData pmcData) { var oldEquipmentId = pmcData.Inventory.Equipment; pmcData.Inventory.Equipment = _hashUtil.Generate(); foreach (var item in pmcData.Inventory.Items) { if (item.ParentId == oldEquipmentId) { item.ParentId = pmcData.Inventory.Equipment; continue; } if (item.Id == oldEquipmentId) { item.Id = pmcData.Inventory.Equipment; } } } /** * For each trader reset their state to what a level 1 player would see * @param sessionId Session id of profile to reset */ protected void ResetAllTradersInProfile(string sessionId) { foreach (var traderId in _databaseService.GetTraders().Keys) { _traderHelper.ResetTrader(sessionId, traderId); } } /** * Ensure a profile has the necessary internal containers e.g. questRaidItems / sortingTable * DOES NOT check that stash exists * @param pmcData Profile to check */ protected void AddMissingInternalContainersToProfile(PmcData pmcData) { if (!pmcData.Inventory.Items.Any((item) => item.Id == pmcData.Inventory.HideoutCustomizationStashId)) { pmcData.Inventory.Items.Add(new() { Id = pmcData.Inventory.HideoutCustomizationStashId, Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION, }); } if (!pmcData.Inventory.Items.Any((item) => item.Id == pmcData.Inventory.SortingTable)) { pmcData.Inventory.Items.Add(new() { Id = pmcData.Inventory.SortingTable, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE, }); } if (!pmcData.Inventory.Items.Any((item) => item.Id == pmcData.Inventory.QuestStashItems)) { pmcData.Inventory.Items.Add(new() { Id = pmcData.Inventory.QuestStashItems, Template = ItemTpl.STASH_QUESTOFFLINE, }); } if (!pmcData.Inventory.Items.Any((item) => item.Id == pmcData.Inventory.QuestRaidItems)) { pmcData.Inventory.Items.Add(new() { Id = pmcData.Inventory.QuestRaidItems, Template = ItemTpl.STASH_QUESTRAID, }); } } private void AddCustomisationUnlocksToProfile(SptProfile fullProfile) { // Some game versions have additional dogtag variants, add them switch (GetGameEdition(fullProfile)) { case GameEditions.EDGE_OF_DARKNESS: // Gets EoD tags fullProfile.CustomisationUnlocks.Add( new CustomisationStorage { Id = "6746fd09bafff85008048838", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); fullProfile.CustomisationUnlocks.Add(new CustomisationStorage { Id = "67471938bafff850080488b7", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); break; case GameEditions.UNHEARD: // Gets EoD+Unheard tags fullProfile.CustomisationUnlocks.Add(new CustomisationStorage { Id = "6746fd09bafff85008048838", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); fullProfile.CustomisationUnlocks.Add(new CustomisationStorage { Id = "67471938bafff850080488b7", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); fullProfile.CustomisationUnlocks.Add(new CustomisationStorage { Id = "67471928d17d6431550563b5", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); fullProfile.CustomisationUnlocks.Add(new CustomisationStorage { Id = "6747193f170146228c0d2226", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); break; } var pretigeLevel = fullProfile?.CharacterData?.PmcData?.Info?.PrestigeLevel; if (pretigeLevel is not null) { if (pretigeLevel >= 1) { fullProfile.CustomisationUnlocks.Add(new CustomisationStorage { Id = "674dbf593bee1152d407f005", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); } if (pretigeLevel >= 2) { fullProfile.CustomisationUnlocks.Add(new CustomisationStorage { Id = "675dcfea7ae1a8792107ca99", Source = CustomisationSource.DEFAULT, Type = CustomisationType.DOG_TAG, }); } } } private string GetGameEdition(SptProfile profile) { return "TODO"; } /** * Iterate over all quests in player profile, inspect rewards for the quests current state (accepted/completed) * and send rewards to them in mail * @param profileDetails Player profile * @param sessionID Session id * @param response Event router response */ protected void GivePlayerStartingQuestRewards( SptProfile profileDetails, string sessionID, ItemEventRouterResponse response ) { foreach (var quest in profileDetails.CharacterData.PmcData.Quests) { var questFromDb = _questHelper.GetQuestFromDb(quest.QId, profileDetails.CharacterData.PmcData); // Get messageId of text to send to player as text message in game // Copy of code from QuestController.acceptQuest() var messageId = _questHelper.GetMessageIdForQuestStart( questFromDb.StartedMessageText, questFromDb.Description ); var itemRewards = _questRewardHelper.ApplyQuestReward( profileDetails.CharacterData.PmcData, quest.QId, QuestStatusEnum.Started, sessionID, response ); /* TODO: _mailSendService.sendLocalisedNpcMessageToPlayer( sessionID, this.traderHelper.getTraderById(questFromDb.traderId), MessageType.QUEST_START, messageId, itemRewards, this.timeUtil.getHoursAsSeconds(100), ); */ } } }