From 3c0c94245cef6bab53be5df24bbbd31f2bc69a60 Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 16 Jan 2025 21:21:23 +0000 Subject: [PATCH] Partially implemented RepeatableQuestController --- Core/Controllers/RepeatableQuestController.cs | 222 +++++++++++++++++- Core/Helpers/RepeatableQuestHelper.cs | 21 +- Core/Models/Eft/Common/Globals.cs | 2 +- 3 files changed, 235 insertions(+), 10 deletions(-) diff --git a/Core/Controllers/RepeatableQuestController.cs b/Core/Controllers/RepeatableQuestController.cs index 536019dd..ceacb2ea 100644 --- a/Core/Controllers/RepeatableQuestController.cs +++ b/Core/Controllers/RepeatableQuestController.cs @@ -7,6 +7,7 @@ using Core.Models.Spt.Config; using Core.Models.Spt.Repeatable; using Core.Generators; using Core.Helpers; +using Core.Models.Enums; using Core.Models.Utils; using Core.Routers; using Core.Servers; @@ -32,9 +33,10 @@ public class RepeatableQuestController protected RepeatableQuestGenerator _repeatableQuestGenerator; protected RepeatableQuestHelper _repeatableQuestHelper; protected QuestHelper _questHelper; + protected DatabaseService _databaseService; protected ConfigServer _configServer; protected ICloner _cloner; - private readonly QuestConfig _questConfig; + protected QuestConfig _questConfig; public RepeatableQuestController( ISptLogger logger, @@ -50,6 +52,7 @@ public class RepeatableQuestController RepeatableQuestGenerator repeatableQuestGenerator, RepeatableQuestHelper repeatableQuestHelper, QuestHelper questHelper, + DatabaseService databaseService, ConfigServer configServer, ICloner cloner) { @@ -66,6 +69,7 @@ public class RepeatableQuestController _repeatableQuestGenerator = repeatableQuestGenerator; _repeatableQuestHelper = repeatableQuestHelper; _questHelper = questHelper; + _databaseService = databaseService; _configServer = configServer; _cloner = cloner; @@ -190,26 +194,230 @@ public class RepeatableQuestController private PmcDataRepeatableQuest GetRepeatableQuestSubTypeFromProfile(RepeatableQuestConfig repeatableConfig, PmcData pmcData) { - throw new NotImplementedException(); + // Get from profile, add if missing + var repeatableQuestDetails = pmcData.RepeatableQuests.FirstOrDefault( + (repeatable) => repeatable.Name == repeatableConfig.Name); + if (repeatableQuestDetails is not null) + { + // Not in profile, generate + var hasAccess = _profileHelper.HasAccessToRepeatableFreeRefreshSystem(pmcData); + repeatableQuestDetails = new PmcDataRepeatableQuest(){ + Id = repeatableConfig.Id, + Name= repeatableConfig.Name, + ActiveQuests= [], + InactiveQuests= [], + EndTime= 0, + ChangeRequirement= { }, + FreeChanges= hasAccess? repeatableConfig.FreeChanges: 0, + FreeChangesAvailable= hasAccess? repeatableConfig.FreeChangesAvailable: 0, + }; + + // Add base object that holds repeatable data to profile + pmcData.RepeatableQuests.Add(repeatableQuestDetails); + } + + return repeatableQuestDetails; } private bool CanProfileAccessRepeatableQuests(RepeatableQuestConfig repeatableConfig, PmcData pmcData) { - throw new NotImplementedException(); + // PMC and daily quests not unlocked yet + if (repeatableConfig.Side == "Pmc" && !PlayerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig)) + { + return false; + } + + // Scav and daily quests not unlocked yet + if (repeatableConfig.Side == "Scav" && !PlayerHasDailyScavQuestsUnlocked(pmcData)) + { + _logger.Debug("Daily scav quests still locked, Intel center not built"); + + return false; + } + + return true; + } + + /** + * Does player have daily pmc quests unlocked + * @param pmcData Player profile to check + * @param repeatableConfig Config of daily type to check + * @returns True if unlocked + */ + private bool PlayerHasDailyPmcQuestsUnlocked(PmcData pmcData, RepeatableQuestConfig repeatableConfig) + { + return pmcData.Info.Level >= repeatableConfig.MinPlayerLevel; + } + + /** + * Does player have daily scav quests unlocked + * @param pmcData Player profile to check + * @returns True if unlocked + */ + private bool PlayerHasDailyScavQuestsUnlocked(PmcData pmcData) + { + return ( + pmcData?.Hideout?.Areas?.FirstOrDefault((hideoutArea) => hideoutArea.Type == HideoutAreas.INTEL_CENTER)?.Level >= 1 + ); } private void ProcessExpiredQuests(PmcDataRepeatableQuest generatedRepeatables, PmcData pmcData) { - throw new NotImplementedException(); + var questsToKeep = new List(); + foreach (var activeQuest in generatedRepeatables.ActiveQuests) { + var questStatusInProfile = pmcData.Quests.FirstOrDefault((quest) => quest.QId == activeQuest.Id); + if (questStatusInProfile is null) + { + continue; + } + + // Keep finished quests in list so player can hand in + if (questStatusInProfile.Status == QuestStatusEnum.AvailableForFinish) + { + questsToKeep.Add(activeQuest); + _logger.Debug($"Keeping repeatable quest: ${ activeQuest.Id} in activeQuests since it is available to hand in"); + + continue; + } + + // Clean up quest-related counters being left in profile + _profileFixerService.RemoveDanglingConditionCounters(pmcData); + + // Remove expired quest from pmc.quest array + pmcData.Quests = pmcData.Quests.Where((quest) => quest.QId != activeQuest.Id).ToList(); + + // Store in inactive array + generatedRepeatables.InactiveQuests.Add(activeQuest); + } + + generatedRepeatables.ActiveQuests = questsToKeep; } - private QuestTypePool GenerateQuestPool(RepeatableQuestConfig repeatableConfig, double? infoLevel) + private QuestTypePool GenerateQuestPool(RepeatableQuestConfig repeatableConfig, int? pmcLevel) { - throw new NotImplementedException(); + var questPool = CreateBaseQuestPool(repeatableConfig); + + // Get the allowed locations based on the PMC's level + var locations = GetAllowedLocationsForPmcLevel(repeatableConfig.Locations, pmcLevel.Value); + + // Populate Exploration and Pickup quest locations + foreach (var location in locations) { + if (location.Key != ELocationName.any) + { + questPool.Pool.Exploration.Locations[location.Key] = location.Value; + questPool.Pool.Pickup.Locations[location.Key] = location.Value; + } + } + + // Add "any" to pickup quest pool + questPool.Pool.Pickup.Locations[ELocationName.any] = ["any"]; + + var eliminationConfig = _repeatableQuestHelper.GetEliminationConfigByPmcLevel(pmcLevel.Value, repeatableConfig); + var targetsConfig = _repeatableQuestHelper.ProbabilityObjectArray(eliminationConfig.Targets); + + // Populate Elimination quest targets and their locations + foreach (var target in targetsConfig) { + // Target is boss + //if (target.isBoss) + //{ + // questPool.Pool.Elimination.Targets[targetKey] = new { locations: ["any"] }; + //} + //else + //{ + // // Non-boss targets + // var possibleLocations = locations; + + // var allowedLocations = + // targetKey == "Savage" + // ? possibleLocations.filter((location) => location != "laboratory") // Exclude labs for Savage targets. + // : possibleLocations; + + // questPool.Pool.Elimination.Targets[targetKey] = new { Locations = allowedLocations }; + //} + _logger.Error("NOT IMPLEMENTED - GenerateQuestPool"); + } + + return questPool; + } + + private QuestTypePool CreateBaseQuestPool(RepeatableQuestConfig repeatableConfig) + { + return new QuestTypePool + { + Types = _cloner.Clone(repeatableConfig.Types), + Pool = new QuestPool + { + Exploration = new ExplorationPool + { + Locations = new Dictionary>() + }, + Elimination = new EliminationPool + { + Targets = new EliminationTargetPool() + }, + Pickup = new ExplorationPool + { + Locations = new Dictionary>() + } + }, + }; + } + + private Dictionary> GetAllowedLocationsForPmcLevel(Dictionary> locations, int pmcLevel) + { + var allowedLocation = new Dictionary>(); + + foreach (var locationKvP in locations) { + var locationNames = new List(); + foreach (var locationName in locationKvP.Value) { + if (IsPmcLevelAllowedOnLocation(locationName, pmcLevel)) + { + locationNames.Add(locationName); + } + } + + if (locationNames.Count > 0) + { + allowedLocation[locationKvP.Key] = locationNames; + } + } + + return allowedLocation; + } + + /** + * Return true if the given pmcLevel is allowed on the given location + * @param location The location name to check + * @param pmcLevel The level of the pmc + * @returns True if the given pmc level is allowed to access the given location + */ + protected bool IsPmcLevelAllowedOnLocation(string location, int pmcLevel) { + // All PMC levels are allowed for 'any' location requirement + if (location == ELocationName.any.ToString()) { + return true; + } + + var locationBase = _databaseService.GetLocation(location.ToLower())?.Base; + if (locationBase is not null) { + return true; + } + + return pmcLevel <= locationBase.RequiredPlayerLevelMax && pmcLevel >= locationBase.RequiredPlayerLevelMin; } private int GetQuestCount(RepeatableQuestConfig repeatableConfig, PmcData pmcData) { - throw new NotImplementedException(); + if (repeatableConfig.Name.ToLower() == "daily" + && _profileHelper.HasEliteSkillLevel(SkillTypes.Charisma, pmcData) + ) + { + // Elite charisma skill gives extra daily quest(s) + return (repeatableConfig.NumQuests + + _databaseService.GetGlobals().Configuration.SkillsSettings.Charisma.BonusSettings.EliteBonusSettings + .RepeatableQuestExtraCount.GetValueOrDefault(0) + ); + } + + return repeatableConfig.NumQuests; } } diff --git a/Core/Helpers/RepeatableQuestHelper.cs b/Core/Helpers/RepeatableQuestHelper.cs index 52ac348b..73d34bff 100644 --- a/Core/Helpers/RepeatableQuestHelper.cs +++ b/Core/Helpers/RepeatableQuestHelper.cs @@ -1,11 +1,20 @@ -using Core.Annotations; +using Core.Annotations; using Core.Models.Spt.Config; +using Core.Models.Utils; namespace Core.Helpers; [Injectable] public class RepeatableQuestHelper { + protected ISptLogger _logger; + + public RepeatableQuestHelper( + ISptLogger logger) + { + _logger = logger; + } + /// /// Get the relevant elimination config based on the current players PMC level /// @@ -19,8 +28,16 @@ public class RepeatableQuestHelper throw new NotImplementedException(); } - public object ProbabilityObjectArray(object configArrayInput) // TODO: ProbabilityObjectArray for return type , param type was List> + public Dictionary> ProbabilityObjectArray(object configArrayInput) // TODO: ProbabilityObjectArray for return type , param type was List> { + _logger.Error("Fuck this in particular, go look up ProbabilityObjectArray in node server, candidate for rewrite"); throw new NotImplementedException(); + var x = new Dictionary>(); + } + + public class ProbabilityData + { + public int RelativeProbability { get; set; } + public T Data { get; set; } } } diff --git a/Core/Models/Eft/Common/Globals.cs b/Core/Models/Eft/Common/Globals.cs index e91ee4b6..b5d569ed 100644 --- a/Core/Models/Eft/Common/Globals.cs +++ b/Core/Models/Eft/Common/Globals.cs @@ -3662,7 +3662,7 @@ public class EliteBonusSettings public double? FenceStandingLossDiscount { get; set; } [JsonPropertyName("RepeatableQuestExtraCount")] - public double? RepeatableQuestExtraCount { get; set; } + public int? RepeatableQuestExtraCount { get; set; } [JsonPropertyName("ScavCaseDiscount")] public double? ScavCaseDiscount { get; set; }