From 7abcf08f09f667d320d012d8399d8cb6aa859008 Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 16 Jan 2025 20:36:50 +0000 Subject: [PATCH] Implemented `getClientRepeatableQuests` Adjusted various profile properties to match EFT types --- Core/Controllers/RepeatableQuestController.cs | 197 +++++++++++++++++- Core/Generators/PlayerScavGenerator.cs | 4 +- Core/Generators/RepeatableQuestGenerator.cs | 2 +- Core/Models/Eft/Common/Tables/BotBase.cs | 9 +- Core/Services/CreateProfileService.cs | 2 +- 5 files changed, 204 insertions(+), 10 deletions(-) diff --git a/Core/Controllers/RepeatableQuestController.cs b/Core/Controllers/RepeatableQuestController.cs index f2f050f3..536019dd 100644 --- a/Core/Controllers/RepeatableQuestController.cs +++ b/Core/Controllers/RepeatableQuestController.cs @@ -3,19 +3,212 @@ using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Quests; +using Core.Models.Spt.Config; +using Core.Models.Spt.Repeatable; +using Core.Generators; +using Core.Helpers; +using Core.Models.Utils; +using Core.Routers; +using Core.Servers; +using Core.Services; +using Core.Utils; +using Core.Utils.Cloners; namespace Core.Controllers; [Injectable] public class RepeatableQuestController { - // TODO + protected ISptLogger _logger; + protected TimeUtil _timeUtil; + protected HashUtil _hashUtil; + protected RandomUtil _randomUtil; + protected HttpResponseUtil _responseUtil; + protected ProfileHelper _profileHelper; + protected ProfileFixerService _profileFixerService; + protected LocalisationService _localisationService; + protected EventOutputHolder _eventOutputHolder; + protected PaymentService _paymentService; + protected RepeatableQuestGenerator _repeatableQuestGenerator; + protected RepeatableQuestHelper _repeatableQuestHelper; + protected QuestHelper _questHelper; + protected ConfigServer _configServer; + protected ICloner _cloner; + private readonly QuestConfig _questConfig; + + public RepeatableQuestController( + ISptLogger logger, + TimeUtil timeUtil, + HashUtil hashUtil, + RandomUtil randomUtil, + HttpResponseUtil responseUtil, + ProfileHelper profileHelper, + ProfileFixerService profileFixerService, + LocalisationService localisationService, + EventOutputHolder eventOutputHolder, + PaymentService paymentService, + RepeatableQuestGenerator repeatableQuestGenerator, + RepeatableQuestHelper repeatableQuestHelper, + QuestHelper questHelper, + ConfigServer configServer, + ICloner cloner) + { + _logger = logger; + _timeUtil = timeUtil; + _hashUtil = hashUtil; + _randomUtil = randomUtil; + _responseUtil = responseUtil; + _profileHelper = profileHelper; + _profileFixerService = profileFixerService; + _localisationService = localisationService; + _eventOutputHolder = eventOutputHolder; + _paymentService = paymentService; + _repeatableQuestGenerator = repeatableQuestGenerator; + _repeatableQuestHelper = repeatableQuestHelper; + _questHelper = questHelper; + _configServer = configServer; + _cloner = cloner; + + _questConfig = _configServer.GetConfig(); + } + public ItemEventRouterResponse ChangeRepeatableQuest(PmcData pmcData, RepeatableQuestChangeRequest info, string sessionId) { throw new NotImplementedException(); } - public List GetClientRepeatableQuests(string sessionId) + public List GetClientRepeatableQuests(string sessionID) + { + var returnData = new List(); + var fullProfile = _profileHelper.GetFullProfile(sessionID); + var pmcData = fullProfile.CharacterData.PmcData; + var currentTime = _timeUtil.GetTimeStamp(); + + // Daily / weekly / Daily_Savage + foreach (var repeatableConfig in _questConfig.RepeatableQuests) { + // Get daily/weekly data from profile, add empty object if missing + var generatedRepeatables = GetRepeatableQuestSubTypeFromProfile(repeatableConfig, pmcData); + var repeatableTypeLower = repeatableConfig.Name.ToLower(); + + var canAccessRepeatables = CanProfileAccessRepeatableQuests(repeatableConfig, pmcData); + if (!canAccessRepeatables) + { + // Don't send any repeatables, even existing ones + continue; + } + + // Existing repeatables are still valid, add to return data and move to next sub-type + if (currentTime < generatedRepeatables.EndTime - 1) + { + returnData.Add(generatedRepeatables); + + _logger.Debug($"[Quest Check] { repeatableTypeLower} quests are still valid."); + + continue; + } + + // Current time is past expiry time + + // Set endtime to be now + new duration + generatedRepeatables.EndTime = currentTime + repeatableConfig.ResetTime; + generatedRepeatables.InactiveQuests = []; + _logger.Debug($"Generating new { repeatableTypeLower}"); + + // Put old quests to inactive (this is required since only then the client makes them fail due to non-completion) + // Also need to push them to the "inactiveQuests" list since we need to remove them from offraidData.profile.Quests + // after a raid (the client seems to keep quests internally and we want to get rid of old repeatable quests) + // and remove them from the PMC's Quests and RepeatableQuests[i].activeQuests + ProcessExpiredQuests(generatedRepeatables, pmcData); + + // Create dynamic quest pool to avoid generating duplicates + var questTypePool = GenerateQuestPool(repeatableConfig, pmcData.Info.Level); + + // Add repeatable quests of this loops sub-type (daily/weekly) + for (var i = 0; i < GetQuestCount(repeatableConfig, pmcData); i++) + { + RepeatableQuest quest = new RepeatableQuest(); + var lifeline = 0; + while (quest.Id is null && questTypePool.Types.Count > 0) + { + quest = _repeatableQuestGenerator.GenerateRepeatableQuest( + sessionID, + pmcData.Info.Level, + pmcData.TradersInfo, + questTypePool, + repeatableConfig); + lifeline++; + if (lifeline > 10) + { + _logger.Debug("We were stuck in repeatable quest generation. This should never happen. Please report"); + + break; + } + } + + // check if there are no more quest types available + if (questTypePool.Types.Count == 0) + { + break; + } + quest.Side = repeatableConfig.Side; + generatedRepeatables.ActiveQuests.Add(quest); + } + + // Nullguard + fullProfile.SptData.FreeRepeatableRefreshUsedCount ??= new Dictionary(); + + + // Reset players free quest count for this repeatable sub-type as we're generating new repeatables for this group (daily/weekly) + fullProfile.SptData.FreeRepeatableRefreshUsedCount[repeatableTypeLower] = 0; + + // Create stupid redundant change requirements from quest data + foreach (var quest in generatedRepeatables.ActiveQuests) { + generatedRepeatables.ChangeRequirement[quest.Id] = new ChangeRequirement{ + ChangeCost = quest.ChangeCost, + ChangeStandingCost = _randomUtil.GetArrayValue([0, 0.01]), // Randomise standing cost to replace + }; + } + + // Reset free repeatable values in player profile to defaults + generatedRepeatables.FreeChanges = repeatableConfig.FreeChanges; + generatedRepeatables.FreeChangesAvailable = repeatableConfig.FreeChanges; + + returnData.Add( new PmcDataRepeatableQuest{ + Id = repeatableConfig.Id, + Name = generatedRepeatables.Name, + EndTime = generatedRepeatables.EndTime, + ActiveQuests = generatedRepeatables.ActiveQuests, + InactiveQuests = generatedRepeatables.InactiveQuests, + ChangeRequirement = generatedRepeatables.ChangeRequirement, + FreeChanges = generatedRepeatables.FreeChanges, + FreeChangesAvailable = generatedRepeatables.FreeChanges, + }); + } + + return returnData; + } + + private PmcDataRepeatableQuest GetRepeatableQuestSubTypeFromProfile(RepeatableQuestConfig repeatableConfig, PmcData pmcData) + { + throw new NotImplementedException(); + } + + private bool CanProfileAccessRepeatableQuests(RepeatableQuestConfig repeatableConfig, PmcData pmcData) + { + throw new NotImplementedException(); + } + + private void ProcessExpiredQuests(PmcDataRepeatableQuest generatedRepeatables, PmcData pmcData) + { + throw new NotImplementedException(); + } + + private QuestTypePool GenerateQuestPool(RepeatableQuestConfig repeatableConfig, double? infoLevel) + { + throw new NotImplementedException(); + } + + private int GetQuestCount(RepeatableQuestConfig repeatableConfig, PmcData pmcData) { throw new NotImplementedException(); } diff --git a/Core/Generators/PlayerScavGenerator.cs b/Core/Generators/PlayerScavGenerator.cs index 6d4f3b03..387e8a21 100644 --- a/Core/Generators/PlayerScavGenerator.cs +++ b/Core/Generators/PlayerScavGenerator.cs @@ -322,7 +322,7 @@ public class PlayerScavGenerator return _profileHelper.GetDefaultCounters(); } - protected double GetScavLevel(PmcData scavProfile) + protected int GetScavLevel(PmcData scavProfile) { // Info can be null on initial account creation if (scavProfile?.Info?.Level == null) @@ -331,7 +331,7 @@ public class PlayerScavGenerator return scavProfile?.Info?.Level ?? 1; } - protected double GetScavExperience(PmcData scavProfile) + protected int GetScavExperience(PmcData scavProfile) { // Info can be null on initial account creation if (scavProfile?.Info?.Experience == null) diff --git a/Core/Generators/RepeatableQuestGenerator.cs b/Core/Generators/RepeatableQuestGenerator.cs index 48c5dcb4..4099a423 100644 --- a/Core/Generators/RepeatableQuestGenerator.cs +++ b/Core/Generators/RepeatableQuestGenerator.cs @@ -25,7 +25,7 @@ public class RepeatableQuestGenerator /// RepeatableQuest public RepeatableQuest GenerateRepeatableQuest( string sessionId, - int pmcLevel, + int? pmcLevel, Dictionary pmcTraderInfo, QuestTypePool questTypePool, RepeatableQuestConfig repeatableConfig diff --git a/Core/Models/Eft/Common/Tables/BotBase.cs b/Core/Models/Eft/Common/Tables/BotBase.cs index 5871f6a3..99da7f4a 100644 --- a/Core/Models/Eft/Common/Tables/BotBase.cs +++ b/Core/Models/Eft/Common/Tables/BotBase.cs @@ -153,10 +153,11 @@ public class Info public bool? HasCoopExtension { get; set; } public bool? HasPveGame { get; set; } public string? Voice { get; set; } - public double? Level { get; set; } - public double? Experience { get; set; } + public int? Level { get; set; } + public int? Experience { get; set; } + [JsonConverter(typeof(StringToNumberFactoryConverter))] - public long? RegistrationDate { get; set; } + public int? RegistrationDate { get; set; } public string? GameVersion { get; set; } public double? AccountType { get; set; } public MemberCategory? MemberCategory { get; set; } @@ -184,7 +185,7 @@ public class Info [JsonPropertyName("isMigratedSkills")] public bool? IsMigratedSkills { get; set; } - public double? PrestigeLevel { get; set; } + public int? PrestigeLevel { get; set; } } public class BotInfoSettings diff --git a/Core/Services/CreateProfileService.cs b/Core/Services/CreateProfileService.cs index 5966b6f7..d2ba6f9e 100644 --- a/Core/Services/CreateProfileService.cs +++ b/Core/Services/CreateProfileService.cs @@ -83,7 +83,7 @@ public class CreateProfileService pmcData.SessionId = sessionId; pmcData.Info.Nickname = request.Nickname; pmcData.Info.LowerNickname = account.Username.ToLower(); - pmcData.Info.RegistrationDate = _timeUtil.GetTimeStamp(); + pmcData.Info.RegistrationDate = (int)_timeUtil.GetTimeStamp(); pmcData.Info.Voice = _databaseService.GetCustomization()[request.VoiceId].Name; pmcData.Stats = _profileHelper.GetDefaultCounters(); pmcData.Info.NeedWipeOptions = [];